|  | // 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/cdm_context.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/mojo/common/media_type_converters.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, | 
|  | mojo::PendingRemote<mojom::Renderer> remote_renderer) | 
|  | : task_runner_(task_runner), | 
|  | video_overlay_factory_(std::move(video_overlay_factory)), | 
|  | video_renderer_sink_(video_renderer_sink), | 
|  | remote_renderer_pending_remote_(std::move(remote_renderer)), | 
|  | media_time_interpolator_(base::DefaultTickClock::GetInstance()) { | 
|  | DVLOG(1) << __func__; | 
|  | } | 
|  |  | 
|  | MojoRenderer::~MojoRenderer() { | 
|  | DVLOG(1) << __func__; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  |  | 
|  | CancelPendingCallbacks(); | 
|  | } | 
|  |  | 
|  | void MojoRenderer::Initialize(MediaResource* media_resource, | 
|  | media::RendererClient* client, | 
|  | PipelineStatusCallback init_cb) { | 
|  | DVLOG(1) << __func__; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  | DCHECK(media_resource); | 
|  |  | 
|  | if (encountered_error_) { | 
|  | task_runner_->PostTask( | 
|  | FROM_HERE, base::BindOnce(std::move(init_cb), | 
|  | PIPELINE_ERROR_INITIALIZATION_FAILED)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | media_resource_ = media_resource; | 
|  | init_cb_ = std::move(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<mojo::PendingRemote<mojom::DemuxerStream>> stream_proxies; | 
|  |  | 
|  | for (auto* stream : streams) { | 
|  | mojo::PendingRemote<mojom::DemuxerStream> stream_proxy; | 
|  | auto mojo_stream = std::make_unique<MojoDemuxerStreamImpl>( | 
|  | stream, stream_proxy.InitWithNewPipeAndPassReceiver()); | 
|  |  | 
|  | // 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_disconnect_handler( | 
|  | base::BindOnce(&MojoRenderer::OnDemuxerStreamConnectionError, | 
|  | base::Unretained(this), mojo_stream.get())); | 
|  |  | 
|  | streams_.push_back(std::move(mojo_stream)); | 
|  | stream_proxies.push_back(std::move(stream_proxy)); | 
|  | } | 
|  |  | 
|  | BindRemoteRendererIfNeeded(); | 
|  |  | 
|  | // 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(client_receiver_.BindNewEndpointAndPassRemote(), | 
|  | std::move(stream_proxies), nullptr, | 
|  | base::BindOnce(&MojoRenderer::OnInitialized, | 
|  | base::Unretained(this), client)); | 
|  | } | 
|  |  | 
|  | void MojoRenderer::InitializeRendererFromUrl(media::RendererClient* client) { | 
|  | DVLOG(2) << __func__; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  |  | 
|  | BindRemoteRendererIfNeeded(); | 
|  |  | 
|  | const 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. | 
|  | mojom::MediaUrlParamsPtr media_url_params = mojom::MediaUrlParams::New( | 
|  | url_params.media_url, url_params.site_for_cookies, | 
|  | url_params.top_frame_origin, url_params.allow_credentials, | 
|  | url_params.is_hls); | 
|  | remote_renderer_->Initialize(client_receiver_.BindNewEndpointAndPassRemote(), | 
|  | absl::nullopt, std::move(media_url_params), | 
|  | base::BindOnce(&MojoRenderer::OnInitialized, | 
|  | base::Unretained(this), client)); | 
|  | } | 
|  |  | 
|  | void MojoRenderer::SetCdm(CdmContext* cdm_context, | 
|  | CdmAttachedCB cdm_attached_cb) { | 
|  | DVLOG(1) << __func__; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  | DCHECK(cdm_context); | 
|  | DCHECK(cdm_attached_cb); | 
|  | DCHECK(!cdm_attached_cb_); | 
|  |  | 
|  | if (encountered_error_) { | 
|  | task_runner_->PostTask(FROM_HERE, | 
|  | base::BindOnce(std::move(cdm_attached_cb), false)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | absl::optional<base::UnguessableToken> cdm_id = cdm_context->GetCdmId(); | 
|  | if (!cdm_id) { | 
|  | DVLOG(2) << "MojoRenderer only works with remote CDMs but the CDM ID " | 
|  | "is invalid."; | 
|  | task_runner_->PostTask(FROM_HERE, | 
|  | base::BindOnce(std::move(cdm_attached_cb), false)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | BindRemoteRendererIfNeeded(); | 
|  |  | 
|  | cdm_attached_cb_ = std::move(cdm_attached_cb); | 
|  | remote_renderer_->SetCdm(cdm_id, base::BindOnce(&MojoRenderer::OnCdmAttached, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void MojoRenderer::SetLatencyHint( | 
|  | absl::optional<base::TimeDelta> latency_hint) { | 
|  | // TODO(chcunningham): Proxy to remote renderer if needed. | 
|  | } | 
|  |  | 
|  | void MojoRenderer::Flush(base::OnceClosure flush_cb) { | 
|  | DVLOG(2) << __func__; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  | DCHECK(remote_renderer_.is_bound()); | 
|  | DCHECK(flush_cb); | 
|  | DCHECK(!flush_cb_); | 
|  |  | 
|  | if (encountered_error_) { | 
|  | task_runner_->PostTask(FROM_HERE, std::move(flush_cb)); | 
|  | return; | 
|  | } | 
|  |  | 
|  | { | 
|  | base::AutoLock auto_lock(lock_); | 
|  | if (media_time_interpolator_.interpolating()) | 
|  | media_time_interpolator_.StopInterpolating(); | 
|  | } | 
|  |  | 
|  | flush_cb_ = std::move(flush_cb); | 
|  | remote_renderer_->Flush( | 
|  | base::BindOnce(&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, base::TimeTicks::Now()); | 
|  | 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()); | 
|  |  | 
|  | volume_ = volume; | 
|  | if (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, | 
|  | BufferingStateChangeReason reason) { | 
|  | DVLOG(2) << __func__; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  | client_->OnBufferingStateChange(state, reason); | 
|  | } | 
|  |  | 
|  | void MojoRenderer::OnEnded() { | 
|  | DVLOG(1) << __func__; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  | client_->OnEnded(); | 
|  | } | 
|  |  | 
|  | void MojoRenderer::OnError(const Status& status) { | 
|  | DVLOG(1) << __func__; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  | DCHECK(!init_cb_); | 
|  |  | 
|  | encountered_error_ = true; | 
|  | absl::optional<PipelineStatus> pipeline_status = | 
|  | StatusCodeToPipelineStatus(status.code()); | 
|  |  | 
|  | // If an unexpected status code is encountered default | 
|  | // back to a decode error. | 
|  | if (!pipeline_status) { | 
|  | // TODO(crbug.com/1153465): Log status code that failed to convert. | 
|  | pipeline_status = PipelineStatus::PIPELINE_ERROR_DECODE; | 
|  | } | 
|  |  | 
|  | client_->OnError(*pipeline_status); | 
|  | } | 
|  |  | 
|  | 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::OnVideoOpacityChange(bool opaque) { | 
|  | DVLOG(2) << __func__ << ": " << opaque; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  | client_->OnVideoOpacityChange(opaque); | 
|  | } | 
|  |  | 
|  | void MojoRenderer::OnAudioConfigChange(const AudioDecoderConfig& config) { | 
|  | DVLOG(2) << __func__ << ": " << config.AsHumanReadableString(); | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  | client_->OnAudioConfigChange(config); | 
|  | } | 
|  |  | 
|  | void MojoRenderer::OnVideoConfigChange(const VideoDecoderConfig& config) { | 
|  | DVLOG(2) << __func__ << ": " << config.AsHumanReadableString(); | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  | client_->OnVideoConfigChange(config); | 
|  | } | 
|  |  | 
|  | void MojoRenderer::OnStatisticsUpdate(const PipelineStatistics& stats) { | 
|  | DVLOG(3) << __func__; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  | if (!client_) { | 
|  | pending_stats_ = stats; | 
|  | return; | 
|  | } | 
|  | client_->OnStatisticsUpdate(stats); | 
|  | } | 
|  |  | 
|  | void MojoRenderer::OnWaiting(WaitingReason reason) { | 
|  | DVLOG(1) << __func__; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  | client_->OnWaiting(reason); | 
|  | } | 
|  |  | 
|  | 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 |remote_renderer_pending_remote_|. | 
|  | remote_renderer_.Bind(std::move(remote_renderer_pending_remote_)); | 
|  |  | 
|  | // 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_disconnect_handler( | 
|  | base::BindOnce(&MojoRenderer::OnConnectionError, base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void MojoRenderer::OnInitialized(media::RendererClient* client, bool success) { | 
|  | DVLOG(1) << __func__; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  | DCHECK(init_cb_); | 
|  |  | 
|  | // Only set |client_| after initialization succeeded. No client methods should | 
|  | // be called before this. | 
|  | if (success) { | 
|  | client_ = client; | 
|  |  | 
|  | // It'd be nice to provide this before Initialize(), but that causes some | 
|  | // MojoRenderer implementations to crash. | 
|  | SetVolume(volume_); | 
|  | } | 
|  |  | 
|  | std::move(init_cb_).Run(success ? PIPELINE_OK | 
|  | : PIPELINE_ERROR_INITIALIZATION_FAILED); | 
|  |  | 
|  | if (client_ && pending_stats_.has_value()) | 
|  | client_->OnStatisticsUpdate(pending_stats_.value()); | 
|  | pending_stats_.reset(); | 
|  | } | 
|  |  | 
|  | void MojoRenderer::OnFlushed() { | 
|  | DVLOG(1) << __func__; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  | DCHECK(flush_cb_); | 
|  |  | 
|  | std::move(flush_cb_).Run(); | 
|  | } | 
|  |  | 
|  | void MojoRenderer::OnCdmAttached(bool success) { | 
|  | DVLOG(1) << __func__; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  | DCHECK(cdm_attached_cb_); | 
|  |  | 
|  | std::move(cdm_attached_cb_).Run(success); | 
|  | } | 
|  |  | 
|  | void MojoRenderer::CancelPendingCallbacks() { | 
|  | DVLOG(1) << __func__; | 
|  | DCHECK(task_runner_->BelongsToCurrentThread()); | 
|  |  | 
|  | if (init_cb_) | 
|  | std::move(init_cb_).Run(PIPELINE_ERROR_INITIALIZATION_FAILED); | 
|  |  | 
|  | if (flush_cb_) | 
|  | std::move(flush_cb_).Run(); | 
|  |  | 
|  | if (cdm_attached_cb_) | 
|  | std::move(cdm_attached_cb_).Run(false); | 
|  | } | 
|  |  | 
|  | }  // namespace media |