| // Copyright 2025 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chromecast/starboard/media/renderer/starboard_renderer.h" |
| |
| #include <utility> |
| |
| #include "base/check_op.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/logging.h" |
| #include "base/task/bind_post_task.h" |
| #include "chromecast/base/metrics/cast_metrics_helper.h" |
| #include "media/base/demuxer_stream.h" |
| #include "media/base/media_resource.h" |
| #include "media/base/pipeline_status.h" |
| #include "media/base/status.h" |
| |
| namespace chromecast { |
| namespace media { |
| |
| StarboardRenderer::StarboardRenderer( |
| std::unique_ptr<chromecast::media::StarboardApiWrapper> starboard, |
| scoped_refptr<base::SequencedTaskRunner> media_task_runner, |
| const base::UnguessableToken& overlay_plane_id, |
| bool enable_buffering, |
| VideoGeometrySetterService* geometry_setter_service, |
| chromecast::metrics::CastMetricsHelper* cast_metrics_helper) |
| : starboard_(std::move(starboard)), |
| media_task_runner_(std::move(media_task_runner)), |
| geometry_change_handler_(geometry_setter_service, |
| starboard_.get(), |
| overlay_plane_id), |
| cast_metrics_helper_(cast_metrics_helper), |
| enable_buffering_(enable_buffering) { |
| CHECK(starboard_); |
| CHECK(media_task_runner_); |
| CHECK(cast_metrics_helper_); |
| LOG(INFO) << "Constructed StarboardRenderer. Buffering is " |
| << (enable_buffering_ ? "enabled" : "disabled"); |
| } |
| |
| StarboardRenderer::~StarboardRenderer() { |
| CHECK(media_task_runner_->RunsTasksInCurrentSequence()); |
| // player_manager_ being non-null implies that StarboardRenderer::Initialize |
| // was called. |
| if (player_manager_ && !end_reported_) { |
| cast_metrics_helper_->RecordApplicationEvent("Cast.Platform.Ended"); |
| } |
| } |
| |
| void StarboardRenderer::Initialize(::media::MediaResource* media_resource, |
| ::media::RendererClient* client, |
| ::media::PipelineStatusCallback init_cb) { |
| CHECK(media_task_runner_->RunsTasksInCurrentSequence()); |
| CHECK(client); |
| |
| ::media::DemuxerStream* audio_stream = |
| media_resource->GetFirstStream(::media::DemuxerStream::Type::AUDIO); |
| ::media::DemuxerStream* video_stream = |
| media_resource->GetFirstStream(::media::DemuxerStream::Type::VIDEO); |
| |
| player_manager_ = StarboardPlayerManager::Create( |
| starboard_.get(), audio_stream, video_stream, client, |
| cast_metrics_helper_, media_task_runner_, enable_buffering_); |
| |
| if (!player_manager_) { |
| LOG(ERROR) << "Unable to create StarboardPlayerManager"; |
| media_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(std::move(init_cb), |
| ::media::PIPELINE_ERROR_INITIALIZATION_FAILED)); |
| return; |
| } |
| |
| geometry_change_handler_.SetSbPlayer(player_manager_->GetSbPlayer()); |
| |
| if (pending_volume_.has_value()) { |
| player_manager_->SetVolume(*pending_volume_); |
| pending_volume_ = std::nullopt; |
| } |
| |
| // Initialization succeeded. Inform the client and update the opacity (if |
| // necessary) only after reporting success. |
| media_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(std::move(init_cb), ::media::PIPELINE_OK)); |
| if (video_stream) { |
| media_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce( |
| [](base::WeakPtr<StarboardRenderer> renderer, |
| ::media::RendererClient* client) { |
| if (renderer) { |
| client->OnVideoOpacityChange(true); |
| } |
| }, |
| weak_factory_.GetWeakPtr(), client)); |
| } |
| } |
| |
| void StarboardRenderer::SetCdm(::media::CdmContext* cdm_context, |
| CdmAttachedCB cdm_attached_cb) { |
| // StarboardDecryptorCast does not do actual decryption, so we do not need to |
| // access it. |
| std::move(cdm_attached_cb).Run(true); |
| } |
| |
| void StarboardRenderer::SetLatencyHint( |
| std::optional<base::TimeDelta> /*latency_hint*/) {} |
| |
| void StarboardRenderer::Flush(base::OnceClosure flush_cb) { |
| CHECK(media_task_runner_->RunsTasksInCurrentSequence()); |
| player_manager_->Flush(); |
| std::move(flush_cb).Run(); |
| cast_metrics_helper_->RecordApplicationEvent("Cast.Platform.Ended"); |
| end_reported_ = true; |
| } |
| |
| void StarboardRenderer::StartPlayingFrom(base::TimeDelta time) { |
| CHECK(media_task_runner_->RunsTasksInCurrentSequence()); |
| LOG(INFO) << "StartPlayingFrom t=" << time; |
| player_manager_->StartPlayingFrom(time); |
| cast_metrics_helper_->RecordApplicationEvent("Cast.Platform.Playing"); |
| |
| end_reported_ = false; |
| } |
| |
| void StarboardRenderer::SetPlaybackRate(double playback_rate) { |
| CHECK(media_task_runner_->RunsTasksInCurrentSequence()); |
| player_manager_->SetPlaybackRate(playback_rate); |
| |
| if (playback_rate == 0.0f) { |
| cast_metrics_helper_->RecordApplicationEvent("Cast.Platform.Pause"); |
| } else { |
| cast_metrics_helper_->RecordApplicationEvent("Cast.Platform.Playing"); |
| } |
| |
| end_reported_ = false; |
| } |
| |
| void StarboardRenderer::SetVolume(float volume) { |
| CHECK(media_task_runner_->RunsTasksInCurrentSequence()); |
| |
| if (player_manager_ == nullptr) { |
| pending_volume_ = volume; |
| } else { |
| player_manager_->SetVolume(volume); |
| } |
| } |
| |
| base::TimeDelta StarboardRenderer::GetMediaTime() { |
| // The documentation for ::media::Renderer::GetMediaTime mentions that this |
| // function in particular can be called from any thread. However, since cast |
| // uses a MojoRenderer, this should always be called from the media thread. |
| // |
| // Note that CastRenderer::GetMediaTime makes this same assumption here: |
| // https://source.chromium.org/chromium/chromium/src/+/main:chromecast/media/service/cast_renderer.cc;l=353;drc=27c605b83ca683345a58ec734e98223ae4e7adf7 |
| CHECK(media_task_runner_->RunsTasksInCurrentSequence()); |
| if (!player_manager_) { |
| LOG(ERROR) << "StarboardRenderer was not successfully initialized before " |
| "GetMediaTime was called. Returning 0."; |
| return base::Microseconds(0); |
| } |
| return player_manager_->GetMediaTime(); |
| } |
| |
| ::media::RendererType StarboardRenderer::GetRendererType() { |
| // TODO(crbug.com/422850895): Add a new RendererType before hooking this up to |
| // production code. |
| return ::media::RendererType::kCast; |
| } |
| |
| } // namespace media |
| } // namespace chromecast |