From 7e04f7faf8b49be64d708352ff83277d0693061f Mon Sep 17 00:00:00 2001 From: Drew Thomas Date: Wed, 16 Oct 2024 09:11:48 -0700 Subject: [PATCH] Create stub for cross-thread MediaSource attachments (#4169) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enables MediaSource, SourceBuffer, and SourceBufferList objects to be created on Dedicated Workers. Creation of these objects is not gated behind any H5VCC flags, but they will be non-transferable without usage of H5VCC flags. The initial implementation of CrossThreadMediaSourceAttachment is very bare bones, and does not allow for MediaElements to interact with the attached MediaSources yet. However, this lays the groundwork for supporting creating the relevant MediaSource objects on the worker thread, and the transfer process to get the reference back to the main thread. Usage of creating MediaSource URLs is gated through the `MediaSource.EnableInWorkers` H5VCC flag, in addition to existing flags `MediaElement.EnableUsingMediaSourceBufferedRange` and `MediaElement.EnableUsingMediaSourceAttachmentMethods`. If and only if all three flags are enabled, MediaSources created on Dedicated Workers and passed to `URL.createObjectURL` will return a non-empty string that can be sent to the main thread, and used to 'access' the MediaSourceAttachment. In addition, if and only if all three H5VCC flags are enabled, [`MediaSource.canConstructInDedicatedWorker`](https://developer.mozilla.org/en-US/docs/Web/API/MediaSource/canConstructInDedicatedWorker_static) will return true. This is based on the following Chromium commit: • https://chromium-review.googlesource.com/c/chromium/src/+/2407075 b/338425449 --- cobalt/dom/BUILD.gn | 2 + .../cross_thread_media_source_attachment.cc | 131 ++++++++++++++++++ .../cross_thread_media_source_attachment.h | 98 +++++++++++++ cobalt/dom/media_settings.cc | 6 + cobalt/dom/media_settings.h | 6 + cobalt/dom/media_settings_test.cc | 10 ++ cobalt/dom/media_source.cc | 79 ++++++++++- cobalt/dom/media_source.h | 3 + cobalt/dom/media_source.idl | 3 + cobalt/dom/source_buffer.idl | 3 + cobalt/dom/source_buffer_list.idl | 3 + cobalt/dom/url_media_source.cc | 65 +++++++-- cobalt/web/url.cc | 67 +++++++++ cobalt/web/url_registry.h | 9 +- cobalt/worker/dedicated_worker.cc | 71 +++++++++- cobalt/worker/dedicated_worker.h | 3 +- cobalt/worker/worker.cc | 4 +- cobalt/worker/worker.h | 4 + cobalt/worker/worker_settings.cc | 9 +- cobalt/worker/worker_settings.h | 21 ++- 20 files changed, 571 insertions(+), 26 deletions(-) create mode 100644 cobalt/dom/cross_thread_media_source_attachment.cc create mode 100644 cobalt/dom/cross_thread_media_source_attachment.h diff --git a/cobalt/dom/BUILD.gn b/cobalt/dom/BUILD.gn index 97d2e676af26..202821b3a259 100644 --- a/cobalt/dom/BUILD.gn +++ b/cobalt/dom/BUILD.gn @@ -57,6 +57,8 @@ static_library("dom") { "character_data.h", "comment.cc", "comment.h", + "cross_thread_media_source_attachment.cc", + "cross_thread_media_source_attachment.h", "css_animations_adapter.cc", "css_animations_adapter.h", "css_transitions_adapter.cc", diff --git a/cobalt/dom/cross_thread_media_source_attachment.cc b/cobalt/dom/cross_thread_media_source_attachment.cc new file mode 100644 index 000000000000..1f798cd9e9f4 --- /dev/null +++ b/cobalt/dom/cross_thread_media_source_attachment.cc @@ -0,0 +1,131 @@ +// Copyright 2024 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. +// +// Copyright 2020 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "cobalt/dom/cross_thread_media_source_attachment.h" + +#include "base/task/sequenced_task_runner.h" +#include "cobalt/dom/media_source.h" +#include "cobalt/dom/media_source_attachment.h" +#include "cobalt/dom/media_source_ready_state.h" +#include "cobalt/dom/time_ranges.h" +#include "cobalt/script/tracer.h" +#include "cobalt/web/url_registry.h" +#include "media/filters/chunk_demuxer.h" + + +namespace cobalt { +namespace dom { + +CrossThreadMediaSourceAttachment::CrossThreadMediaSourceAttachment( + scoped_refptr media_source) + : media_source_(media_source), + task_runner_(base::SequencedTaskRunner::GetCurrentDefault()), + recent_element_time_(0.0), + element_has_error_(false) {} + +void CrossThreadMediaSourceAttachment::TraceMembers(script::Tracer* tracer) { + DCHECK_NE(task_runner_, base::SequencedTaskRunner::GetCurrentDefault()); + + tracer->Trace(attached_element_); + tracer->Trace(media_source_); +} + +bool CrossThreadMediaSourceAttachment::StartAttachingToMediaElement( + HTMLMediaElement* media_element) { + DCHECK_NE(task_runner_, base::SequencedTaskRunner::GetCurrentDefault()); + NOTIMPLEMENTED(); + return false; +} + +void CrossThreadMediaSourceAttachment::CompleteAttachingToMediaElement( + ::media::ChunkDemuxer* chunk_demuxer) { + DCHECK_NE(task_runner_, base::SequencedTaskRunner::GetCurrentDefault()); + NOTIMPLEMENTED(); +} + +void CrossThreadMediaSourceAttachment::Close() { + DCHECK_NE(task_runner_, base::SequencedTaskRunner::GetCurrentDefault()); + NOTIMPLEMENTED(); +} + +scoped_refptr CrossThreadMediaSourceAttachment::GetBufferedRange() + const { + DCHECK_NE(task_runner_, base::SequencedTaskRunner::GetCurrentDefault()); + NOTIMPLEMENTED(); + return nullptr; +} + +MediaSourceReadyState CrossThreadMediaSourceAttachment::GetReadyState() const { + DCHECK_NE(task_runner_, base::SequencedTaskRunner::GetCurrentDefault()); + NOTIMPLEMENTED(); + return kMediaSourceReadyStateClosed; +} + +void CrossThreadMediaSourceAttachment::NotifyDurationChanged(double duration) { + DCHECK_NE(task_runner_, base::SequencedTaskRunner::GetCurrentDefault()); + NOTIMPLEMENTED(); +} + +bool CrossThreadMediaSourceAttachment::HasMaxVideoCapabilities() const { + DCHECK_NE(task_runner_, base::SequencedTaskRunner::GetCurrentDefault()); + NOTIMPLEMENTED(); + return false; +} + +double CrossThreadMediaSourceAttachment::GetRecentMediaTime() { + DCHECK_NE(task_runner_, base::SequencedTaskRunner::GetCurrentDefault()); + NOTIMPLEMENTED(); + return 0; +} + +bool CrossThreadMediaSourceAttachment::GetElementError() { + DCHECK_NE(task_runner_, base::SequencedTaskRunner::GetCurrentDefault()); + NOTIMPLEMENTED(); + return false; +} + +scoped_refptr +CrossThreadMediaSourceAttachment::CreateAudioTrackList( + script::EnvironmentSettings* settings) { + DCHECK_NE(task_runner_, base::SequencedTaskRunner::GetCurrentDefault()); + NOTIMPLEMENTED(); + return nullptr; +} + +scoped_refptr +CrossThreadMediaSourceAttachment::CreateVideoTrackList( + script::EnvironmentSettings* settings) { + DCHECK_NE(task_runner_, base::SequencedTaskRunner::GetCurrentDefault()); + NOTIMPLEMENTED(); + return nullptr; +} + +void CrossThreadMediaSourceAttachment::OnElementTimeUpdate(double time) { + NOTIMPLEMENTED(); + recent_element_time_ = time; +} + +void CrossThreadMediaSourceAttachment::OnElementError() { + DCHECK(!element_has_error_) + << "At most one transition to element error per attachment is expected"; + NOTIMPLEMENTED(); + element_has_error_ = true; +} + +} // namespace dom +} // namespace cobalt diff --git a/cobalt/dom/cross_thread_media_source_attachment.h b/cobalt/dom/cross_thread_media_source_attachment.h new file mode 100644 index 000000000000..134b0a5550af --- /dev/null +++ b/cobalt/dom/cross_thread_media_source_attachment.h @@ -0,0 +1,98 @@ +// Copyright 2024 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. +// +// Copyright 2020 The Chromium Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COBALT_DOM_CROSS_THREAD_MEDIA_SOURCE_ATTACHMENT_H_ +#define COBALT_DOM_CROSS_THREAD_MEDIA_SOURCE_ATTACHMENT_H_ + +#include "base/memory/weak_ptr.h" +#include "base/task/sequenced_task_runner.h" +#include "cobalt/dom/audio_track_list.h" +#include "cobalt/dom/media_source.h" +#include "cobalt/dom/media_source_attachment_supplement.h" +#include "cobalt/dom/media_source_ready_state.h" +#include "cobalt/dom/time_ranges.h" +#include "cobalt/dom/video_track_list.h" +#include "cobalt/script/environment_settings.h" +#include "cobalt/script/tracer.h" +#include "cobalt/web/url_registry.h" +#include "media/filters/chunk_demuxer.h" + +namespace cobalt { +namespace dom { + +// MediaSourceAttachment that supports operations between a HTMLMediaElement on +// the main browser thread and a MediaSource on a DedicatedWorker. +class CrossThreadMediaSourceAttachment + : public MediaSourceAttachmentSupplement { + public: + explicit CrossThreadMediaSourceAttachment( + scoped_refptr media_source); + + // Traceable + void TraceMembers(script::Tracer* tracer) override; + + // MediaSourceAttachment + bool StartAttachingToMediaElement(HTMLMediaElement* media_element) override; + void CompleteAttachingToMediaElement( + ::media::ChunkDemuxer* chunk_demuxer) override; + void Close() override; + scoped_refptr GetBufferedRange() const override; + MediaSourceReadyState GetReadyState() const override; + + void OnElementTimeUpdate(double time) override; + void OnElementError() override; + + // MediaSourceAttachmentSupplement + void NotifyDurationChanged(double duration) override; + bool HasMaxVideoCapabilities() const override; + double GetRecentMediaTime() override; + bool GetElementError() override; + scoped_refptr CreateAudioTrackList( + script::EnvironmentSettings* settings) override; + scoped_refptr CreateVideoTrackList( + script::EnvironmentSettings* settings) override; + + // TODO(338425449): Remove methods after feature rollout. + scoped_refptr media_source() const override { return nullptr; } + base::WeakPtr media_element() const override { + return nullptr; + } + + private: + ~CrossThreadMediaSourceAttachment() = default; + + // Reference to the registered MediaSource. + scoped_refptr media_source_; + + // Reference to the HTMLMediaElement the associated MediaSource is attached + // to. Only set after StartAttachingToMediaElement is called. + base::WeakPtr attached_element_ = nullptr; + + // Used to ensure all calls are made on the thread that created this object. + base::SequencedTaskRunner* task_runner_; + + double recent_element_time_; // See OnElementTimeUpdate(). + bool element_has_error_; // See OnElementError(). + + DISALLOW_COPY_AND_ASSIGN(CrossThreadMediaSourceAttachment); +}; + +} // namespace dom +} // namespace cobalt + +#endif // COBALT_DOM_CROSS_THREAD_MEDIA_SOURCE_ATTACHMENT_H_ diff --git a/cobalt/dom/media_settings.cc b/cobalt/dom/media_settings.cc index 45a3c826ce3f..a8ef3e2cc896 100644 --- a/cobalt/dom/media_settings.cc +++ b/cobalt/dom/media_settings.cc @@ -96,6 +96,12 @@ bool MediaSettingsImpl::Set(const std::string& name, int value) { LOG(INFO) << name << ": set to " << value; return true; } + } else if (name == "MediaSource.EnableInWorkers") { + if (value == 0 || value == 1) { + is_mse_in_workers_enabled_ = value != 0; + LOG(INFO) << name << ": set to " << value; + return true; + } } else { LOG(WARNING) << "Ignore unknown setting with name \"" << name << "\""; return false; diff --git a/cobalt/dom/media_settings.h b/cobalt/dom/media_settings.h index a92ee9992e4b..007245f044b5 100644 --- a/cobalt/dom/media_settings.h +++ b/cobalt/dom/media_settings.h @@ -46,6 +46,7 @@ class MediaSettings { IsMediaElementUsingMediaSourceBufferedRangeEnabled() const = 0; virtual base::Optional IsMediaElementUsingMediaSourceAttachmentMethodsEnabled() const = 0; + virtual base::Optional IsMseInWorkersEnabled() const = 0; protected: MediaSettings() = default; @@ -107,6 +108,10 @@ class MediaSettingsImpl : public MediaSettings { base::AutoLock auto_lock(lock_); return is_media_element_using_media_source_attachment_methods_enabled_; } + base::Optional IsMseInWorkersEnabled() const override { + base::AutoLock auto_lock(lock_); + return is_mse_in_workers_enabled_; + } // Returns true when the setting associated with `name` is set to `value`. // Returns false when `name` is not associated with any settings, or if @@ -128,6 +133,7 @@ class MediaSettingsImpl : public MediaSettings { is_media_element_using_media_source_buffered_range_enabled_; base::Optional is_media_element_using_media_source_attachment_methods_enabled_; + base::Optional is_mse_in_workers_enabled_; base::Optional is_painting_video_background_to_black_; }; diff --git a/cobalt/dom/media_settings_test.cc b/cobalt/dom/media_settings_test.cc index 935aeb0049d8..e42f98b17807 100644 --- a/cobalt/dom/media_settings_test.cc +++ b/cobalt/dom/media_settings_test.cc @@ -32,6 +32,7 @@ TEST(MediaSettingsImplTest, Empty) { EXPECT_FALSE(impl.GetMaxSourceBufferAppendSizeInBytes()); EXPECT_FALSE(impl.GetMediaElementTimeupdateEventIntervalInMilliseconds()); EXPECT_FALSE(impl.IsPaintingVideoBackgroundToBlack()); + EXPECT_FALSE(impl.IsMseInWorkersEnabled()); } TEST(MediaSettingsImplTest, SunnyDay) { @@ -51,6 +52,7 @@ TEST(MediaSettingsImplTest, SunnyDay) { ASSERT_TRUE(impl.Set("MediaElement.EnableUsingMediaSourceBufferedRange", 1)); ASSERT_TRUE( impl.Set("MediaElement.EnableUsingMediaSourceAttachmentMethods", 1)); + ASSERT_TRUE(impl.Set("MediaSource.EnableInWorkers", 1)); EXPECT_EQ(impl.GetSourceBufferEvictExtraInBytes().value(), 100); EXPECT_EQ(impl.GetMinimumProcessorCountToOffloadAlgorithm().value(), 101); @@ -66,6 +68,7 @@ TEST(MediaSettingsImplTest, SunnyDay) { impl.IsMediaElementUsingMediaSourceBufferedRangeEnabled().value()); EXPECT_TRUE( impl.IsMediaElementUsingMediaSourceAttachmentMethodsEnabled().value()); + EXPECT_TRUE(impl.IsMseInWorkersEnabled().value()); } TEST(MediaSettingsImplTest, RainyDay) { @@ -86,6 +89,7 @@ TEST(MediaSettingsImplTest, RainyDay) { impl.Set("MediaElement.EnableUsingMediaSourceBufferedRange", -101)); ASSERT_FALSE( impl.Set("MediaElement.EnableUsingMediaSourceAttachmentMethods", -101)); + ASSERT_FALSE(impl.Set("MediaElement.EnableInWorkers", -123)); EXPECT_FALSE(impl.GetSourceBufferEvictExtraInBytes()); EXPECT_FALSE(impl.GetMinimumProcessorCountToOffloadAlgorithm()); @@ -98,6 +102,7 @@ TEST(MediaSettingsImplTest, RainyDay) { EXPECT_FALSE(impl.IsPaintingVideoBackgroundToBlack()); EXPECT_FALSE(impl.IsMediaElementUsingMediaSourceBufferedRangeEnabled()); EXPECT_FALSE(impl.IsMediaElementUsingMediaSourceAttachmentMethodsEnabled()); + EXPECT_FALSE(impl.IsMseInWorkersEnabled()); } TEST(MediaSettingsImplTest, ZeroValuesWork) { @@ -117,6 +122,7 @@ TEST(MediaSettingsImplTest, ZeroValuesWork) { ASSERT_TRUE(impl.Set("MediaElement.EnableUsingMediaSourceBufferedRange", 0)); ASSERT_TRUE( impl.Set("MediaElement.EnableUsingMediaSourceAttachmentMethods", 0)); + ASSERT_TRUE(impl.Set("MediaSource.EnableInWorkers", 0)); EXPECT_EQ(impl.GetSourceBufferEvictExtraInBytes().value(), 0); EXPECT_EQ(impl.GetMinimumProcessorCountToOffloadAlgorithm().value(), 0); @@ -129,6 +135,7 @@ TEST(MediaSettingsImplTest, ZeroValuesWork) { impl.IsMediaElementUsingMediaSourceBufferedRangeEnabled().value()); EXPECT_FALSE( impl.IsMediaElementUsingMediaSourceAttachmentMethodsEnabled().value()); + EXPECT_FALSE(impl.IsMseInWorkersEnabled().value()); } TEST(MediaSettingsImplTest, Updatable) { @@ -148,6 +155,7 @@ TEST(MediaSettingsImplTest, Updatable) { ASSERT_TRUE(impl.Set("MediaElement.EnableUsingMediaSourceBufferedRange", 0)); ASSERT_TRUE( impl.Set("MediaElement.EnableUsingMediaSourceAttachmentMethods", 0)); + ASSERT_TRUE(impl.Set("MediaSource.EnableInWorkers", 0)); ASSERT_TRUE(impl.Set("MediaSource.SourceBufferEvictExtraInBytes", 1)); ASSERT_TRUE( @@ -163,6 +171,7 @@ TEST(MediaSettingsImplTest, Updatable) { ASSERT_TRUE(impl.Set("MediaElement.EnableUsingMediaSourceBufferedRange", 1)); ASSERT_TRUE( impl.Set("MediaElement.EnableUsingMediaSourceAttachmentMethods", 1)); + ASSERT_TRUE(impl.Set("MediaSource.EnableInWorkers", 1)); EXPECT_EQ(impl.GetSourceBufferEvictExtraInBytes().value(), 1); EXPECT_EQ(impl.GetMinimumProcessorCountToOffloadAlgorithm().value(), 1); @@ -178,6 +187,7 @@ TEST(MediaSettingsImplTest, Updatable) { impl.IsMediaElementUsingMediaSourceBufferedRangeEnabled().value()); EXPECT_TRUE( impl.IsMediaElementUsingMediaSourceAttachmentMethodsEnabled().value()); + EXPECT_TRUE(impl.IsMseInWorkersEnabled().value()); } TEST(MediaSettingsImplTest, InvalidSettingNames) { diff --git a/cobalt/dom/media_source.cc b/cobalt/dom/media_source.cc index 16dbea235dfe..0785b4fa211e 100644 --- a/cobalt/dom/media_source.cc +++ b/cobalt/dom/media_source.cc @@ -63,7 +63,10 @@ #include "cobalt/dom/same_thread_media_source_attachment.h" #include "cobalt/web/context.h" #include "cobalt/web/dom_exception.h" +#include "cobalt/web/environment_settings.h" #include "cobalt/web/event.h" +#include "cobalt/web/window_or_worker_global_scope.h" +#include "cobalt/worker/worker_settings.h" #include "media/base/pipeline_status.h" #include "starboard/media.h" @@ -145,6 +148,27 @@ bool IsMediaElementUsingMediaSourceBufferedRangeEnabled( .value_or(false); } +// If this function returns true, communication between HTMLMediaElement and +// MediaSource objects will be fully proxied between MediaSourceAttachment. +// The default value is false. +bool IsMediaElementUsingMediaSourceAttachmentMethodsEnabled( + web::EnvironmentSettings* settings) { + return GetMediaSettings(settings) + .IsMediaElementUsingMediaSourceAttachmentMethodsEnabled() + .value_or(false); +} + +// If this function returns true, experimental support for creating MediaSource +// objects in Dedicated Workers will be enabled. This also allows MSE handles +// to be transferred from Dedicated Workers back to the main thread. +// Requires MediaElement.EnableUsingMediaSourceBufferedRange and +// MediaElement.EnableUsingMediaSourceAttachmentMethods as prerequisites for +// this feature. +// The default value is false. +bool IsMseInWorkersEnabled(web::EnvironmentSettings* settings) { + return GetMediaSettings(settings).IsMseInWorkersEnabled().value_or(false); +} + } // namespace MediaSource::MediaSource(script::EnvironmentSettings* settings) @@ -406,11 +430,46 @@ bool MediaSource::IsTypeSupported(script::EnvironmentSettings* settings, const std::string& type) { TRACE_EVENT1("cobalt::dom", "MediaSource::IsTypeSupported()", "type", type); DCHECK(settings); - DOMSettings* dom_settings = - base::polymorphic_downcast(settings); - DCHECK(dom_settings->can_play_type_handler()); - SbMediaSupportType support_type = - dom_settings->can_play_type_handler()->CanPlayAdaptive(type.c_str(), ""); + + SbMediaSupportType support_type; + web::EnvironmentSettings* web_settings = + base::polymorphic_downcast(settings); + DCHECK(web_settings); + if (IsMseInWorkersEnabled(web_settings) && + IsMediaElementUsingMediaSourceAttachmentMethodsEnabled(web_settings) && + IsMediaElementUsingMediaSourceBufferedRangeEnabled(web_settings)) { + DCHECK(web_settings->context()); + web::WindowOrWorkerGlobalScope* global_scope = + web_settings->context()->GetWindowOrWorkerGlobalScope(); + + if (global_scope->IsDedicatedWorker()) { + worker::WorkerSettings* worker_settings = + base::polymorphic_downcast(settings); + DCHECK(worker_settings); + DCHECK(worker_settings->can_play_type_handler()); + support_type = worker_settings->can_play_type_handler()->CanPlayAdaptive( + type.c_str(), ""); + } else if (global_scope->IsWindow()) { + dom::DOMSettings* dom_settings = + base::polymorphic_downcast(settings); + DCHECK(dom_settings); + DCHECK(dom_settings->can_play_type_handler()); + support_type = dom_settings->can_play_type_handler()->CanPlayAdaptive( + type.c_str(), ""); + } else { + CHECK(false) + << "MediaSource.IsTypeSupported() is only supported from Dedicated " + "Workers and the main Window"; + return false; + } + } else { + DOMSettings* dom_settings = + base::polymorphic_downcast(settings); + DCHECK(dom_settings->can_play_type_handler()); + support_type = dom_settings->can_play_type_handler()->CanPlayAdaptive( + type.c_str(), ""); + } + switch (support_type) { case kSbMediaSupportTypeNotSupported: return false; @@ -424,6 +483,16 @@ bool MediaSource::IsTypeSupported(script::EnvironmentSettings* settings, } } +// static +bool MediaSource::can_construct_in_dedicated_worker( + script::EnvironmentSettings* settings) { + web::EnvironmentSettings* web_settings = + base::polymorphic_downcast(settings); + return IsMseInWorkersEnabled(web_settings) && + IsMediaElementUsingMediaSourceAttachmentMethodsEnabled(web_settings) && + IsMediaElementUsingMediaSourceBufferedRangeEnabled(web_settings); +} + bool MediaSource::StartAttachingToMediaElement( HTMLMediaElement* media_element) { is_using_media_source_attachment_methods_ = false; diff --git a/cobalt/dom/media_source.h b/cobalt/dom/media_source.h index 1cae5b40b17f..44087ec640f2 100644 --- a/cobalt/dom/media_source.h +++ b/cobalt/dom/media_source.h @@ -109,6 +109,9 @@ class MediaSource : public web::EventTarget { static bool IsTypeSupported(script::EnvironmentSettings* settings, const std::string& type); + static bool can_construct_in_dedicated_worker( + script::EnvironmentSettings* settings); + // Custom, not in any spec. // // HTMLMediaSource diff --git a/cobalt/dom/media_source.idl b/cobalt/dom/media_source.idl index 63a64d8b825a..1ebde681a889 100644 --- a/cobalt/dom/media_source.idl +++ b/cobalt/dom/media_source.idl @@ -18,6 +18,7 @@ [ Constructor, ConstructorCallWith=EnvironmentSettings, + Exposed=(Window,DedicatedWorker), ] interface MediaSource : EventTarget { // All the source buffers created by this object. @@ -28,6 +29,8 @@ interface MediaSource : EventTarget { readonly attribute MediaSourceReadyState readyState; [RaisesException] attribute unrestricted double duration; + [CallWith=EnvironmentSettings] static readonly attribute boolean canConstructInDedicatedWorker; + [RaisesException, CallWith=EnvironmentSettings] SourceBuffer addSourceBuffer(DOMString type); [RaisesException] void removeSourceBuffer(SourceBuffer sourceBuffer); diff --git a/cobalt/dom/source_buffer.idl b/cobalt/dom/source_buffer.idl index a963802b1ea5..1877a6046b5a 100644 --- a/cobalt/dom/source_buffer.idl +++ b/cobalt/dom/source_buffer.idl @@ -15,6 +15,9 @@ // https://www.w3.org/TR/media-source/#idl-def-SourceBuffer // https://www.w3.org/TR/2016/CR-media-source-20160705/#sourcebuffer +[ + Exposed=(Window,DedicatedWorker), +] interface SourceBuffer : EventTarget { // MSE 2016 Interface [RaisesException] attribute SourceBufferAppendMode mode; diff --git a/cobalt/dom/source_buffer_list.idl b/cobalt/dom/source_buffer_list.idl index a2f4544b8c37..4cc568a8a34a 100644 --- a/cobalt/dom/source_buffer_list.idl +++ b/cobalt/dom/source_buffer_list.idl @@ -15,6 +15,9 @@ // https://www.w3.org/TR/media-source/#idl-def-SourceBuffer // https://www.w3.org/TR/2016/CR-media-source-20160705/#sourcebufferlist +[ + Exposed=(Window,DedicatedWorker), +] interface SourceBufferList : EventTarget { readonly attribute unsigned long length; getter SourceBuffer? item(unsigned long index); diff --git a/cobalt/dom/url_media_source.cc b/cobalt/dom/url_media_source.cc index a0ab550794e5..10304d948077 100644 --- a/cobalt/dom/url_media_source.cc +++ b/cobalt/dom/url_media_source.cc @@ -15,6 +15,7 @@ #include "base/guid.h" #include "base/logging.h" #include "cobalt/base/polymorphic_downcast.h" +#include "cobalt/dom/cross_thread_media_source_attachment.h" #include "cobalt/dom/dom_settings.h" #include "cobalt/dom/media_source.h" #include "cobalt/dom/media_source_attachment.h" @@ -22,6 +23,8 @@ #include "cobalt/web/context.h" #include "cobalt/web/environment_settings.h" #include "cobalt/web/url.h" +#include "cobalt/web/window_or_worker_global_scope.h" +#include "cobalt/worker/worker_settings.h" #include "url/gurl.h" namespace cobalt { @@ -32,25 +35,63 @@ void RegisterMediaSourceObjectURL( script::EnvironmentSettings* environment_settings, const std::string& blob_url, const scoped_refptr& media_source) { - dom::DOMSettings* dom_settings = - base::polymorphic_downcast(environment_settings); - DCHECK(dom_settings); - DCHECK(dom_settings->media_source_registry()); + web::EnvironmentSettings* web_settings = + base::polymorphic_downcast( + environment_settings); + DCHECK(web_settings); + DCHECK(web_settings->context()); + web::WindowOrWorkerGlobalScope* global_scope = + web_settings->context()->GetWindowOrWorkerGlobalScope(); - scoped_refptr attachment = - base::MakeRefCounted(media_source); + if (global_scope->IsWorker()) { + worker::WorkerSettings* worker_settings = + base::polymorphic_downcast( + environment_settings); + DCHECK(worker_settings); + DCHECK(worker_settings->media_source_registry()); - dom_settings->media_source_registry()->Register(blob_url, attachment); + scoped_refptr attachment = + base::MakeRefCounted(media_source); + + worker_settings->media_source_registry()->Register(blob_url, attachment); + } else { + dom::DOMSettings* dom_settings = + base::polymorphic_downcast(environment_settings); + DCHECK(dom_settings); + DCHECK(dom_settings->media_source_registry()); + + scoped_refptr attachment = + base::MakeRefCounted(media_source); + + dom_settings->media_source_registry()->Register(blob_url, attachment); + } } // extern bool UnregisterMediaSourceObjectURL( script::EnvironmentSettings* environment_settings, const std::string& url) { - dom::DOMSettings* dom_settings = - base::polymorphic_downcast(environment_settings); - DCHECK(dom_settings); - DCHECK(dom_settings->media_source_registry()); - return dom_settings->media_source_registry()->Unregister(url); + web::EnvironmentSettings* web_environment_settings = + base::polymorphic_downcast( + environment_settings); + DCHECK(web_environment_settings); + DCHECK(web_environment_settings->context()); + web::WindowOrWorkerGlobalScope* global_scope = + web_environment_settings->context()->GetWindowOrWorkerGlobalScope(); + + if (global_scope->IsWorker()) { + worker::WorkerSettings* worker_settings = + base::polymorphic_downcast( + environment_settings); + DCHECK(worker_settings); + DCHECK(worker_settings->media_source_registry()); + return worker_settings->media_source_registry()->Unregister(url); + } else { + dom::DOMSettings* dom_settings = + base::polymorphic_downcast(environment_settings); + DCHECK(dom_settings); + DCHECK(dom_settings->media_source_registry()); + return dom_settings->media_source_registry()->Unregister(url); + } } } // namespace dom diff --git a/cobalt/web/url.cc b/cobalt/web/url.cc index 87092e7704d7..29a1d38ecc9a 100644 --- a/cobalt/web/url.cc +++ b/cobalt/web/url.cc @@ -17,10 +17,12 @@ #include "base/guid.h" #include "base/logging.h" #include "cobalt/base/polymorphic_downcast.h" +#include "cobalt/dom/media_settings.h" #include "cobalt/script/exception_message.h" #include "cobalt/script/exception_state.h" #include "cobalt/web/context.h" #include "cobalt/web/environment_settings.h" +#include "cobalt/web/window_or_worker_global_scope.h" #include "url/gurl.h" namespace cobalt { @@ -28,6 +30,48 @@ namespace web { namespace { const char kBlobUrlProtocol[] = "blob"; + +const dom::MediaSettings& GetMediaSettings(web::EnvironmentSettings* settings) { + DCHECK(settings); + DCHECK(settings->context()); + DCHECK(settings->context()->web_settings()); + + const auto& web_settings = settings->context()->web_settings(); + return web_settings->media_settings(); +} + +// If this function returns true, MediaSource::GetSeekable() will short-circuit +// getting the buffered range from HTMLMediaElement by directly calling to +// MediaSource::GetBufferedRange(). This reduces potential cross-object, +// cross-thread calls between MediaSource and HTMLMediaElement. +// The default value is false. +bool IsMediaElementUsingMediaSourceBufferedRangeEnabled( + web::EnvironmentSettings* settings) { + return GetMediaSettings(settings) + .IsMediaElementUsingMediaSourceBufferedRangeEnabled() + .value_or(false); +} + +// If this function returns true, communication between HTMLMediaElement and +// MediaSource objects will be fully proxied between MediaSourceAttachment. +// The default value is false. +bool IsMediaElementUsingMediaSourceAttachmentMethodsEnabled( + web::EnvironmentSettings* settings) { + return GetMediaSettings(settings) + .IsMediaElementUsingMediaSourceAttachmentMethodsEnabled() + .value_or(false); +} + +// If this function returns true, experimental support for creating MediaSource +// objects in Dedicated Workers will be enabled. This also allows MSE handles +// to be transferred from Dedicated Workers back to the main thread. +// Requires MediaElement.EnableUsingMediaSourceBufferedRange and +// MediaElement.EnableUsingMediaSourceAttachmentMethods as prerequisites for +// this feature. +// The default value is false. +bool IsMseInWorkersEnabled(web::EnvironmentSettings* settings) { + return GetMediaSettings(settings).IsMseInWorkersEnabled().value_or(false); +} } // namespace URL::URL(const std::string& url, const std::string& base, @@ -69,6 +113,29 @@ std::string URL::CreateObjectURL( return ""; } + web::EnvironmentSettings* web_settings = + base::polymorphic_downcast( + environment_settings); + DCHECK(web_settings); + DCHECK(web_settings->context()); + web::WindowOrWorkerGlobalScope* global_scope = + web_settings->context()->GetWindowOrWorkerGlobalScope(); + if (global_scope->IsWorker()) { + if (!global_scope->IsDedicatedWorker()) { + // MSE-in-Workers is only available from DedicatedWorkers, but the URL + // API is exposed to multiple types of Workers. This branch is used to + // handle Workers that don't support MSE. + return ""; + } + if (!IsMseInWorkersEnabled(web_settings) || + !IsMediaElementUsingMediaSourceAttachmentMethodsEnabled(web_settings) || + !IsMediaElementUsingMediaSourceBufferedRangeEnabled(web_settings)) { + // Prevent usage of MSE-in-Workers if any of the pre-requisites are not + // satisfied. + return ""; + } + } + std::string blob_url = kBlobUrlProtocol; blob_url += ':' + base::GenerateGUID(); dom::RegisterMediaSourceObjectURL(environment_settings, blob_url, diff --git a/cobalt/web/url_registry.h b/cobalt/web/url_registry.h index 870ed505ff39..710edd696ca7 100644 --- a/cobalt/web/url_registry.h +++ b/cobalt/web/url_registry.h @@ -20,6 +20,7 @@ #include "base/containers/hash_tables.h" #include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" #include "cobalt/script/tracer.h" namespace cobalt { @@ -43,17 +44,21 @@ class UrlRegistry : public script::Traceable { bool Unregister(const std::string& blob_url); void TraceMembers(script::Tracer* tracer) override { + base::AutoLock auto_lock(lock_); tracer->TraceValues(object_registry_); } private: typedef base::hash_map > UrlMap; - UrlMap object_registry_; + UrlMap object_registry_ GUARDED_BY(lock_); + + base::Lock lock_; }; template void UrlRegistry::Register( const std::string& blob_url, const scoped_refptr& object) { + base::AutoLock auto_lock(lock_); DCHECK(object); DCHECK(object_registry_.find(blob_url) == object_registry_.end()); object_registry_.insert(std::make_pair(blob_url, object)); @@ -62,6 +67,7 @@ void UrlRegistry::Register( template scoped_refptr UrlRegistry::Retrieve( const std::string& blob_url) { + base::AutoLock auto_lock(lock_); typename UrlMap::iterator iter = object_registry_.find(blob_url); if (iter == object_registry_.end()) { DLOG(WARNING) << "Cannot find object for blob url " << blob_url; @@ -73,6 +79,7 @@ scoped_refptr UrlRegistry::Retrieve( template bool UrlRegistry::Unregister(const std::string& blob_url) { + base::AutoLock auto_lock(lock_); typename UrlMap::iterator iter = object_registry_.find(blob_url); if (iter == object_registry_.end()) { return false; diff --git a/cobalt/worker/dedicated_worker.cc b/cobalt/worker/dedicated_worker.cc index af3e62b5614c..09f40e6e585c 100644 --- a/cobalt/worker/dedicated_worker.cc +++ b/cobalt/worker/dedicated_worker.cc @@ -18,21 +18,68 @@ #include #include "cobalt/browser/stack_size_constants.h" +#include "cobalt/dom/dom_settings.h" +#include "cobalt/dom/media_settings.h" #include "cobalt/script/environment_settings.h" +#include "cobalt/web/environment_settings.h" #include "cobalt/web/event_target.h" #include "cobalt/web/message_port.h" +#include "cobalt/web/window_or_worker_global_scope.h" #include "cobalt/worker/worker.h" #include "cobalt/worker/worker_options.h" +#include "cobalt/worker/worker_settings.h" #include "url/gurl.h" namespace cobalt { namespace worker { +namespace { +const dom::MediaSettings& GetMediaSettings(web::EnvironmentSettings* settings) { + DCHECK(settings); + DCHECK(settings->context()); + DCHECK(settings->context()->web_settings()); + const auto& web_settings = settings->context()->web_settings(); + return web_settings->media_settings(); +} +// If this function returns true, MediaSource::GetSeekable() will short-circuit +// getting the buffered range from HTMLMediaElement by directly calling to +// MediaSource::GetBufferedRange(). This reduces potential cross-object, +// cross-thread calls between MediaSource and HTMLMediaElement. +// The default value is false. +bool IsMediaElementUsingMediaSourceBufferedRangeEnabled( + web::EnvironmentSettings* settings) { + return GetMediaSettings(settings) + .IsMediaElementUsingMediaSourceBufferedRangeEnabled() + .value_or(false); +} + +// If this function returns true, communication between HTMLMediaElement and +// MediaSource objects will be fully proxied between MediaSourceAttachment. +// The default value is false. +bool IsMediaElementUsingMediaSourceAttachmentMethodsEnabled( + web::EnvironmentSettings* settings) { + return GetMediaSettings(settings) + .IsMediaElementUsingMediaSourceAttachmentMethodsEnabled() + .value_or(false); +} + +// If this function returns true, experimental support for creating MediaSource +// objects in Dedicated Workers will be enabled. This also allows MSE handles +// to be transferred from Dedicated Workers back to the main thread. +// Requires MediaElement.EnableUsingMediaSourceBufferedRange and +// MediaElement.EnableUsingMediaSourceAttachmentMethods as prerequisites for +// this feature. +// The default value is false. +bool IsMseInWorkersEnabled(web::EnvironmentSettings* settings) { + return GetMediaSettings(settings).IsMseInWorkersEnabled().value_or(false); +} +} // namespace + DedicatedWorker::DedicatedWorker(script::EnvironmentSettings* settings, const std::string& scriptURL, script::ExceptionState* exception_state) : web::EventTarget(settings), script_url_(scriptURL) { - Initialize(exception_state); + Initialize(settings, exception_state); } DedicatedWorker::DedicatedWorker(script::EnvironmentSettings* settings, @@ -42,10 +89,11 @@ DedicatedWorker::DedicatedWorker(script::EnvironmentSettings* settings, : web::EventTarget(settings), script_url_(scriptURL), worker_options_(worker_options) { - Initialize(exception_state); + Initialize(settings, exception_state); } -void DedicatedWorker::Initialize(script::ExceptionState* exception_state) { +void DedicatedWorker::Initialize(script::EnvironmentSettings* settings, + script::ExceptionState* exception_state) { // Algorithm for the Worker constructor. // https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#dom-worker @@ -108,6 +156,23 @@ void DedicatedWorker::Initialize(script::ExceptionState* exception_state) { options.construction_location.file_path = environment_settings()->creation_url().spec(); } + + web::EnvironmentSettings* web_settings = + base::polymorphic_downcast(settings); + DCHECK(web_settings); + if (IsMseInWorkersEnabled(web_settings) && + IsMediaElementUsingMediaSourceAttachmentMethodsEnabled(web_settings) && + IsMediaElementUsingMediaSourceBufferedRangeEnabled(web_settings)) { + // DedicatedWorker constructor is only callable from the main Window. + dom::DOMSettings* dom_settings = + base::polymorphic_downcast(settings); + DCHECK(dom_settings); + DCHECK(dom_settings->can_play_type_handler()); + DCHECK(dom_settings->media_source_registry()); + options.can_play_type_handler = dom_settings->can_play_type_handler(); + options.media_source_registry = dom_settings->media_source_registry(); + } + worker_.reset(new Worker(WorkerConsts::kDedicatedWorkerName, options)); // 10. Return worker. } diff --git a/cobalt/worker/dedicated_worker.h b/cobalt/worker/dedicated_worker.h index 9a88582a612a..6dbe7f93d087 100644 --- a/cobalt/worker/dedicated_worker.h +++ b/cobalt/worker/dedicated_worker.h @@ -80,7 +80,8 @@ class DedicatedWorker : public AbstractWorker, public web::EventTarget { private: ~DedicatedWorker() override; - void Initialize(script::ExceptionState* exception_state); + void Initialize(script::EnvironmentSettings* settings, + script::ExceptionState* exception_state); const std::string script_url_; const WorkerOptions worker_options_; diff --git a/cobalt/worker/worker.cc b/cobalt/worker/worker.cc index 005f67687d55..8aed3eb90f93 100644 --- a/cobalt/worker/worker.cc +++ b/cobalt/worker/worker.cc @@ -86,7 +86,9 @@ void Worker::Initialize(web::Context* context) { // . For the global object, if is shared is true, create a new // SharedWorkerGlobalScope object. Otherwise, create a new // DedicatedWorkerGlobalScope object. - WorkerSettings* worker_settings = new WorkerSettings(options_.outside_port); + WorkerSettings* worker_settings = + new WorkerSettings(options_.outside_port, options_.media_source_registry, + options_.can_play_type_handler); // From algorithm to set up a worker environment settings object // Let inherited origin be outside settings's origin. // The origin return a unique opaque origin if worker global scope's url's diff --git a/cobalt/worker/worker.h b/cobalt/worker/worker.h index 84cc1897e478..d18ea1ffe22e 100644 --- a/cobalt/worker/worker.h +++ b/cobalt/worker/worker.h @@ -28,6 +28,7 @@ #include "base/threading/thread.h" #include "cobalt/base/source_location.h" #include "cobalt/csp/content_security_policy.h" +#include "cobalt/dom/media_source_attachment.h" #include "cobalt/loader/script_loader_factory.h" #include "cobalt/script/environment_settings.h" #include "cobalt/script/execution_state.h" @@ -66,6 +67,9 @@ class Worker : public base::CurrentThread::DestructionObserver { // True if worker is a SharedWorker object, and false otherwise. bool is_shared = false; + media::CanPlayTypeHandler* can_play_type_handler = nullptr; + dom::MediaSourceAttachment::Registry* media_source_registry = nullptr; + // Parameters from 'Run a worker' step 9.1 in the spec. // https://html.spec.whatwg.org/commit-snapshots/465a6b672c703054de278b0f8133eb3ad33d93f4/#dom-worker GURL url; diff --git a/cobalt/worker/worker_settings.cc b/cobalt/worker/worker_settings.cc index 96d52ae5481d..769cddf07d66 100644 --- a/cobalt/worker/worker_settings.cc +++ b/cobalt/worker/worker_settings.cc @@ -30,8 +30,13 @@ namespace worker { WorkerSettings::WorkerSettings() : web::EnvironmentSettings() {} -WorkerSettings::WorkerSettings(web::MessagePort* message_port) - : web::EnvironmentSettings(), message_port_(message_port) {} +WorkerSettings::WorkerSettings(web::MessagePort* message_port, + MediaSourceRegistry* media_source_registry, + media::CanPlayTypeHandler* can_play_type_handler) + : web::EnvironmentSettings(), + message_port_(message_port), + media_source_registry_(media_source_registry), + can_play_type_handler_(can_play_type_handler) {} const GURL& WorkerSettings::base_url() const { // From algorithm for to setup up a worker environment settings object: diff --git a/cobalt/worker/worker_settings.h b/cobalt/worker/worker_settings.h index 3550b1945965..58a90577378e 100644 --- a/cobalt/worker/worker_settings.h +++ b/cobalt/worker/worker_settings.h @@ -15,12 +15,17 @@ #ifndef COBALT_WORKER_WORKER_SETTINGS_H_ #define COBALT_WORKER_WORKER_SETTINGS_H_ +#include "cobalt/media/can_play_type_handler.h" #include "cobalt/script/global_environment.h" #include "cobalt/script/javascript_engine.h" #include "cobalt/web/environment_settings.h" #include "cobalt/web/message_port.h" +#include "cobalt/web/url_registry.h" namespace cobalt { +namespace dom { +class MediaSourceAttachment; +} // namespace dom namespace worker { // Worker Environment Settings object as described in @@ -28,8 +33,11 @@ namespace worker { class WorkerSettings : public web::EnvironmentSettings { public: + typedef web::UrlRegistry MediaSourceRegistry; WorkerSettings(); - explicit WorkerSettings(web::MessagePort* message_port); + WorkerSettings(web::MessagePort* message_port, + MediaSourceRegistry* media_source_registry, + media::CanPlayTypeHandler* can_play_type_handler); web::MessagePort* message_port() const { return message_port_; } @@ -42,11 +50,22 @@ class WorkerSettings : public web::EnvironmentSettings { void set_origin(const loader::Origin& origin) { origin_ = origin; } loader::Origin GetOrigin() const override; + MediaSourceRegistry* media_source_registry() const { + return media_source_registry_; + } + + media::CanPlayTypeHandler* can_play_type_handler() const { + return can_play_type_handler_; + } + private: // Outer message port. web::MessagePort* message_port_ = nullptr; loader::Origin origin_; + + MediaSourceRegistry* media_source_registry_; + media::CanPlayTypeHandler* can_play_type_handler_; }; } // namespace worker