From c280e82cd1852334b1e6a070b6d6716ee294ca05 Mon Sep 17 00:00:00 2001 From: Abhijeet Kandalkar Date: Fri, 29 May 2026 01:18:57 +0530 Subject: [PATCH 1/2] media: Implement URL player for native HLS on tvOS Add URL player support to StarboardRenderer for native HLS playback. UrlPlayerDemuxer provides placeholder streams for pipeline init while the platform player handles actual playback via SetSourceUrl. Bug: 512045535 --- .../cobalt_content_renderer_client.cc | 20 +++ .../renderer/cobalt_content_renderer_client.h | 6 + .../browser/shell_platform_delegate_ios.mm | 3 +- media/base/demuxer.h | 1 + media/base/media_resource.cc | 7 + media/base/media_resource.h | 10 ++ .../starboard/starboard_renderer_client.cc | 25 ++- .../starboard_renderer_client_unittest.cc | 1 + media/mojo/mojom/media_types.mojom | 1 + .../mojom/media_types_enum_mojom_traits.h | 9 +- media/mojo/mojom/renderer_extensions.mojom | 3 + .../starboard/starboard_renderer_wrapper.cc | 7 + .../starboard/starboard_renderer_wrapper.h | 1 + media/starboard/BUILD.gn | 6 + media/starboard/starboard_renderer.cc | 157 ++++++++++++++---- media/starboard/starboard_renderer.h | 15 ++ media/starboard/url_player_demuxer.cc | 150 +++++++++++++++++ media/starboard/url_player_demuxer.h | 99 +++++++++++ .../platform/media/web_media_player_impl.cc | 2 +- tools/metrics/histograms/enums.xml | 1 + 20 files changed, 482 insertions(+), 42 deletions(-) create mode 100644 media/starboard/url_player_demuxer.cc create mode 100644 media/starboard/url_player_demuxer.h diff --git a/cobalt/renderer/cobalt_content_renderer_client.cc b/cobalt/renderer/cobalt_content_renderer_client.cc index 77d442497d2b..db8335d68d96 100644 --- a/cobalt/renderer/cobalt_content_renderer_client.cc +++ b/cobalt/renderer/cobalt_content_renderer_client.cc @@ -20,6 +20,7 @@ #include "base/task/bind_post_task.h" #include "base/time/time.h" +#include "build/build_config.h" #include "cobalt/media/service/mojom/platform_window_provider.mojom.h" #include "cobalt/renderer/cobalt_render_frame_observer.h" #include "cobalt/shell/common/url_constants.h" @@ -33,6 +34,7 @@ #include "media/base/media_switches.h" #include "media/base/renderer_factory.h" #include "media/base/starboard/experimental_features.h" +#include "media/media_buildflags.h" #include "media/mojo/clients/starboard/starboard_renderer_client_factory.h" #include "media/starboard/starboard_media_external_memory_allocator.h" #include "mojo/public/cpp/bindings/generic_pending_receiver.h" @@ -46,6 +48,10 @@ #include "third_party/blink/public/web/web_view.h" #include "ui/gfx/geometry/size_conversions.h" +#if BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_STARBOARD_MEDIA) +#include "media/starboard/url_player_demuxer.h" +#endif // BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_STARBOARD_MEDIA) + namespace cobalt { namespace { @@ -368,4 +374,18 @@ void CobaltContentRendererClient::PostSandboxInitialized() { } } +std::unique_ptr<::media::Demuxer> +CobaltContentRendererClient::OverrideDemuxerForUrl( + content::RenderFrame* render_frame, + const GURL& url, + scoped_refptr task_runner) { +#if BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_STARBOARD_MEDIA) + if (::media::IsHlsUrl(url)) { + return std::make_unique<::media::UrlPlayerDemuxer>(std::move(task_runner), + url); + } +#endif // BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_STARBOARD_MEDIA) + return nullptr; +} + } // namespace cobalt diff --git a/cobalt/renderer/cobalt_content_renderer_client.h b/cobalt/renderer/cobalt_content_renderer_client.h index 020d63ac6f35..11502047e1dc 100644 --- a/cobalt/renderer/cobalt_content_renderer_client.h +++ b/cobalt/renderer/cobalt_content_renderer_client.h @@ -16,6 +16,7 @@ #define COBALT_RENDERER_COBALT_CONTENT_RENDERER_CLIENT_H_ #include +#include #include "base/functional/callback.h" #include "base/memory/weak_ptr.h" @@ -27,6 +28,7 @@ #include "cobalt/media/audio/cobalt_audio_device_factory.h" #include "cobalt/media/service/mojom/platform_window_provider.mojom.h" #include "content/public/renderer/content_renderer_client.h" +#include "media/base/demuxer.h" #include "media/base/key_systems_support_registration.h" #include "media/base/starboard/renderer_factory_traits.h" #include "mojo/public/cpp/bindings/remote.h" @@ -74,6 +76,10 @@ class CobaltContentRendererClient : public content::ContentRendererClient { void GetStarboardRendererFactoryTraits( ::media::RendererFactoryTraits* traits) override; void PostSandboxInitialized() override; + std::unique_ptr<::media::Demuxer> OverrideDemuxerForUrl( + content::RenderFrame* render_frame, + const GURL& url, + scoped_refptr task_runner) override; uint64_t GetSbWindowHandle() const { return sb_window_handle_; } diff --git a/cobalt/shell/browser/shell_platform_delegate_ios.mm b/cobalt/shell/browser/shell_platform_delegate_ios.mm index 46fe1b562875..0fb6dcbd7520 100644 --- a/cobalt/shell/browser/shell_platform_delegate_ios.mm +++ b/cobalt/shell/browser/shell_platform_delegate_ios.mm @@ -365,7 +365,8 @@ - (void)viewDidLoad { // Note that the actual size and and position of this view are irrelevant at // this point: it will be changed in starboard's // AVSBVideoRenderer::SetBounds() when necessary. - UIView* playerContainerView = [[UIView alloc] init]; + UIView* playerContainerView = + [[UIView alloc] initWithFrame:[UIScreen mainScreen].bounds]; playerContainerView.accessibilityIdentifier = @"Player Container"; [_contentView addSubview:playerContainerView]; [SBDGetApplication() setPlayerContainerView:playerContainerView]; diff --git a/media/base/demuxer.h b/media/base/demuxer.h index 6d4512496f23..c21cdec91918 100644 --- a/media/base/demuxer.h +++ b/media/base/demuxer.h @@ -38,6 +38,7 @@ enum class DemuxerType { kFrameInjectingDemuxer = 5, kStreamProviderDemuxer = 6, kManifestDemuxer = 7, + kUrlPlayerDemuxer = 8, // URL player placeholder demuxer. }; class MEDIA_EXPORT DemuxerHost { diff --git a/media/base/media_resource.cc b/media/base/media_resource.cc index deeadbbf89b2..55c2dc34d570 100644 --- a/media/base/media_resource.cc +++ b/media/base/media_resource.cc @@ -5,6 +5,7 @@ #include "media/base/media_resource.h" #include "base/no_destructor.h" +#include "media/media_buildflags.h" #include "net/cookies/site_for_cookies.h" #include "net/storage_access_api/status.h" #include "url/gurl.h" @@ -25,4 +26,10 @@ DemuxerStream* MediaResource::GetFirstStream(DemuxerStream::Type type) { return nullptr; } +#if BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_STARBOARD_MEDIA) +GURL MediaResource::GetMediaUrl() const { + return GURL(); +} +#endif // BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_STARBOARD_MEDIA) + } // namespace media diff --git a/media/base/media_resource.h b/media/base/media_resource.h index 62e0b605df2f..dc963828f878 100644 --- a/media/base/media_resource.h +++ b/media/base/media_resource.h @@ -11,6 +11,11 @@ #include "base/time/time.h" #include "media/base/demuxer_stream.h" #include "media/base/media_export.h" +#include "build/build_config.h" +#include "media/media_buildflags.h" +#if BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_STARBOARD_MEDIA) +#include "url/gurl.h" +#endif // BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_STARBOARD_MEDIA) namespace media { @@ -41,6 +46,11 @@ class MEDIA_EXPORT MediaResource { // A helper function that return the first stream of the given `type` if one // exists or a null pointer if there is no streams of that type. DemuxerStream* GetFirstStream(DemuxerStream::Type type); + +#if BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_STARBOARD_MEDIA) + // Returns the media URL for URL player. + virtual GURL GetMediaUrl() const; +#endif // BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_STARBOARD_MEDIA) }; } // namespace media diff --git a/media/mojo/clients/starboard/starboard_renderer_client.cc b/media/mojo/clients/starboard/starboard_renderer_client.cc index 6a85a2f1c728..064b236859f2 100644 --- a/media/mojo/clients/starboard/starboard_renderer_client.cc +++ b/media/mojo/clients/starboard/starboard_renderer_client.cc @@ -20,6 +20,7 @@ #include "base/functional/bind.h" #include "base/time/time.h" #include "base/unguessable_token.h" +#include "build/build_config.h" #include "media/base/media_log.h" #include "media/base/media_resource.h" #include "media/base/media_switches.h" @@ -30,6 +31,10 @@ #include "mojo/public/cpp/bindings/callback_helpers.h" #include "mojo/public/cpp/bindings/pending_receiver.h" +#if BUILDFLAG(IS_IOS_TVOS) +#include "url/gurl.h" +#endif // BUILDFLAG(IS_IOS_TVOS) + #if BUILDFLAG(IS_ANDROID) #include "base/task/bind_post_task.h" #endif // BUILDFLAG(IS_ANDROID) @@ -351,8 +356,18 @@ void StarboardRendererClient::InitializeMojoRenderer( DCHECK(media_task_runner_->RunsTasksInCurrentSequence()); DCHECK(AreMojoPipesConnected()); - if (base::FeatureList::IsEnabled(kCobaltBypassMojoForMedia) || - bypass_mojo_for_media_) { + // URL player is incompatible with the bypass bridge because the bypass + // bridge proxies demuxer streams, while the URL player delegates playback + // entirely to the native platform player. + bool is_url_player = false; +#if BUILDFLAG(IS_IOS_TVOS) + GURL url = media_resource->GetMediaUrl(); + is_url_player = url.is_valid(); +#endif // BUILDFLAG(IS_IOS_TVOS) + + if (!is_url_player && + (base::FeatureList::IsEnabled(kCobaltBypassMojoForMedia) || + bypass_mojo_for_media_)) { bypass_bridge_ = base::MakeRefCounted( media_task_runner_, base::BindRepeating(&StarboardRendererClient::OnTimeUpdateFromBridge, @@ -378,6 +393,12 @@ void StarboardRendererClient::InitializeMojoRenderer( return; } +#if BUILDFLAG(IS_IOS_TVOS) + if (is_url_player) { + renderer_extension_->SetSourceUrl(url.spec()); + } +#endif // BUILDFLAG(IS_IOS_TVOS) + MojoRendererWrapper::Initialize(media_resource, client, std::move(init_cb)); } diff --git a/media/mojo/clients/starboard/starboard_renderer_client_unittest.cc b/media/mojo/clients/starboard/starboard_renderer_client_unittest.cc index 942d4732bad2..03c9d0a6cbf5 100644 --- a/media/mojo/clients/starboard/starboard_renderer_client_unittest.cc +++ b/media/mojo/clients/starboard/starboard_renderer_client_unittest.cc @@ -96,6 +96,7 @@ class FakeStarboardRendererExtension #endif // BUILDFLAG(IS_ANDROID) void OnGpuChannelTokenReady( mojom::CommandBufferIdPtr command_buffer_id) override {} + void SetSourceUrl(const std::string& source_url) override {} private: FakeMojomRendererCallRecord* record_ = nullptr; diff --git a/media/mojo/mojom/media_types.mojom b/media/mojo/mojom/media_types.mojom index d649d9e890a2..26d9cfe8f034 100644 --- a/media/mojo/mojom/media_types.mojom +++ b/media/mojo/mojom/media_types.mojom @@ -662,6 +662,7 @@ enum DemuxerType { kFrameInjectingDemuxer = 5, kStreamProviderDemuxer = 6, kManifestDemuxer = 7, + kUrlPlayerDemuxer = 8, // URL player placeholder demuxer. }; // See media::CreateCdmStatus diff --git a/media/mojo/mojom/media_types_enum_mojom_traits.h b/media/mojo/mojom/media_types_enum_mojom_traits.h index 20563e88d10e..b66a10917676 100644 --- a/media/mojo/mojom/media_types_enum_mojom_traits.h +++ b/media/mojo/mojom/media_types_enum_mojom_traits.h @@ -391,7 +391,7 @@ struct EnumTraits { #if BUILDFLAG(USE_STARBOARD_MEDIA) case ::media::RendererType::kStarboard: return media::mojom::RendererType::kStarboard; -#endif // BUILDFLAG(USE_STARBOARD_MEDIA) +#endif // BUILDFLAG(USE_STARBOARD_MEDIA) } NOTREACHED(); @@ -436,7 +436,7 @@ struct EnumTraits { case media::mojom::RendererType::kStarboard: *output = ::media::RendererType::kStarboard; return true; -#endif // BUILDFLAG(USE_STARBOARD_MEDIA) +#endif // BUILDFLAG(USE_STARBOARD_MEDIA) } NOTREACHED(); @@ -461,6 +461,8 @@ struct EnumTraits { return media::mojom::DemuxerType::kStreamProviderDemuxer; case ::media::DemuxerType::kManifestDemuxer: return media::mojom::DemuxerType::kManifestDemuxer; + case ::media::DemuxerType::kUrlPlayerDemuxer: + return media::mojom::DemuxerType::kUrlPlayerDemuxer; } NOTREACHED(); @@ -492,6 +494,9 @@ struct EnumTraits { case media::mojom::DemuxerType::kManifestDemuxer: *output = ::media::DemuxerType::kManifestDemuxer; return true; + case media::mojom::DemuxerType::kUrlPlayerDemuxer: + *output = ::media::DemuxerType::kUrlPlayerDemuxer; + return true; } NOTREACHED(); diff --git a/media/mojo/mojom/renderer_extensions.mojom b/media/mojo/mojom/renderer_extensions.mojom index d61f091311e8..8ee721606d50 100644 --- a/media/mojo/mojom/renderer_extensions.mojom +++ b/media/mojo/mojom/renderer_extensions.mojom @@ -151,6 +151,9 @@ interface StarboardRendererExtension { // Initialize media bypass bridge in single-process mode. InitializeWithBypassBridge(uint32 bypass_bridge_id) => (bool success); + // Pass source URL for URL player. + SetSourceUrl(string source_url); + [EnableIf=is_android] // Notify StarboardRendererWrapper when the current OverlayInfo changes. OnOverlayInfoChanged(OverlayInfo overlay_info); diff --git a/media/mojo/services/starboard/starboard_renderer_wrapper.cc b/media/mojo/services/starboard/starboard_renderer_wrapper.cc index 2ac7ab0da1ea..f9e44e8d414e 100644 --- a/media/mojo/services/starboard/starboard_renderer_wrapper.cc +++ b/media/mojo/services/starboard/starboard_renderer_wrapper.cc @@ -515,6 +515,13 @@ void StarboardRendererWrapper::InitializeWithBypassBridge( std::move(callback).Run(true); } +void StarboardRendererWrapper::SetSourceUrl(const std::string& source_url) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); +#if SB_HAS(PLAYER_WITH_URL) + GetRenderer()->SetSourceUrl(source_url); +#endif // SB_HAS(PLAYER_WITH_URL) +} + #if BUILDFLAG(IS_ANDROID) void StarboardRendererWrapper::OnOverlayInfoChanged( const OverlayInfo& overlay_info) { diff --git a/media/mojo/services/starboard/starboard_renderer_wrapper.h b/media/mojo/services/starboard/starboard_renderer_wrapper.h index efd09cfee6bd..ff9bd79511f9 100644 --- a/media/mojo/services/starboard/starboard_renderer_wrapper.h +++ b/media/mojo/services/starboard/starboard_renderer_wrapper.h @@ -104,6 +104,7 @@ class StarboardRendererWrapper void InitializeWithBypassBridge( uint32_t bypass_bridge_id, InitializeWithBypassBridgeCallback callback) override; + void SetSourceUrl(const std::string& source_url) override; #if BUILDFLAG(IS_ANDROID) void OnOverlayInfoChanged(const OverlayInfo& overlay_info) override; #endif // BUILDFLAG(IS_ANDROID) diff --git a/media/starboard/BUILD.gn b/media/starboard/BUILD.gn index 7bb524b3251b..d56ab6a69434 100644 --- a/media/starboard/BUILD.gn +++ b/media/starboard/BUILD.gn @@ -60,6 +60,12 @@ source_set("starboard") { ] deps += [ ":buildflags" ] + if (is_ios && target_platform == "tvos") { + sources += [ + "url_player_demuxer.cc", + "url_player_demuxer.h", + ] + } } deps += [ diff --git a/media/starboard/starboard_renderer.cc b/media/starboard/starboard_renderer.cc index e9d26c540503..e4825b0cb3ee 100644 --- a/media/starboard/starboard_renderer.cc +++ b/media/starboard/starboard_renderer.cc @@ -213,6 +213,16 @@ void StarboardRenderer::Initialize(MediaResource* media_resource, client_ = client; init_cb_ = std::move(init_cb); + if (IsUrlPlayer()) { + state_ = STATE_INITIALIZING; + if (get_sb_window_handle_cb_) { + get_sb_window_handle_cb_.Run(); + return; + } + CreatePlayerBridge(); + return; + } + audio_stream_ = media_resource->GetFirstStream(DemuxerStream::AUDIO); video_stream_ = media_resource->GetFirstStream(DemuxerStream::VIDEO); @@ -530,6 +540,52 @@ void StarboardRenderer::OnSbWindowHandleReady(const uint64_t sb_window_handle) { CreatePlayerBridge(); } +bool StarboardRenderer::IsUrlPlayer() const { +#if SB_HAS(PLAYER_WITH_URL) + return !source_url_.empty(); +#else + return false; +#endif +} + +void StarboardRenderer::OnUrlPlayerPresenting() { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); +#if SB_HAS(PLAYER_WITH_URL) + if (!player_bridge_) { + return; + } + int width = 0, height = 0; + player_bridge_->GetVideoResolution(&width, &height); + if (width > 0 && height > 0) { + gfx::Size size(width, height); + client_->OnVideoNaturalSizeChange(size); + paint_video_hole_frame_cb_.Run(size); + } else { + LOG(WARNING) << "Platform player reported invalid dimensions (" << width + << "x" << height + << ") at presenting; skipping video hole update."; + } + + // Re-apply playback rate; the platform player ignores rate changes + // before it is ready to play. + player_bridge_->SetPlaybackRate(playback_rate_); +#endif // SB_HAS(PLAYER_WITH_URL) +} + +#if SB_HAS(PLAYER_WITH_URL) +void StarboardRenderer::SetSourceUrl(const std::string& source_url) { + DCHECK(task_runner_->RunsTasksInCurrentSequence()); + source_url_ = source_url; +} + +void StarboardRenderer::OnEncryptedMediaInitDataEncountered( + const char* init_data_type, + const unsigned char* init_data, + unsigned int init_data_length) { + // TODO: Forward encrypted media init data to the EME/DRM layer. +} +#endif // SB_HAS(PLAYER_WITH_URL) + #if BUILDFLAG(IS_ANDROID) void StarboardRenderer::OnOverlayInfoChanged(const OverlayInfo& overlay_info) { bool overlay_changed = !overlay_info_.RefersToSameOverlayAs(overlay_info); @@ -582,7 +638,7 @@ void StarboardRenderer::CreatePlayerBridge() { DCHECK(task_runner_->RunsTasksInCurrentSequence()); DCHECK(init_cb_); DCHECK_EQ(state_, STATE_INITIALIZING); - DCHECK(audio_stream_ || video_stream_); + DCHECK(audio_stream_ || video_stream_ || IsUrlPlayer()); TRACE_EVENT0("media", "StarboardRenderer::CreatePlayerBridge"); @@ -622,39 +678,59 @@ void StarboardRenderer::CreatePlayerBridge() { // number of active players. player_bridge_.reset(); - LOG(INFO) << "Creating SbPlayerBridge."; - - player_bridge_.reset(new SbPlayerBridge( - GetSbPlayerInterface(), task_runner_, - get_decode_target_graphics_context_provider_func_, audio_config, - audio_mime_type, video_config, video_mime_type, - // TODO(b/326497953): Support suspend/resume. - // TODO(b/326508279): Support background mode. - sb_window_, drm_system_, this, - // TODO(b/326497953): Support suspend/resume. - false, - // TODO(b/326825450): Revisit 360 videos. - kSbPlayerOutputModeInvalid, max_video_capabilities_, - // TODO(b/326654546): Revisit HTMLVideoElement.setMaxVideoInputSize. - /*max_video_input_size=*/-1, experimental_features_ + if (IsUrlPlayer()) { +#if SB_HAS(PLAYER_WITH_URL) + player_bridge_.reset(new SbPlayerBridge( + GetSbPlayerInterface(), task_runner_, source_url_, sb_window_, this, + /*allow_resume_after_suspend=*/false, kSbPlayerOutputModePunchOut, + base::BindRepeating( + &StarboardRenderer::OnEncryptedMediaInitDataEncountered, + base::Unretained(this)) +#if BUILDFLAG(COBALT_MEDIA_ENABLE_CVAL) + , + /*pipeline_identifier=*/"" +#endif // BUILDFLAG(COBALT_MEDIA_ENABLE_CVAL) + )); +#endif // SB_HAS(PLAYER_WITH_URL) + } else { + LOG(INFO) << "Creating SbPlayerBridge."; + + player_bridge_.reset(new SbPlayerBridge( + GetSbPlayerInterface(), task_runner_, + get_decode_target_graphics_context_provider_func_, audio_config, + audio_mime_type, video_config, video_mime_type, + // TODO(b/326497953): Support suspend/resume. + // TODO(b/326508279): Support background mode. + sb_window_, drm_system_, this, + // TODO(b/326497953): Support suspend/resume. + false, + // TODO(b/326825450): Revisit 360 videos. + kSbPlayerOutputModeInvalid, max_video_capabilities_, + // TODO(b/326654546): Revisit HTMLVideoElement.setMaxVideoInputSize. + /*max_video_input_size=*/-1, experimental_features_ #if BUILDFLAG(IS_ANDROID) - , - // TODO: b/475294958 - Revisit platform-specific codes above starboard. - surface_view_ + , + // TODO: b/475294958 - Revisit platform-specific codes above starboard. + surface_view_ #endif // BUILDFLAG(IS_ANDROID) - )); + )); + } if (player_bridge_->IsValid()) { - // TODO(b/267678497): When `player_bridge_->GetAudioConfigurations()` - // returns no audio configurations, update the write durations again - // before the SbPlayer reaches `kSbPlayerStatePresenting`. - audio_write_duration_for_preroll_ = audio_write_duration_ = - HasRemoteAudioOutputs(player_bridge_->GetAudioConfigurations()) - ? audio_write_duration_remote_ - : audio_write_duration_local_; - LOG(INFO) << "SbPlayerBridge created, with audio write duration at " - << audio_write_duration_for_preroll_ - << " and with max_video_capabilities_ at " - << max_video_capabilities_; + if (IsUrlPlayer()) { + // URL player: skip audio config query, the platform player manages audio. + } else { + // TODO(b/267678497): When `player_bridge_->GetAudioConfigurations()` + // returns no audio configurations, update the write durations again + // before the SbPlayer reaches `kSbPlayerStatePresenting`. + audio_write_duration_for_preroll_ = audio_write_duration_ = + HasRemoteAudioOutputs(player_bridge_->GetAudioConfigurations()) + ? audio_write_duration_remote_ + : audio_write_duration_local_; + LOG(INFO) << "SbPlayerBridge created, with audio write duration at " + << audio_write_duration_for_preroll_ + << " and with max_video_capabilities_ at " + << max_video_capabilities_; + } } else { error_message = player_bridge_->GetPlayerCreationErrorMessage(); player_bridge_.reset(); @@ -860,6 +936,11 @@ void StarboardRenderer::OnNeedData(DemuxerStream::Type type, return; } + // URL player handles all buffering natively so ignore OnNeedData. + if (IsUrlPlayer()) { + return; + } + int max_buffers = std::min(max_number_of_buffers_to_write, max_samples_per_write_); @@ -986,11 +1067,15 @@ void StarboardRenderer::OnPlayerStatus(SbPlayerState state) { FROM_HERE, base::BindOnce(&StarboardRenderer::OnBufferingStateChange, weak_factory_.GetWeakPtr(), buffering_state_)); - audio_write_duration_for_preroll_ = audio_write_duration_ = - HasRemoteAudioOutputs(player_bridge_->GetAudioConfigurations()) - ? audio_write_duration_remote_ - : audio_write_duration_local_; - LOG(INFO) << "Audio write duration is " << audio_write_duration_; + if (IsUrlPlayer()) { + OnUrlPlayerPresenting(); + } else { + audio_write_duration_for_preroll_ = audio_write_duration_ = + HasRemoteAudioOutputs(player_bridge_->GetAudioConfigurations()) + ? audio_write_duration_remote_ + : audio_write_duration_local_; + LOG(INFO) << "Audio write duration is " << audio_write_duration_; + } break; case kSbPlayerStateEndOfStream: client_->OnEnded(); diff --git a/media/starboard/starboard_renderer.h b/media/starboard/starboard_renderer.h index 03ad7cebd201..f46e187c688d 100644 --- a/media/starboard/starboard_renderer.h +++ b/media/starboard/starboard_renderer.h @@ -122,6 +122,12 @@ class MEDIA_EXPORT StarboardRenderer : public Renderer, void OnVideoGeometryChange(const gfx::Rect& output_rect); void OnSbWindowHandleReady(const uint64_t sb_window_handle); +#if SB_HAS(PLAYER_WITH_URL) + void SetSourceUrl(const std::string& source_url); + void OnEncryptedMediaInitDataEncountered(const char* init_data_type, + const unsigned char* init_data, + unsigned int init_data_length); +#endif // SB_HAS(PLAYER_WITH_URL) #if BUILDFLAG(IS_ANDROID) void OnOverlayInfoChanged(const OverlayInfo& overlay_info); #endif // BUILDFLAG(IS_ANDROID) @@ -150,6 +156,12 @@ class MEDIA_EXPORT StarboardRenderer : public Renderer, STATE_ERROR }; + // Returns true when the renderer is operating in URL player mode. + bool IsUrlPlayer() const; + // Handles presenting state for URL player: propagates video resolution + // for hole-punch rendering and re-applies playback rate. + void OnUrlPlayerPresenting(); + void CreatePlayerBridge(); void ApplyPendingBounds(); void UpdateDecoderConfig(DemuxerStream* stream); @@ -200,6 +212,9 @@ class MEDIA_EXPORT StarboardRenderer : public Renderer, const TimeDelta audio_write_duration_local_; const TimeDelta audio_write_duration_remote_; const std::string max_video_capabilities_; +#if SB_HAS(PLAYER_WITH_URL) + std::string source_url_; +#endif // SB_HAS(PLAYER_WITH_URL) const StarboardRendererConfig::ExperimentalFeatures experimental_features_; // TODO: b/375674101 - Connect this to h5vcc setting. const int max_samples_per_write_; diff --git a/media/starboard/url_player_demuxer.cc b/media/starboard/url_player_demuxer.cc new file mode 100644 index 000000000000..bf3259347267 --- /dev/null +++ b/media/starboard/url_player_demuxer.cc @@ -0,0 +1,150 @@ +// Copyright 2026 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "media/starboard/url_player_demuxer.h" + +#include +#include + +#include "base/functional/bind.h" +#include "base/logging.h" +#include "base/task/sequenced_task_runner.h" +#include "media/base/audio_decoder_config.h" +#include "media/base/channel_layout.h" +#include "media/base/decoder_buffer.h" +#include "media/base/media_track.h" +#include "media/base/sample_format.h" +#include "media/base/video_decoder_config.h" + +namespace media { + +bool IsHlsUrl(const GURL& url) { + auto path = url.path_piece(); + return path.ends_with(".m3u8") || + path.find("hls_variant") != std::string_view::npos; +} + +UrlPlayerDemuxerStream::UrlPlayerDemuxerStream(Type type) : type_(type) {} + +UrlPlayerDemuxerStream::~UrlPlayerDemuxerStream() = default; + +void UrlPlayerDemuxerStream::Read(uint32_t count, ReadCB read_cb) { + NOTREACHED(); +} + +AudioDecoderConfig UrlPlayerDemuxerStream::audio_decoder_config() { + return AudioDecoderConfig(AudioCodec::kAAC, kSampleFormatS16, + CHANNEL_LAYOUT_STEREO, + /*samples_per_second=*/44100, /*extra_data=*/{}, + EncryptionScheme::kUnencrypted); +} + +VideoDecoderConfig UrlPlayerDemuxerStream::video_decoder_config() { + static const gfx::Size kPlaceholderSize(1, 1); + return VideoDecoderConfig( + VideoCodec::kH264, VideoCodecProfile::H264PROFILE_BASELINE, + VideoDecoderConfig::AlphaMode::kIsOpaque, VideoColorSpace(), + kNoTransformation, kPlaceholderSize, gfx::Rect(kPlaceholderSize), + kPlaceholderSize, /*extra_data=*/{}, EncryptionScheme::kUnencrypted); +} + +DemuxerStream::Type UrlPlayerDemuxerStream::type() const { + return type_; +} + +bool UrlPlayerDemuxerStream::SupportsConfigChanges() { + return false; +} + +UrlPlayerDemuxer::UrlPlayerDemuxer( + scoped_refptr media_task_runner, + GURL url) + : media_task_runner_(std::move(media_task_runner)), url_(std::move(url)) { + DCHECK(media_task_runner_); +} + +UrlPlayerDemuxer::~UrlPlayerDemuxer() = default; + +std::vector UrlPlayerDemuxer::GetAllStreams() { + return {&audio_stream_, &video_stream_}; +} + +GURL UrlPlayerDemuxer::GetMediaUrl() const { + return url_; +} + +std::string UrlPlayerDemuxer::GetDisplayName() const { + return "UrlPlayerDemuxer"; +} + +DemuxerType UrlPlayerDemuxer::GetDemuxerType() const { + return DemuxerType::kUrlPlayerDemuxer; +} + +void UrlPlayerDemuxer::Initialize(DemuxerHost* host, + PipelineStatusCallback status_cb) { + DVLOG(1) << __func__; + host_ = host; + media_task_runner_->PostTask( + FROM_HERE, base::BindOnce(std::move(status_cb), PIPELINE_OK)); +} + +void UrlPlayerDemuxer::AbortPendingReads() {} +void UrlPlayerDemuxer::StartWaitingForSeek(base::TimeDelta seek_time) {} +void UrlPlayerDemuxer::CancelPendingSeek(base::TimeDelta seek_time) {} + +void UrlPlayerDemuxer::Seek(base::TimeDelta time, + PipelineStatusCallback status_cb) { + DVLOG(1) << __func__ << "(" << time << ")"; + media_task_runner_->PostTask( + FROM_HERE, base::BindOnce(std::move(status_cb), PIPELINE_OK)); +} + +// While the demuxer itself is not seekable, the underlying URL player is. +bool UrlPlayerDemuxer::IsSeekable() const { + return true; +} + +void UrlPlayerDemuxer::Stop() {} + +base::TimeDelta UrlPlayerDemuxer::GetStartTime() const { + return base::TimeDelta(); +} + +base::Time UrlPlayerDemuxer::GetTimelineOffset() const { + return base::Time(); +} + +int64_t UrlPlayerDemuxer::GetMemoryUsage() const { + return 0; +} + +std::optional +UrlPlayerDemuxer::GetContainerForMetrics() const { + return std::nullopt; +} + +void UrlPlayerDemuxer::OnTracksChanged( + DemuxerStream::Type track_type, + const std::vector& track_ids, + base::TimeDelta curr_time, + TrackChangeCB change_completed_cb) { + std::vector streams; + std::move(change_completed_cb).Run(streams); + DLOG(WARNING) << "Track changes are not supported by UrlPlayerDemuxer."; +} + +void UrlPlayerDemuxer::SetPlaybackRate(double rate) {} + +} // namespace media diff --git a/media/starboard/url_player_demuxer.h b/media/starboard/url_player_demuxer.h new file mode 100644 index 000000000000..2cd0e8779e43 --- /dev/null +++ b/media/starboard/url_player_demuxer.h @@ -0,0 +1,99 @@ +// Copyright 2026 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef MEDIA_STARBOARD_URL_PLAYER_DEMUXER_H_ +#define MEDIA_STARBOARD_URL_PLAYER_DEMUXER_H_ + +#include +#include +#include +#include + +#include "base/memory/raw_ptr.h" +#include "base/memory/scoped_refptr.h" +#include "base/task/sequenced_task_runner.h" +#include "media/base/demuxer.h" +#include "media/base/demuxer_stream.h" +#include "media/base/media_export.h" +#include "url/gurl.h" + +namespace media { + +// Returns true if the URL looks like an HLS stream that the URL player +// should handle. +MEDIA_EXPORT bool IsHlsUrl(const GURL& url); + +// Placeholder stream to satisfy the stream-based pipeline initialization. +// Owned by UrlPlayerDemuxer. Created and accessed on the media thread. +class MEDIA_EXPORT UrlPlayerDemuxerStream : public DemuxerStream { + public: + explicit UrlPlayerDemuxerStream(Type type); + ~UrlPlayerDemuxerStream() override; + + // DemuxerStream implementation. + void Read(uint32_t count, ReadCB read_cb) override; + AudioDecoderConfig audio_decoder_config() override; + VideoDecoderConfig video_decoder_config() override; + Type type() const override; + bool SupportsConfigChanges() override; + + private: + const Type type_; +}; + +// Demuxer placeholder for URL player. Carries the media URL and exposes +// placeholder streams required by the stream-based pipeline initialization. +// Owned by PipelineImpl. Created and accessed on the media thread. +class MEDIA_EXPORT UrlPlayerDemuxer : public Demuxer { + public: + UrlPlayerDemuxer(scoped_refptr media_task_runner, + GURL url); + ~UrlPlayerDemuxer() override; + + // MediaResource implementation. + std::vector GetAllStreams() override; + GURL GetMediaUrl() const override; + + // Demuxer implementation. + std::string GetDisplayName() const override; + DemuxerType GetDemuxerType() const override; + void Initialize(DemuxerHost* host, PipelineStatusCallback status_cb) override; + void AbortPendingReads() override; + void StartWaitingForSeek(base::TimeDelta seek_time) override; + void CancelPendingSeek(base::TimeDelta seek_time) override; + void Seek(base::TimeDelta time, PipelineStatusCallback status_cb) override; + bool IsSeekable() const override; + void Stop() override; + base::TimeDelta GetStartTime() const override; + base::Time GetTimelineOffset() const override; + int64_t GetMemoryUsage() const override; + std::optional GetContainerForMetrics() + const override; + void OnTracksChanged(DemuxerStream::Type track_type, + const std::vector& track_ids, + base::TimeDelta curr_time, + TrackChangeCB change_completed_cb) override; + void SetPlaybackRate(double rate) override; + + private: + scoped_refptr media_task_runner_; + raw_ptr host_ = nullptr; + const GURL url_; + UrlPlayerDemuxerStream audio_stream_{DemuxerStream::AUDIO}; + UrlPlayerDemuxerStream video_stream_{DemuxerStream::VIDEO}; +}; + +} // namespace media + +#endif // MEDIA_STARBOARD_URL_PLAYER_DEMUXER_H_ diff --git a/third_party/blink/renderer/platform/media/web_media_player_impl.cc b/third_party/blink/renderer/platform/media/web_media_player_impl.cc index 863cca8298f1..e28ebb3d2e07 100644 --- a/third_party/blink/renderer/platform/media/web_media_player_impl.cc +++ b/third_party/blink/renderer/platform/media/web_media_player_impl.cc @@ -108,7 +108,7 @@ #if BUILDFLAG(USE_STARBOARD_MEDIA) #include "media/starboard/starboard_renderer.h" -#endif // BUILDFLAG(USE_STARBOARD_MEDIA) +#endif // BUILDFLAG(USE_STARBOARD_MEDIA) #if BUILDFLAG(ENABLE_HLS_DEMUXER) #include "media/filters/hls_data_source_provider_impl.h" diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml index 0c8b02df7e97..a382e43ebffd 100644 --- a/tools/metrics/histograms/enums.xml +++ b/tools/metrics/histograms/enums.xml @@ -19911,6 +19911,7 @@ from previous Chrome versions. + From 4d118820b31a78bc756c97f741626ade5e2c5f0d Mon Sep 17 00:00:00 2001 From: Abhijeet Kandalkar Date: Sat, 27 Jun 2026 22:40:44 +0530 Subject: [PATCH 2/2] Plumb duration and buffered time range reporting from the URL player through StarboardRenderer, StarboardRendererWrapper, Mojo IPC, and UrlPlayerDemuxer. This ensures that the JavaScript video.duration and video.buffered APIs correctly reflect the underlying platform player state on tvOS. Duration is reported once when the player reaches the presenting state. Buffered ranges are polled via GetMediaTime on each timer tick to provide continuous updates to the web application. Blocked by #10698 Bug: 512045535 --- media/base/media_resource.cc | 7 ++++++ media/base/media_resource.h | 7 ++++++ .../starboard/starboard_renderer_client.cc | 21 +++++++++++++++++ .../starboard/starboard_renderer_client.h | 5 ++++ media/mojo/mojom/renderer_extensions.mojom | 5 ++++ .../starboard/starboard_renderer_wrapper.cc | 23 +++++++++++++++++++ .../starboard/starboard_renderer_wrapper.h | 5 ++++ media/starboard/starboard_renderer.cc | 21 +++++++++++++++++ media/starboard/starboard_renderer.h | 21 +++++++++++++++++ media/starboard/url_player_demuxer.cc | 18 +++++++++++++++ media/starboard/url_player_demuxer.h | 5 ++++ 11 files changed, 138 insertions(+) diff --git a/media/base/media_resource.cc b/media/base/media_resource.cc index 55c2dc34d570..f68ed21ee8d7 100644 --- a/media/base/media_resource.cc +++ b/media/base/media_resource.cc @@ -30,6 +30,13 @@ DemuxerStream* MediaResource::GetFirstStream(DemuxerStream::Type type) { GURL MediaResource::GetMediaUrl() const { return GURL(); } + +void MediaResource::ForwardDurationChangeToDemuxerHost( + base::TimeDelta duration) {} + +void MediaResource::ForwardBufferedTimeRangesToDemuxerHost( + base::TimeDelta start, + base::TimeDelta length) {} #endif // BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_STARBOARD_MEDIA) } // namespace media diff --git a/media/base/media_resource.h b/media/base/media_resource.h index dc963828f878..41e14d3497c9 100644 --- a/media/base/media_resource.h +++ b/media/base/media_resource.h @@ -50,6 +50,13 @@ class MEDIA_EXPORT MediaResource { #if BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_STARBOARD_MEDIA) // Returns the media URL for URL player. virtual GURL GetMediaUrl() const; + + // Forwards duration change to DemuxerHost. + virtual void ForwardDurationChangeToDemuxerHost(base::TimeDelta duration); + + // Forwards buffered ranges to DemuxerHost. + virtual void ForwardBufferedTimeRangesToDemuxerHost(base::TimeDelta start, + base::TimeDelta length); #endif // BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_STARBOARD_MEDIA) }; diff --git a/media/mojo/clients/starboard/starboard_renderer_client.cc b/media/mojo/clients/starboard/starboard_renderer_client.cc index 064b236859f2..ca7aac2fbe2a 100644 --- a/media/mojo/clients/starboard/starboard_renderer_client.cc +++ b/media/mojo/clients/starboard/starboard_renderer_client.cc @@ -107,6 +107,7 @@ void StarboardRendererClient::Initialize(MediaResource* media_resource, DCHECK(!init_cb_); client_ = client; + media_resource_ = media_resource; init_cb_ = std::move(init_cb); DCHECK(!AreMojoPipesConnected()); @@ -285,6 +286,26 @@ void StarboardRendererClient::GetSbWindowHandle() { renderer_extension_->OnSbWindowHandleReady(sb_window_handle); } +void StarboardRendererClient::OnDurationChange(base::TimeDelta duration) { + DCHECK(media_task_runner_->RunsTasksInCurrentSequence()); +#if BUILDFLAG(IS_IOS_TVOS) + if (media_resource_) { + media_resource_->ForwardDurationChangeToDemuxerHost(duration); + } +#endif // BUILDFLAG(IS_IOS_TVOS) +} + +void StarboardRendererClient::OnBufferedTimeRangesChange( + base::TimeDelta start, + base::TimeDelta length) { + DCHECK(media_task_runner_->RunsTasksInCurrentSequence()); +#if BUILDFLAG(IS_IOS_TVOS) + if (media_resource_) { + media_resource_->ForwardBufferedTimeRangesToDemuxerHost(start, length); + } +#endif // BUILDFLAG(IS_IOS_TVOS) +} + #if BUILDFLAG(IS_ANDROID) void StarboardRendererClient::RequestOverlayInfo(bool restart_for_transitions) { DCHECK(media_task_runner_->RunsTasksInCurrentSequence()); diff --git a/media/mojo/clients/starboard/starboard_renderer_client.h b/media/mojo/clients/starboard/starboard_renderer_client.h index c0b5a18648c2..029847c151f6 100644 --- a/media/mojo/clients/starboard/starboard_renderer_client.h +++ b/media/mojo/clients/starboard/starboard_renderer_client.h @@ -21,6 +21,7 @@ #include "base/memory/weak_ptr.h" #include "base/synchronization/lock.h" #include "base/task/sequenced_task_runner.h" +#include "build/build_config.h" #include "media/base/pipeline_status.h" #include "media/base/renderer_client.h" #include "media/base/starboard/starboard_rendering_mode.h" @@ -110,6 +111,9 @@ class MEDIA_EXPORT StarboardRendererClient void PaintVideoHoleFrame(const gfx::Size& size) override; void UpdateStarboardRenderingMode(const StarboardRenderingMode mode) override; void GetSbWindowHandle() override; + void OnDurationChange(base::TimeDelta duration) override; + void OnBufferedTimeRangesChange(base::TimeDelta start, + base::TimeDelta length) override; #if BUILDFLAG(IS_ANDROID) void RequestOverlayInfo(bool restart_for_transitions) override; #endif // BUILDFLAG(IS_ANDROID) @@ -169,6 +173,7 @@ class MEDIA_EXPORT StarboardRendererClient mojo::Remote renderer_extension_; raw_ptr client_ = nullptr; + raw_ptr media_resource_ = nullptr; PipelineStatusCallback init_cb_; // Rendering mode the Starboard Renderer will use. diff --git a/media/mojo/mojom/renderer_extensions.mojom b/media/mojo/mojom/renderer_extensions.mojom index 8ee721606d50..6b2fc2f0660b 100644 --- a/media/mojo/mojom/renderer_extensions.mojom +++ b/media/mojo/mojom/renderer_extensions.mojom @@ -80,6 +80,11 @@ interface StarboardRendererClientExtension { // Get SbWindow from StarboardRendererClient. GetSbWindowHandle(); + // URL player duration and buffered ranges forwarding. + OnDurationChange(mojo_base.mojom.TimeDelta duration); + OnBufferedTimeRangesChange(mojo_base.mojom.TimeDelta start, + mojo_base.mojom.TimeDelta length); + [EnableIf=is_android] // Request OverlayInfo from StarboardRendererClient. RequestOverlayInfo(bool restart_for_transitions); diff --git a/media/mojo/services/starboard/starboard_renderer_wrapper.cc b/media/mojo/services/starboard/starboard_renderer_wrapper.cc index f9e44e8d414e..9a2092914657 100644 --- a/media/mojo/services/starboard/starboard_renderer_wrapper.cc +++ b/media/mojo/services/starboard/starboard_renderer_wrapper.cc @@ -248,6 +248,15 @@ void StarboardRendererWrapper::Initialize(MediaResource* media_resource, #endif // BUILDFLAG(IS_ANDROID) ); +#if SB_HAS(PLAYER_WITH_URL) + // Wire duration and buffered ranges callbacks. + GetRenderer()->SetDurationChangeCB(base::BindRepeating( + &StarboardRendererWrapper::OnDurationChange, weak_factory_.GetWeakPtr())); + GetRenderer()->SetBufferedRangesCB( + base::BindRepeating(&StarboardRendererWrapper::OnBufferedTimeRangesChange, + weak_factory_.GetWeakPtr())); +#endif // SB_HAS(PLAYER_WITH_URL) + base::ScopedClosureRunner scoped_init_cb( base::BindOnce(&StarboardRendererWrapper::ContinueInitialization, weak_factory_.GetWeakPtr(), std::move(media_resource), @@ -626,6 +635,20 @@ void StarboardRendererWrapper::OnGetSbWindowHandle() { client_extension_remote_->GetSbWindowHandle(); } +#if SB_HAS(PLAYER_WITH_URL) +void StarboardRendererWrapper::OnDurationChange(base::TimeDelta duration) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + client_extension_remote_->OnDurationChange(duration); +} + +void StarboardRendererWrapper::OnBufferedTimeRangesChange( + base::TimeDelta start, + base::TimeDelta length) { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + client_extension_remote_->OnBufferedTimeRangesChange(start, length); +} +#endif // SB_HAS(PLAYER_WITH_URL) + void StarboardRendererWrapper::OnSubscribeToVideoGeometryChange( MediaResource* /* media_resource */, RendererClient* /* client */) { diff --git a/media/mojo/services/starboard/starboard_renderer_wrapper.h b/media/mojo/services/starboard/starboard_renderer_wrapper.h index ff9bd79511f9..a91c09154986 100644 --- a/media/mojo/services/starboard/starboard_renderer_wrapper.h +++ b/media/mojo/services/starboard/starboard_renderer_wrapper.h @@ -136,6 +136,11 @@ class StarboardRendererWrapper void OnUpdateStarboardRenderingModeByStarboard( const StarboardRenderingMode mode); void OnGetSbWindowHandle(); +#if SB_HAS(PLAYER_WITH_URL) + void OnDurationChange(base::TimeDelta duration); + void OnBufferedTimeRangesChange(base::TimeDelta start, + base::TimeDelta length); +#endif // SB_HAS(PLAYER_WITH_URL) void OnSubscribeToVideoGeometryChange(MediaResource* media_resource, RendererClient* client); #if BUILDFLAG(IS_ANDROID) diff --git a/media/starboard/starboard_renderer.cc b/media/starboard/starboard_renderer.cc index e4825b0cb3ee..bbb428a89718 100644 --- a/media/starboard/starboard_renderer.cc +++ b/media/starboard/starboard_renderer.cc @@ -26,6 +26,7 @@ #include "media/base/decoder_buffer.h" #include "media/base/media_switches.h" #include "media/base/starboard/experimental_features.h" +#include "media/base/timestamp_constants.h" #include "media/base/video_codecs.h" #include "media/starboard/buildflags.h" #include "media/starboard/decoder_buffer_allocator.h" @@ -480,6 +481,17 @@ TimeDelta StarboardRenderer::GetMediaTime() { FROM_HERE, base::BindOnce(&StarboardRenderer::OnStatisticsUpdate, weak_factory_.GetWeakPtr(), statistics)); } +#if SB_HAS(PLAYER_WITH_URL) + if (IsUrlPlayer() && player_bridge_ && buffered_ranges_cb_) { + TimeDelta buffer_start, buffer_length; + player_bridge_->GetUrlPlayerBufferedTimeRanges(&buffer_start, + &buffer_length); + if (buffer_length > TimeDelta()) { + buffered_ranges_cb_.Run(buffer_start, buffer_length); + } + } +#endif // SB_HAS(PLAYER_WITH_URL) + StoreMediaTime(media_time); return media_time; @@ -566,6 +578,15 @@ void StarboardRenderer::OnUrlPlayerPresenting() { << ") at presenting; skipping video hole update."; } + // Forward duration; zero is treated as infinite. + if (duration_change_cb_) { + TimeDelta duration = player_bridge_->GetDuration(); + if (duration.is_zero()) { + duration = kInfiniteDuration; + } + duration_change_cb_.Run(duration); + } + // Re-apply playback rate; the platform player ignores rate changes // before it is ready to play. player_bridge_->SetPlaybackRate(playback_rate_); diff --git a/media/starboard/starboard_renderer.h b/media/starboard/starboard_renderer.h index f46e187c688d..642f4c029b5e 100644 --- a/media/starboard/starboard_renderer.h +++ b/media/starboard/starboard_renderer.h @@ -106,6 +106,21 @@ class MEDIA_EXPORT StarboardRenderer : public Renderer, using UpdateStarboardRenderingModeCallback = base::RepeatingCallback; using GetSbWindowHandleCallback = base::RepeatingCallback; +#if SB_HAS(PLAYER_WITH_URL) + using DurationChangeCB = + base::RepeatingCallback; + using BufferedRangesCB = + base::RepeatingCallback; + + void SetDurationChangeCB(DurationChangeCB cb) { + duration_change_cb_ = std::move(cb); + } + void SetBufferedRangesCB(BufferedRangesCB cb) { + buffered_ranges_cb_ = std::move(cb); + } +#endif // SB_HAS(PLAYER_WITH_URL) + #if BUILDFLAG(IS_ANDROID) using RequestOverlayInfoCallBack = base::RepeatingCallback; @@ -157,6 +172,8 @@ class MEDIA_EXPORT StarboardRenderer : public Renderer, }; // Returns true when the renderer is operating in URL player mode. + // This is a runtime check only; it does not replace the compile-time + // #if SB_HAS(PLAYER_WITH_URL) guard needed for URL-player-only symbols. bool IsUrlPlayer() const; // Handles presenting state for URL player: propagates video resolution // for hole-punch rendering and re-applies playback rate. @@ -237,6 +254,10 @@ class MEDIA_EXPORT StarboardRenderer : public Renderer, PaintVideoHoleFrameCallback paint_video_hole_frame_cb_; UpdateStarboardRenderingModeCallback update_starboard_rendering_mode_cb_; GetSbWindowHandleCallback get_sb_window_handle_cb_; +#if SB_HAS(PLAYER_WITH_URL) + DurationChangeCB duration_change_cb_; + BufferedRangesCB buffered_ranges_cb_; +#endif // SB_HAS(PLAYER_WITH_URL) #if BUILDFLAG(IS_ANDROID) RequestOverlayInfoCallBack request_overlay_info_cb_; #endif // BUILDFLAG(IS_ANDROID) diff --git a/media/starboard/url_player_demuxer.cc b/media/starboard/url_player_demuxer.cc index bf3259347267..4e3f4dc54317 100644 --- a/media/starboard/url_player_demuxer.cc +++ b/media/starboard/url_player_demuxer.cc @@ -24,6 +24,7 @@ #include "media/base/channel_layout.h" #include "media/base/decoder_buffer.h" #include "media/base/media_track.h" +#include "media/base/ranges.h" #include "media/base/sample_format.h" #include "media/base/video_decoder_config.h" @@ -147,4 +148,21 @@ void UrlPlayerDemuxer::OnTracksChanged( void UrlPlayerDemuxer::SetPlaybackRate(double rate) {} +void UrlPlayerDemuxer::ForwardDurationChangeToDemuxerHost( + base::TimeDelta duration) { + if (host_) { + host_->SetDuration(duration); + } +} + +void UrlPlayerDemuxer::ForwardBufferedTimeRangesToDemuxerHost( + base::TimeDelta start, + base::TimeDelta length) { + if (host_) { + Ranges ranges; + ranges.Add(start, start + length); + host_->OnBufferedTimeRangesChanged(ranges); + } +} + } // namespace media diff --git a/media/starboard/url_player_demuxer.h b/media/starboard/url_player_demuxer.h index 2cd0e8779e43..644403fe3455 100644 --- a/media/starboard/url_player_demuxer.h +++ b/media/starboard/url_player_demuxer.h @@ -86,6 +86,11 @@ class MEDIA_EXPORT UrlPlayerDemuxer : public Demuxer { TrackChangeCB change_completed_cb) override; void SetPlaybackRate(double rate) override; + // MediaResource overrides. + void ForwardDurationChangeToDemuxerHost(base::TimeDelta duration) override; + void ForwardBufferedTimeRangesToDemuxerHost(base::TimeDelta start, + base::TimeDelta length) override; + private: scoped_refptr media_task_runner_; raw_ptr host_ = nullptr;