| // 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 <utility> |
| |
| #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<base::SingleThreadTaskRunner>& task_runner, |
| std::unique_ptr<VideoOverlayFactory> 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<DemuxerStream*> streams = media_resource_->GetAllStreams(); |
| std::vector<mojom::DemuxerStreamPtr> stream_proxies; |
| |
| for (auto* stream : streams) { |
| mojom::DemuxerStreamPtr stream_proxy; |
| std::unique_ptr<MojoDemuxerStreamImpl> mojo_stream = |
| base::MakeUnique<MojoDemuxerStreamImpl>(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<mojom::DemuxerStreamPtr> 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 |