// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "media/mojo/clients/mojo_renderer.h" #include #include "base/bind.h" #include "base/callback_helpers.h" #include "base/location.h" #include "base/single_thread_task_runner.h" #include "media/base/media_resource.h" #include "media/base/pipeline_status.h" #include "media/base/renderer_client.h" #include "media/base/video_renderer_sink.h" #include "media/mojo/clients/mojo_demuxer_stream_impl.h" #include "media/renderers/video_overlay_factory.h" namespace media { MojoRenderer::MojoRenderer( const scoped_refptr& task_runner, std::unique_ptr video_overlay_factory, VideoRendererSink* video_renderer_sink, mojom::RendererPtr remote_renderer) : task_runner_(task_runner), video_overlay_factory_(std::move(video_overlay_factory)), video_renderer_sink_(video_renderer_sink), remote_renderer_info_(remote_renderer.PassInterface()), client_binding_(this), media_time_interpolator_(&media_clock_) { DVLOG(1) << __func__; } MojoRenderer::~MojoRenderer() { DVLOG(1) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); CancelPendingCallbacks(); } void MojoRenderer::Initialize(MediaResource* media_resource, media::RendererClient* client, const PipelineStatusCB& init_cb) { DVLOG(1) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(media_resource); if (encountered_error_) { task_runner_->PostTask( FROM_HERE, base::Bind(init_cb, PIPELINE_ERROR_INITIALIZATION_FAILED)); return; } media_resource_ = media_resource; init_cb_ = init_cb; switch (media_resource_->GetType()) { case MediaResource::Type::STREAM: InitializeRendererFromStreams(client); break; case MediaResource::Type::URL: InitializeRendererFromUrl(client); break; } } void MojoRenderer::InitializeRendererFromStreams( media::RendererClient* client) { DVLOG(1) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); // Create mojom::DemuxerStream for each demuxer stream and bind its lifetime // to the pipe. std::vector streams = media_resource_->GetAllStreams(); std::vector stream_proxies; for (auto* stream : streams) { mojom::DemuxerStreamPtr stream_proxy; std::unique_ptr mojo_stream = base::MakeUnique(stream, MakeRequest(&stream_proxy)); // Using base::Unretained(this) is safe because |this| owns |mojo_stream|, // and the error handler can't be invoked once |mojo_stream| is destroyed. mojo_stream->set_connection_error_handler( base::Bind(&MojoRenderer::OnDemuxerStreamConnectionError, base::Unretained(this), mojo_stream.get())); streams_.push_back(std::move(mojo_stream)); stream_proxies.push_back(std::move(stream_proxy)); } BindRemoteRendererIfNeeded(); mojom::RendererClientAssociatedPtrInfo client_ptr_info; client_binding_.Bind(&client_ptr_info); // Using base::Unretained(this) is safe because |this| owns // |remote_renderer_|, and the callback won't be dispatched if // |remote_renderer_| is destroyed. remote_renderer_->Initialize( std::move(client_ptr_info), std::move(stream_proxies), base::nullopt, base::nullopt, base::Bind(&MojoRenderer::OnInitialized, base::Unretained(this), client)); } void MojoRenderer::InitializeRendererFromUrl(media::RendererClient* client) { DVLOG(2) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); BindRemoteRendererIfNeeded(); mojom::RendererClientAssociatedPtrInfo client_ptr_info; client_binding_.Bind(&client_ptr_info); MediaUrlParams url_params = media_resource_->GetMediaUrlParams(); // Using base::Unretained(this) is safe because |this| owns // |remote_renderer_|, and the callback won't be dispatched if // |remote_renderer_| is destroyed. std::vector streams; remote_renderer_->Initialize( std::move(client_ptr_info), std::move(streams), url_params.media_url, url_params.first_party_for_cookies, base::Bind(&MojoRenderer::OnInitialized, base::Unretained(this), client)); } void MojoRenderer::SetCdm(CdmContext* cdm_context, const CdmAttachedCB& cdm_attached_cb) { DVLOG(1) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(cdm_context); DCHECK(!cdm_attached_cb.is_null()); DCHECK(cdm_attached_cb_.is_null()); if (encountered_error_) { task_runner_->PostTask(FROM_HERE, base::Bind(cdm_attached_cb, false)); return; } int32_t cdm_id = cdm_context->GetCdmId(); if (cdm_id == CdmContext::kInvalidCdmId) { DVLOG(2) << "MojoRenderer only works with remote CDMs but the CDM ID " "is invalid."; task_runner_->PostTask(FROM_HERE, base::Bind(cdm_attached_cb, false)); return; } BindRemoteRendererIfNeeded(); cdm_attached_cb_ = cdm_attached_cb; remote_renderer_->SetCdm( cdm_id, base::Bind(&MojoRenderer::OnCdmAttached, base::Unretained(this))); } void MojoRenderer::Flush(const base::Closure& flush_cb) { DVLOG(2) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(remote_renderer_.is_bound()); DCHECK(!flush_cb.is_null()); DCHECK(flush_cb_.is_null()); if (encountered_error_) { task_runner_->PostTask(FROM_HERE, flush_cb); return; } { base::AutoLock auto_lock(lock_); if (media_time_interpolator_.interpolating()) media_time_interpolator_.StopInterpolating(); } flush_cb_ = flush_cb; remote_renderer_->Flush( base::Bind(&MojoRenderer::OnFlushed, base::Unretained(this))); } void MojoRenderer::StartPlayingFrom(base::TimeDelta time) { DVLOG(2) << __func__ << "(" << time << ")"; DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(remote_renderer_.is_bound()); { base::AutoLock auto_lock(lock_); media_time_interpolator_.SetBounds(time, time, media_clock_.NowTicks()); media_time_interpolator_.StartInterpolating(); } remote_renderer_->StartPlayingFrom(time); } void MojoRenderer::SetPlaybackRate(double playback_rate) { DVLOG(2) << __func__ << "(" << playback_rate << ")"; DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(remote_renderer_.is_bound()); remote_renderer_->SetPlaybackRate(playback_rate); { base::AutoLock auto_lock(lock_); media_time_interpolator_.SetPlaybackRate(playback_rate); } } void MojoRenderer::SetVolume(float volume) { DVLOG(2) << __func__ << "(" << volume << ")"; DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(remote_renderer_.is_bound()); remote_renderer_->SetVolume(volume); } base::TimeDelta MojoRenderer::GetMediaTime() { base::AutoLock auto_lock(lock_); return media_time_interpolator_.GetInterpolatedTime(); } void MojoRenderer::OnTimeUpdate(base::TimeDelta time, base::TimeDelta max_time, base::TimeTicks capture_time) { DVLOG(4) << __func__ << "(" << time << ", " << max_time << ", " << capture_time << ")"; DCHECK(task_runner_->BelongsToCurrentThread()); base::AutoLock auto_lock(lock_); media_time_interpolator_.SetBounds(time, max_time, capture_time); } void MojoRenderer::OnBufferingStateChange(BufferingState state) { DVLOG(2) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); client_->OnBufferingStateChange(state); } void MojoRenderer::OnEnded() { DVLOG(1) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); client_->OnEnded(); } void MojoRenderer::InitiateScopedSurfaceRequest( const ReceiveSurfaceRequestTokenCB& receive_request_token_cb) { DVLOG(1) << __func__; remote_renderer_->InitiateScopedSurfaceRequest(receive_request_token_cb); } void MojoRenderer::OnError() { DVLOG(1) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(init_cb_.is_null()); encountered_error_ = true; // TODO(tim): Should we plumb error code from remote renderer? // http://crbug.com/410451. client_->OnError(PIPELINE_ERROR_DECODE); } void MojoRenderer::OnVideoNaturalSizeChange(const gfx::Size& size) { DVLOG(2) << __func__ << ": " << size.ToString(); DCHECK(task_runner_->BelongsToCurrentThread()); if (video_overlay_factory_) { video_renderer_sink_->PaintSingleFrame( video_overlay_factory_->CreateFrame(size)); } client_->OnVideoNaturalSizeChange(size); } void MojoRenderer::OnDurationChange(base::TimeDelta duration) { DVLOG(2) << __func__ << ": duration" << duration; client_->OnDurationChange(duration); } void MojoRenderer::OnVideoOpacityChange(bool opaque) { DVLOG(2) << __func__ << ": " << opaque; DCHECK(task_runner_->BelongsToCurrentThread()); client_->OnVideoOpacityChange(opaque); } void MojoRenderer::OnStatisticsUpdate(const PipelineStatistics& stats) { DVLOG(3) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); client_->OnStatisticsUpdate(stats); } void MojoRenderer::OnWaitingForDecryptionKey() { DVLOG(1) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); client_->OnWaitingForDecryptionKey(); } void MojoRenderer::OnConnectionError() { DVLOG(1) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); encountered_error_ = true; CancelPendingCallbacks(); if (client_) client_->OnError(PIPELINE_ERROR_DECODE); } void MojoRenderer::OnDemuxerStreamConnectionError( MojoDemuxerStreamImpl* stream) { DVLOG(1) << __func__ << ": stream=" << stream; DCHECK(task_runner_->BelongsToCurrentThread()); for (auto& s : streams_) { if (s.get() == stream) { s.reset(); return; } } NOTREACHED() << "Unrecognized demuxer stream=" << stream; } void MojoRenderer::BindRemoteRendererIfNeeded() { DVLOG(2) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); // If |remote_renderer_| has already been bound, do nothing. // Note that after Bind() is called, |remote_renderer_| is always bound even // after connection error. if (remote_renderer_.is_bound()) return; // Bind |remote_renderer_| to the |task_runner_|. remote_renderer_.Bind(std::move(remote_renderer_info_)); // Otherwise, set an error handler to catch the connection error. // Using base::Unretained(this) is safe because |this| owns // |remote_renderer_|, and the error handler can't be invoked once // |remote_renderer_| is destroyed. remote_renderer_.set_connection_error_handler( base::Bind(&MojoRenderer::OnConnectionError, base::Unretained(this))); } void MojoRenderer::OnInitialized(media::RendererClient* client, bool success) { DVLOG(1) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(!init_cb_.is_null()); // Only set |client_| after initialization succeeded. No client methods should // be called before this. if (success) client_ = client; base::ResetAndReturn(&init_cb_).Run( success ? PIPELINE_OK : PIPELINE_ERROR_INITIALIZATION_FAILED); } void MojoRenderer::OnFlushed() { DVLOG(1) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(!flush_cb_.is_null()); base::ResetAndReturn(&flush_cb_).Run(); } void MojoRenderer::OnCdmAttached(bool success) { DVLOG(1) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); DCHECK(!cdm_attached_cb_.is_null()); base::ResetAndReturn(&cdm_attached_cb_).Run(success); } void MojoRenderer::CancelPendingCallbacks() { DVLOG(1) << __func__; DCHECK(task_runner_->BelongsToCurrentThread()); if (!init_cb_.is_null()) base::ResetAndReturn(&init_cb_).Run(PIPELINE_ERROR_INITIALIZATION_FAILED); if (!flush_cb_.is_null()) base::ResetAndReturn(&flush_cb_).Run(); if (!cdm_attached_cb_.is_null()) base::ResetAndReturn(&cdm_attached_cb_).Run(false); } } // namespace media