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..f68ed21ee8d7 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,17 @@ DemuxerStream* MediaResource::GetFirstStream(DemuxerStream::Type type) { return nullptr; } +#if BUILDFLAG(IS_IOS_TVOS) && BUILDFLAG(USE_STARBOARD_MEDIA) +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 62e0b605df2f..41e14d3497c9 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,18 @@ 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; + + // 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) }; } // namespace media diff --git a/media/mojo/clients/starboard/starboard_renderer_client.cc b/media/mojo/clients/starboard/starboard_renderer_client.cc index 6a85a2f1c728..ca7aac2fbe2a 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) @@ -102,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()); @@ -280,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()); @@ -351,8 +377,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 +414,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.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/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..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); @@ -151,6 +156,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..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), @@ -515,6 +524,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) { @@ -619,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 efd09cfee6bd..a91c09154986 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) @@ -135,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/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..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" @@ -213,6 +214,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); @@ -470,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; @@ -530,6 +552,61 @@ 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."; + } + + // 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_); +#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 +659,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 +699,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 +957,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 +1088,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..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; @@ -122,6 +137,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 +171,14 @@ class MEDIA_EXPORT StarboardRenderer : public Renderer, STATE_ERROR }; + // 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. + void OnUrlPlayerPresenting(); + void CreatePlayerBridge(); void ApplyPendingBounds(); void UpdateDecoderConfig(DemuxerStream* stream); @@ -200,6 +229,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_; @@ -222,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 new file mode 100644 index 000000000000..4e3f4dc54317 --- /dev/null +++ b/media/starboard/url_player_demuxer.cc @@ -0,0 +1,168 @@ +// 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/ranges.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) {} + +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 new file mode 100644 index 000000000000..644403fe3455 --- /dev/null +++ b/media/starboard/url_player_demuxer.h @@ -0,0 +1,104 @@ +// 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; + + // 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; + 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. +