Commit 6a4f6db0 authored by Matt Wolenetz's avatar Matt Wolenetz Committed by Commit Bot

MSE-in-Workers: Cross-thread registry with stubbed attachment

Introduces a concrete CrossThreadMediaSourceAttachment with minimum
implementation necessary to enable worker thread MediaSource object URL
creation. To enable registration of worker-thread MediaSource object
URLs, which is inherent in the createObjectURL implementation, updates
MediaSourceRegistryImpl to perform registration and unregistration while
holding a mutex in the singleton, main-thread-owned, registry instance.
Mutexes previously were thought unusable for this, but that was due to
the registry previously using an Oilpan HeapHashMap bound to the main
thread's Oilpan heap. The current registry now uses a non-Oilpan
HashMap, so any thread can update the singleton so long as such
accesses or updates are mutex-protected. Also, using cross-thread task
posting to perform these tasks led to complex races whose solution is
much simpler using the mutex approach herein. See
CrossThreadMediaSourceAttachment::Unregister() for more detail.

Includes a necessary update which makes both types of attachments manage
the registered media source in appropriate Oilpan type: the cross thread
attachment must hold that reference as CrossThreadPersistent. The URL
registry implementation already unregisters all entries created on an
execution context if that context is destroyed, so this
CrossThreadPersistent registry entry will not outlive the worker
thread's context. See PublicURLManager::ContextDestroyed() for where
that logic exists already. Rationale for not also making the
SameThreadMediaSourceAttachment use a CrossThreadPersistent type is that
such type introduces a new root in all Oilpan heaps, and resulting
performance hit can be avoided by just using regular Persistents for
same thread attachments' |registered_media_source_|.

Includes new web_tests that exercise basic worker context MediaSource
construction, object URL creation and revocation (with revocation of
worker MediaSource object URL also tested on main thread). Starting an
attachment to a worker MediaSource is also tested, but is currently
expected by the test to fail until upcoming
CrossThreadMediaSourceAttachment changes land.

BUG=878133

Change-Id: I367b6610da9aca3aca7c78f4a11f571e48afc6c8
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2407075Reviewed-by: default avatarWill Cassella <cassew@google.com>
Commit-Queue: Matthew Wolenetz <wolenetz@chromium.org>
Cr-Commit-Position: refs/heads/master@{#810002}
parent 4fd1c521
...@@ -6,6 +6,8 @@ import("//third_party/blink/renderer/modules/modules.gni") ...@@ -6,6 +6,8 @@ import("//third_party/blink/renderer/modules/modules.gni")
blink_modules_sources("mediasource") { blink_modules_sources("mediasource") {
sources = [ sources = [
"cross_thread_media_source_attachment.cc",
"cross_thread_media_source_attachment.h",
"html_video_element_media_source.cc", "html_video_element_media_source.cc",
"html_video_element_media_source.h", "html_video_element_media_source.h",
"media_source.cc", "media_source.cc",
......
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/mediasource/cross_thread_media_source_attachment.h"
#include "base/memory/scoped_refptr.h"
#include "third_party/blink/renderer/core/html/media/html_media_element.h"
#include "third_party/blink/renderer/modules/mediasource/media_source.h"
namespace blink {
CrossThreadMediaSourceAttachment::CrossThreadMediaSourceAttachment(
MediaSource* media_source,
util::PassKey<URLMediaSource> /* passkey */)
: registered_media_source_(media_source) {
// This kind of attachment can only be constructed by the worker thread.
DCHECK(!IsMainThread());
DVLOG(1) << __func__ << " this=" << this << " media_source=" << media_source;
// Verify that at construction time, refcounting of this object begins at
// precisely 1.
DCHECK(HasOneRef());
}
CrossThreadMediaSourceAttachment::~CrossThreadMediaSourceAttachment() {
DVLOG(1) << __func__ << " this=" << this;
}
void CrossThreadMediaSourceAttachment::NotifyDurationChanged(
MediaSourceTracer* tracer,
double duration) {
// Called only by the MSE API on worker thread.
DCHECK(!IsMainThread());
DVLOG(1) << __func__ << " this=" << this;
// TODO(https://crbug.com/878133): Implement cross-thread behavior for this.
NOTIMPLEMENTED();
}
double CrossThreadMediaSourceAttachment::GetRecentMediaTime(
MediaSourceTracer* tracer) {
// Called only by the MSE API on worker thread.
DCHECK(!IsMainThread());
DVLOG(1) << __func__ << " this=" << this;
// TODO(https://crbug.com/878133): Implement cross-thread behavior for this.
NOTIMPLEMENTED();
return 0.0;
}
bool CrossThreadMediaSourceAttachment::GetElementError(
MediaSourceTracer* tracer) {
// Called only by the MSE API on worker thread.
DCHECK(!IsMainThread());
DVLOG(1) << __func__ << " this=" << this;
// TODO(https://crbug.com/878133): Implement cross-thread behavior for this.
NOTIMPLEMENTED();
return true;
}
void CrossThreadMediaSourceAttachment::Unregister() {
DVLOG(1) << __func__ << " this=" << this
<< ", IsMainThread=" << IsMainThread();
// The only expected caller is a MediaSourceRegistryImpl on the main thread
// (or possibly on the worker thread, if MediaSourceInWorkers is enabled).
DCHECK(IsMainThread() ||
RuntimeEnabledFeatures::MediaSourceInWorkersEnabled());
// Release our strong reference to the MediaSource. Note that revokeObjectURL
// of the url associated with this attachment could commonly follow this path
// while the MediaSource (and any attachment to an HTMLMediaElement) may still
// be alive/active. Also note that |registered_media_source_| could be
// incorrectly cleared already if its owner's execution context destruction
// has completed without notifying us, hence careful locking in
// MediaSourceRegistryImpl around this scenario, and allowance for us to be
// called on the worker context. Locking there instead of cross-thread posting
// to the main thread to reach us enables stability in cases where worker's
// context destruction or explicit object URL revocation from worker context
// races attempted usage of the object URL (or |registered_media_source_|
// here).
DCHECK(registered_media_source_);
registered_media_source_ = nullptr;
}
MediaSourceTracer*
CrossThreadMediaSourceAttachment::StartAttachingToMediaElement(
HTMLMediaElement* element,
bool* success) {
// Called only by the media element on main thread.
DCHECK(IsMainThread());
DCHECK(success);
// TODO(https://crbug.com/878133): Implement cross-thread behavior for this.
NOTIMPLEMENTED();
*success = false;
return nullptr;
}
void CrossThreadMediaSourceAttachment::CompleteAttachingToMediaElement(
MediaSourceTracer* tracer,
std::unique_ptr<WebMediaSource> web_media_source) {
// Called only by the media element on main thread.
DCHECK(IsMainThread());
// TODO(https://crbug.com/878133): Implement cross-thread behavior for this.
NOTIMPLEMENTED();
}
void CrossThreadMediaSourceAttachment::Close(MediaSourceTracer* tracer) {
// Called only by the media element on main thread.
DCHECK(IsMainThread());
// TODO(https://crbug.com/878133): Implement cross-thread behavior for this.
NOTIMPLEMENTED();
}
WebTimeRanges CrossThreadMediaSourceAttachment::BufferedInternal(
MediaSourceTracer* tracer) const {
// Called only by the media element on main thread.
DCHECK(IsMainThread());
// TODO(https://crbug.com/878133): Implement cross-thread behavior for this.
NOTIMPLEMENTED();
return {};
}
WebTimeRanges CrossThreadMediaSourceAttachment::SeekableInternal(
MediaSourceTracer* tracer) const {
// Called only by the media element on main thread.
DCHECK(IsMainThread());
// TODO(https://crbug.com/878133): Implement cross-thread behavior for this.
NOTIMPLEMENTED();
return {};
}
void CrossThreadMediaSourceAttachment::OnTrackChanged(MediaSourceTracer* tracer,
TrackBase* track) {
// Called only by the media element on main thread.
DCHECK(IsMainThread());
// TODO(https://crbug.com/878133): Implement cross-thread behavior for this.
NOTIMPLEMENTED();
}
void CrossThreadMediaSourceAttachment::OnElementTimeUpdate(double time) {
// Called only by the media element on main thread.
DCHECK(IsMainThread());
DVLOG(3) << __func__ << " this=" << this << ", time=" << time;
// TODO(https://crbug.com/878133): Implement cross-thread behavior for this.
NOTIMPLEMENTED();
}
void CrossThreadMediaSourceAttachment::OnElementError() {
// Called only by the media element on main thread.
DCHECK(IsMainThread());
DVLOG(3) << __func__ << " this=" << this;
// TODO(https://crbug.com/878133): Implement cross-thread behavior for this.
NOTIMPLEMENTED();
}
void CrossThreadMediaSourceAttachment::OnElementContextDestroyed() {
// Called only by the media element on main thread.
DCHECK(IsMainThread());
DVLOG(3) << __func__ << " this=" << this;
// TODO(https://crbug.com/878133): Implement cross-thread behavior for this.
NOTIMPLEMENTED();
}
void CrossThreadMediaSourceAttachment::OnMediaSourceContextDestroyed() {
// Called only by the MSE API on worker thread.
DCHECK(!IsMainThread());
DVLOG(3) << __func__ << " this=" << this;
// TODO(https://crbug.com/878133): Implement cross-thread behavior for this.
NOTIMPLEMENTED();
}
} // namespace blink
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASOURCE_CROSS_THREAD_MEDIA_SOURCE_ATTACHMENT_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASOURCE_CROSS_THREAD_MEDIA_SOURCE_ATTACHMENT_H_
#include <memory>
#include "base/util/type_safety/pass_key.h"
#include "third_party/blink/public/platform/web_time_range.h"
#include "third_party/blink/renderer/modules/mediasource/media_source.h"
#include "third_party/blink/renderer/modules/mediasource/media_source_attachment_supplement.h"
#include "third_party/blink/renderer/modules/mediasource/url_media_source.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
namespace blink {
// Concrete attachment that supports operation between a media element on the
// main thread and the MSE API on a dedicated worker thread.
// TODO(https://crbug.com/878133): Implement this more fully. Currently it is
// implementing only the constructor, necessary for cross-thread registry
// implementation and basic verification.
class CrossThreadMediaSourceAttachment final
: public MediaSourceAttachmentSupplement {
public:
// The only intended caller of this constructor is
// URLMediaSource::createObjectUrl (as shown by using the PassKey), executing
// in the worker thread context. The raw pointer is then adopted into a
// scoped_refptr in MediaSourceRegistryImpl::RegisterURL.
CrossThreadMediaSourceAttachment(MediaSource* media_source,
util::PassKey<URLMediaSource>);
// MediaSourceAttachmentSupplement, called by MSE API on worker thread.
void NotifyDurationChanged(MediaSourceTracer* tracer, double duration) final;
double GetRecentMediaTime(MediaSourceTracer* tracer) final;
bool GetElementError(MediaSourceTracer* tracer) final;
void OnMediaSourceContextDestroyed() final;
// MediaSourceAttachment methods called on main thread by media element,
// except Unregister is called on either main or dedicated worker thread by
// MediaSourceRegistryImpl.
void Unregister() final;
MediaSourceTracer* StartAttachingToMediaElement(HTMLMediaElement*,
bool* success) final;
void CompleteAttachingToMediaElement(MediaSourceTracer* tracer,
std::unique_ptr<WebMediaSource>) final;
void Close(MediaSourceTracer* tracer) final;
WebTimeRanges BufferedInternal(MediaSourceTracer* tracer) const final;
WebTimeRanges SeekableInternal(MediaSourceTracer* tracer) const final;
void OnTrackChanged(MediaSourceTracer* tracer, TrackBase*) final;
void OnElementTimeUpdate(double time) final;
void OnElementError() final;
void OnElementContextDestroyed() final;
private:
~CrossThreadMediaSourceAttachment() override;
// Cache of the registered worker-thread MediaSource. Retains strong reference
// on all Oilpan heaps, from construction of this object until Unregister() is
// called. This lets the main thread successfully attach (modulo normal
// reasons why StartAttaching..() can fail) to the worker-thread MediaSource
// even if there were no other strong references other than this one on the
// worker-thread Oilpan heap to the MediaSource.
CrossThreadPersistent<MediaSource> registered_media_source_;
DISALLOW_COPY_AND_ASSIGN(CrossThreadMediaSourceAttachment);
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASOURCE_CROSS_THREAD_MEDIA_SOURCE_ATTACHMENT_H_
...@@ -43,12 +43,10 @@ namespace { ...@@ -43,12 +43,10 @@ namespace {
enum class MseExecutionContext { enum class MseExecutionContext {
kWindow = 0, kWindow = 0,
// TODO(wolenetz): Support MSE usage in dedicated workers. See
// https://crbug.com/878133.
kDedicatedWorker = 1, kDedicatedWorker = 1,
// TODO(wolenetz): Consider supporting MSE usage in SharedWorkers. See // TODO(https://crbug.com/1054566): Consider supporting MSE usage in
// https://crbug.com/1054566. // SharedWorkers.
kSharedWorker = 2, kSharedWorker = 2,
kMaxValue = kSharedWorker kMaxValue = kSharedWorker
}; };
...@@ -134,12 +132,11 @@ MediaSource::MediaSource(ExecutionContext* context) ...@@ -134,12 +132,11 @@ MediaSource::MediaSource(ExecutionContext* context)
} }
base::UmaHistogramEnumeration("Media.MSE.ExecutionContext", type); base::UmaHistogramEnumeration("Media.MSE.ExecutionContext", type);
// TODO(wolenetz): Actually enable experimental usage of MediaSource API from // TODO(https://crbug.com/1054566): Also consider supporting experimental
// dedicated worker contexts. See https://crbug.com/878133. // usage of MediaSource API from shared worker contexts. Meanwhile, IDL limits
// TODO(wolenetz): Also consider supporting experimental usage of MediaSource // constructor exposure to not include shared worker.
// API from shared worker contexts. See https://crbug.com/1054566. CHECK_NE(type, MseExecutionContext::kSharedWorker)
CHECK(type == MseExecutionContext::kWindow) << "MSE is not supported from SharedWorkers";
<< "MSE is not yet supported from workers";
} }
MediaSource::~MediaSource() { MediaSource::~MediaSource() {
...@@ -888,6 +885,7 @@ bool MediaSource::HasPendingActivity() const { ...@@ -888,6 +885,7 @@ bool MediaSource::HasPendingActivity() const {
} }
void MediaSource::ContextDestroyed() { void MediaSource::ContextDestroyed() {
DVLOG(1) << __func__ << " this=" << this;
if (media_source_attachment_) if (media_source_attachment_)
media_source_attachment_->OnMediaSourceContextDestroyed(); media_source_attachment_->OnMediaSourceContextDestroyed();
if (!IsClosed()) if (!IsClosed())
......
...@@ -4,29 +4,10 @@ ...@@ -4,29 +4,10 @@
#include "third_party/blink/renderer/modules/mediasource/media_source_attachment_supplement.h" #include "third_party/blink/renderer/modules/mediasource/media_source_attachment_supplement.h"
#include "third_party/blink/renderer/modules/mediasource/media_source.h"
#include "third_party/blink/renderer/modules/mediasource/same_thread_media_source_tracer.h"
namespace blink { namespace blink {
MediaSourceAttachmentSupplement::MediaSourceAttachmentSupplement( MediaSourceAttachmentSupplement::MediaSourceAttachmentSupplement() = default;
MediaSource* media_source)
: registered_media_source_(media_source) {}
MediaSourceAttachmentSupplement::~MediaSourceAttachmentSupplement() = default; MediaSourceAttachmentSupplement::~MediaSourceAttachmentSupplement() = default;
void MediaSourceAttachmentSupplement::Unregister() {
DVLOG(1) << __func__ << " this=" << this;
// The only expected caller is a MediaSourceRegistryImpl on the main thread.
DCHECK(IsMainThread());
// Release our strong reference to the MediaSource. Note that revokeObjectURL
// of the url associated with this attachment could commonly follow this path
// while the MediaSource (and any attachment to an HTMLMediaElement) may still
// be alive/active.
DCHECK(registered_media_source_);
registered_media_source_ = nullptr;
}
} // namespace blink } // namespace blink
...@@ -9,7 +9,6 @@ ...@@ -9,7 +9,6 @@
#include "third_party/blink/renderer/core/html/media/media_source_tracer.h" #include "third_party/blink/renderer/core/html/media/media_source_tracer.h"
#include "third_party/blink/renderer/modules/mediasource/media_source.h" #include "third_party/blink/renderer/modules/mediasource/media_source.h"
#include "third_party/blink/renderer/platform/heap/handle.h" #include "third_party/blink/renderer/platform/heap/handle.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/wtf/forward.h" #include "third_party/blink/renderer/platform/wtf/forward.h"
namespace blink { namespace blink {
...@@ -48,17 +47,10 @@ class MediaSourceAttachmentSupplement : public MediaSourceAttachment { ...@@ -48,17 +47,10 @@ class MediaSourceAttachmentSupplement : public MediaSourceAttachment {
virtual void OnMediaSourceContextDestroyed() = 0; virtual void OnMediaSourceContextDestroyed() = 0;
// MediaSourceAttachment
void Unregister() final;
protected: protected:
explicit MediaSourceAttachmentSupplement(MediaSource* media_source); MediaSourceAttachmentSupplement();
~MediaSourceAttachmentSupplement() override; ~MediaSourceAttachmentSupplement() override;
// Cache of the registered MediaSource. Retains strong reference from
// construction of this object until Unregister() is called.
Persistent<MediaSource> registered_media_source_;
DISALLOW_COPY_AND_ASSIGN(MediaSourceAttachmentSupplement); DISALLOW_COPY_AND_ASSIGN(MediaSourceAttachmentSupplement);
}; };
......
...@@ -30,7 +30,10 @@ ...@@ -30,7 +30,10 @@
#include "third_party/blink/renderer/modules/mediasource/media_source_registry_impl.h" #include "third_party/blink/renderer/modules/mediasource/media_source_registry_impl.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h" #include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/functional.h"
#include "third_party/blink/renderer/platform/wtf/wtf.h"
namespace blink { namespace blink {
...@@ -44,24 +47,28 @@ void MediaSourceRegistryImpl::Init() { ...@@ -44,24 +47,28 @@ void MediaSourceRegistryImpl::Init() {
void MediaSourceRegistryImpl::RegisterURL(SecurityOrigin*, void MediaSourceRegistryImpl::RegisterURL(SecurityOrigin*,
const KURL& url, const KURL& url,
URLRegistrable* registrable) { URLRegistrable* registrable) {
// TODO(https://crbug.com/878133): Allow dedicated workers to register MutexLocker lock(map_mutex_);
// MediaSource objectUrls, too.
DCHECK(IsMainThread()); DCHECK(IsMainThread() ||
RuntimeEnabledFeatures::MediaSourceInWorkersEnabled());
DCHECK_EQ(&registrable->Registry(), this); DCHECK_EQ(&registrable->Registry(), this);
DCHECK(!url.IsEmpty()); // Caller of interface should already enforce this. DCHECK(!url.IsEmpty()); // Caller of interface should already enforce this.
DVLOG(1) << __func__ << " url=" << url; DVLOG(1) << __func__ << " url=" << url << ", IsMainThread=" << IsMainThread();
scoped_refptr<MediaSourceAttachment> attachment = scoped_refptr<MediaSourceAttachment> attachment =
base::AdoptRef(static_cast<MediaSourceAttachment*>(registrable)); base::AdoptRef(static_cast<MediaSourceAttachment*>(registrable));
media_sources_.Set(url.GetString(), std::move(attachment)); media_sources_.Set(url.GetString(), std::move(attachment));
} }
void MediaSourceRegistryImpl::UnregisterURL(const KURL& url) { void MediaSourceRegistryImpl::UnregisterURL(const KURL& url) {
DVLOG(1) << __func__ << " url=" << url; MutexLocker lock(map_mutex_);
// TODO(https://crbug.com/878133): Allow dedicated workers to unregister
// MediaSource objectUrls, too. DVLOG(1) << __func__ << " url=" << url << ", IsMainThread=" << IsMainThread();
DCHECK(IsMainThread()); DCHECK(IsMainThread() ||
RuntimeEnabledFeatures::MediaSourceInWorkersEnabled());
DCHECK(!url.IsEmpty()); // Caller of interface should already enforce this. DCHECK(!url.IsEmpty()); // Caller of interface should already enforce this.
auto iter = media_sources_.find(url.GetString()); auto iter = media_sources_.find(url.GetString());
...@@ -75,6 +82,8 @@ void MediaSourceRegistryImpl::UnregisterURL(const KURL& url) { ...@@ -75,6 +82,8 @@ void MediaSourceRegistryImpl::UnregisterURL(const KURL& url) {
scoped_refptr<MediaSourceAttachment> MediaSourceRegistryImpl::LookupMediaSource( scoped_refptr<MediaSourceAttachment> MediaSourceRegistryImpl::LookupMediaSource(
const String& url) { const String& url) {
MutexLocker lock(map_mutex_);
DCHECK(IsMainThread()); DCHECK(IsMainThread());
DCHECK(!url.IsEmpty()); DCHECK(!url.IsEmpty());
return media_sources_.at(url); return media_sources_.at(url);
......
...@@ -12,18 +12,15 @@ ...@@ -12,18 +12,15 @@
#include "third_party/blink/renderer/platform/heap/persistent.h" #include "third_party/blink/renderer/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/wtf/hash_map.h" #include "third_party/blink/renderer/platform/wtf/hash_map.h"
#include "third_party/blink/renderer/platform/wtf/text/string_hash.h" #include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
#include "third_party/blink/renderer/platform/wtf/threading_primitives.h"
namespace blink { namespace blink {
class KURL; class KURL;
// This singleton lives on the main thread. It allows registration and // This singleton lives on the main thread. It allows registration and
// deregistration of MediaSource objectUrls. Lookups to retrieve a reference to // deregistration of MediaSource objectUrls from both main and dedicated worker
// a registered MediaSource by its objectUrl are only allowed on the main // threads, internally locking to access or update |media_sources_| coherently.
// thread; the only intended Lookup() caller is invoked by HTMLMediaElement's
// MSE attachment during element load.
// TODO(https://crbug.com/878133): Refactor this to allow registration and
// lookup of cross-thread (worker) MediaSource objectUrls.
class MediaSourceRegistryImpl final : public MediaSourceRegistry { class MediaSourceRegistryImpl final : public MediaSourceRegistry {
public: public:
// Creates the singleton instance. Must be run on the main thread (expected to // Creates the singleton instance. Must be run on the main thread (expected to
...@@ -32,20 +29,29 @@ class MediaSourceRegistryImpl final : public MediaSourceRegistry { ...@@ -32,20 +29,29 @@ class MediaSourceRegistryImpl final : public MediaSourceRegistry {
static void Init(); static void Init();
// MediaSourceRegistry : URLRegistry overrides for (un)registering blob URLs // MediaSourceRegistry : URLRegistry overrides for (un)registering blob URLs
// referring to the specified media source attachment. RegisterURL creates a // referring to the specified media source attachment, potentially
// scoped_refptr to manage the registrable's ref-counted lifetime and puts it // cross-thread. RegisterURL creates a scoped_refptr to manage the
// in |media_sources_|. // registrable's ref-counted lifetime and puts it in |media_sources_|. Can be
void RegisterURL(SecurityOrigin*, const KURL&, URLRegistrable*) override; // called from either the main thread or a dedicated worker thread.
// Regardless, must be called on the thread which created the URLRegistrable
// (the MediaSourceAttachment).
void RegisterURL(SecurityOrigin*, const KURL&, URLRegistrable*) override
LOCKS_EXCLUDED(map_mutex_);
// UnregisterURL removes the corresponding scoped_refptr and KURL from // UnregisterURL removes the corresponding scoped_refptr and KURL from
// |media_sources_| if its KURL was there. // |media_sources_| if its KURL was there. Can be called from either the main
void UnregisterURL(const KURL&) override; // thread (explicit revocation or automatic revocation on attachment success)
// or from a worker thread (explicit revocation on worker context or worker
// context destruction).
void UnregisterURL(const KURL&) override LOCKS_EXCLUDED(map_mutex_);
// MediaSourceRegistry override that finds |url| in |media_sources_| and // MediaSourceRegistry override that finds |url| in |media_sources_| and
// returns the corresponding scoped_refptr if found. Otherwise, returns an // returns the corresponding scoped_refptr if found. Otherwise, returns an
// unset scoped_refptr. |url| must be non-empty. // unset scoped_refptr. |url| must be non-empty. Even with
// MediaSourceInWorkers feature, this must only be called on the main thread
// (typically for attachment of MediaSource to an HTMLMediaElement).
scoped_refptr<MediaSourceAttachment> LookupMediaSource( scoped_refptr<MediaSourceAttachment> LookupMediaSource(
const String& url) override; const String& url) override LOCKS_EXCLUDED(map_mutex_);
private: private:
// Construction of this singleton informs MediaSourceAttachment of this // Construction of this singleton informs MediaSourceAttachment of this
...@@ -53,7 +59,9 @@ class MediaSourceRegistryImpl final : public MediaSourceRegistry { ...@@ -53,7 +59,9 @@ class MediaSourceRegistryImpl final : public MediaSourceRegistry {
// this registry like lookup, registration and unregistration. // this registry like lookup, registration and unregistration.
MediaSourceRegistryImpl(); MediaSourceRegistryImpl();
HashMap<String, scoped_refptr<MediaSourceAttachment>> media_sources_; mutable Mutex map_mutex_;
HashMap<String, scoped_refptr<MediaSourceAttachment>> media_sources_
GUARDED_BY(map_mutex_);
}; };
} // namespace blink } // namespace blink
......
...@@ -34,7 +34,7 @@ namespace blink { ...@@ -34,7 +34,7 @@ namespace blink {
SameThreadMediaSourceAttachment::SameThreadMediaSourceAttachment( SameThreadMediaSourceAttachment::SameThreadMediaSourceAttachment(
MediaSource* media_source, MediaSource* media_source,
util::PassKey<URLMediaSource> /* passkey */) util::PassKey<URLMediaSource> /* passkey */)
: MediaSourceAttachmentSupplement(media_source), : registered_media_source_(media_source),
recent_element_time_(0.0), recent_element_time_(0.0),
element_has_error_(false), element_has_error_(false),
element_context_destroyed_(false), element_context_destroyed_(false),
...@@ -95,6 +95,20 @@ bool SameThreadMediaSourceAttachment::GetElementError( ...@@ -95,6 +95,20 @@ bool SameThreadMediaSourceAttachment::GetElementError(
return current_element_error_state; return current_element_error_state;
} }
void SameThreadMediaSourceAttachment::Unregister() {
DVLOG(1) << __func__ << " this=" << this;
// The only expected caller is a MediaSourceRegistryImpl on the main thread.
DCHECK(IsMainThread());
// Release our strong reference to the MediaSource. Note that revokeObjectURL
// of the url associated with this attachment could commonly follow this path
// while the MediaSource (and any attachment to an HTMLMediaElement) may still
// be alive/active.
DCHECK(registered_media_source_);
registered_media_source_ = nullptr;
}
MediaSourceTracer* MediaSourceTracer*
SameThreadMediaSourceAttachment::StartAttachingToMediaElement( SameThreadMediaSourceAttachment::StartAttachingToMediaElement(
HTMLMediaElement* element, HTMLMediaElement* element,
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
#include "third_party/blink/renderer/modules/mediasource/media_source.h" #include "third_party/blink/renderer/modules/mediasource/media_source.h"
#include "third_party/blink/renderer/modules/mediasource/media_source_attachment_supplement.h" #include "third_party/blink/renderer/modules/mediasource/media_source_attachment_supplement.h"
#include "third_party/blink/renderer/modules/mediasource/url_media_source.h" #include "third_party/blink/renderer/modules/mediasource/url_media_source.h"
#include "third_party/blink/renderer/platform/heap/persistent.h"
namespace blink { namespace blink {
...@@ -22,7 +23,7 @@ class SameThreadMediaSourceAttachment final ...@@ -22,7 +23,7 @@ class SameThreadMediaSourceAttachment final
// The only intended caller of this constructor is // The only intended caller of this constructor is
// URLMediaSource::createObjectUrl, made more clear by using the PassKey. The // URLMediaSource::createObjectUrl, made more clear by using the PassKey. The
// raw pointer is then adopted into a scoped_refptr in // raw pointer is then adopted into a scoped_refptr in
// SameThreadMediaSourceRegistry::RegisterURL. // MediaSourceRegistryImpl::RegisterURL.
SameThreadMediaSourceAttachment(MediaSource* media_source, SameThreadMediaSourceAttachment(MediaSource* media_source,
util::PassKey<URLMediaSource>); util::PassKey<URLMediaSource>);
...@@ -33,16 +34,16 @@ class SameThreadMediaSourceAttachment final ...@@ -33,16 +34,16 @@ class SameThreadMediaSourceAttachment final
void OnMediaSourceContextDestroyed() final; void OnMediaSourceContextDestroyed() final;
// MediaSourceAttachment // MediaSourceAttachment
void Unregister() final;
MediaSourceTracer* StartAttachingToMediaElement(HTMLMediaElement*, MediaSourceTracer* StartAttachingToMediaElement(HTMLMediaElement*,
bool* success) override; bool* success) final;
void CompleteAttachingToMediaElement( void CompleteAttachingToMediaElement(MediaSourceTracer* tracer,
MediaSourceTracer* tracer, std::unique_ptr<WebMediaSource>) final;
std::unique_ptr<WebMediaSource>) override;
void Close(MediaSourceTracer* tracer) override; void Close(MediaSourceTracer* tracer) final;
WebTimeRanges BufferedInternal(MediaSourceTracer* tracer) const override; WebTimeRanges BufferedInternal(MediaSourceTracer* tracer) const final;
WebTimeRanges SeekableInternal(MediaSourceTracer* tracer) const override; WebTimeRanges SeekableInternal(MediaSourceTracer* tracer) const final;
void OnTrackChanged(MediaSourceTracer* tracer, TrackBase*) override; void OnTrackChanged(MediaSourceTracer* tracer, TrackBase*) final;
void OnElementTimeUpdate(double time) final; void OnElementTimeUpdate(double time) final;
void OnElementError() final; void OnElementError() final;
...@@ -56,6 +57,10 @@ class SameThreadMediaSourceAttachment final ...@@ -56,6 +57,10 @@ class SameThreadMediaSourceAttachment final
// this precondition in debug mode. // this precondition in debug mode.
void VerifyCalledWhileContextsAliveForDebugging() const; void VerifyCalledWhileContextsAliveForDebugging() const;
// Cache of the registered MediaSource. Retains strong reference from
// construction of this object until Unregister() is called.
Persistent<MediaSource> registered_media_source_;
// These are mostly used to verify correct behavior of the media element and // These are mostly used to verify correct behavior of the media element and
// media source state pumping in debug builds. In a cross-thread attachment // media source state pumping in debug builds. In a cross-thread attachment
// implementation, state like this will be relied upon for servicing the // implementation, state like this will be relied upon for servicing the
......
...@@ -34,38 +34,50 @@ ...@@ -34,38 +34,50 @@
#include "third_party/blink/renderer/core/frame/web_feature.h" #include "third_party/blink/renderer/core/frame/web_feature.h"
#include "third_party/blink/renderer/core/html/media/media_source_attachment.h" #include "third_party/blink/renderer/core/html/media/media_source_attachment.h"
#include "third_party/blink/renderer/core/url/dom_url.h" #include "third_party/blink/renderer/core/url/dom_url.h"
#include "third_party/blink/renderer/modules/mediasource/cross_thread_media_source_attachment.h"
#include "third_party/blink/renderer/modules/mediasource/media_source.h" #include "third_party/blink/renderer/modules/mediasource/media_source.h"
#include "third_party/blink/renderer/modules/mediasource/media_source_registry_impl.h" #include "third_party/blink/renderer/modules/mediasource/media_source_registry_impl.h"
#include "third_party/blink/renderer/modules/mediasource/same_thread_media_source_attachment.h" #include "third_party/blink/renderer/modules/mediasource/same_thread_media_source_attachment.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h" #include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h" #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
namespace blink { namespace blink {
// static // static
String URLMediaSource::createObjectURL(ScriptState* script_state, String URLMediaSource::createObjectURL(ScriptState* script_state,
MediaSource* source) { MediaSource* source) {
// Since WebWorkers cannot obtain MediaSource objects (yet), we should be on // Since WebWorkers previously could not obtain MediaSource objects, we should
// the main thread. // be on the main thread unless MediaSourceInWorkers is enabled and we're in a
// TODO(https://crbug.com/878133): Let DedicatedWorkers create MediaSource // dedicated worker execution context.
// object URLs. DCHECK(IsMainThread() ||
DCHECK(IsMainThread()); RuntimeEnabledFeatures::MediaSourceInWorkersEnabled());
ExecutionContext* execution_context = ExecutionContext::From(script_state); ExecutionContext* execution_context = ExecutionContext::From(script_state);
DCHECK(execution_context); DCHECK(execution_context);
DCHECK(source); DCHECK(source);
MediaSourceAttachment* attachment;
if (execution_context->IsDedicatedWorkerGlobalScope()) {
DCHECK(!IsMainThread());
// PassKey usage here ensures that only we can call the constructor.
attachment = new CrossThreadMediaSourceAttachment(source, PassKey());
} else {
// Other contexts outside of main window thread or conditionally a dedicated
// worker thread are not supported (like Shared Worker and Service Worker).
DCHECK(IsMainThread() && execution_context->IsWindow());
// PassKey usage here ensures that only we can call the constructor.
attachment = new SameThreadMediaSourceAttachment(source, PassKey());
}
UseCounter::Count(execution_context, WebFeature::kCreateObjectURLMediaSource); UseCounter::Count(execution_context, WebFeature::kCreateObjectURLMediaSource);
// This creation of a ThreadSafeRefCounted object should have a refcount of 1 // The creation of a ThreadSafeRefCounted attachment object, above, should
// immediately. It will be adopted into a scoped_refptr in // have a refcount of 1 immediately. It will be adopted into a scoped_refptr
// MediaSourceRegistryImpl::RegisterURL. See also MediaSourceAttachment (and // in MediaSourceRegistryImpl::RegisterURL. See also MediaSourceAttachment
// usage in HTMLMediaElement, MediaSourceRegistry{Impl}, and MediaSource) for // (and usage in HTMLMediaElement, MediaSourceRegistry{Impl}, and MediaSource)
// further detail. Passkey usage statically ensures that only we can call the // for further detail.
// attachment constructor.
// TODO(https://crbug.com/878133): Support creation of a cross-thread
// attachment.
MediaSourceAttachment* attachment =
new SameThreadMediaSourceAttachment(source, PassKey());
DCHECK(attachment->HasOneRef()); DCHECK(attachment->HasOneRef());
String url = DOMURL::CreatePublicURL(execution_context, attachment); String url = DOMURL::CreatePublicURL(execution_context, attachment);
......
<!DOCTYPE html>
<html>
<title>Test attachment to dedicated worker MediaSource</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<body>
<script>
async_test((t) => {
const video = document.createElement('video');
document.body.appendChild(video);
// TODO(https://crbug.com/878133): Enable attachment success by
// completing the CrossThreadAttachment implementation. Currently,
// a custom Chromium MediaError.message is confirmed.
video.onerror = t.step_func(() => {
assert_not_equals(video.error, null);
assert_equals(video.error.message, "MEDIA_ELEMENT_ERROR: Unable to attach MediaSource");
t.done();
});
let worker = new Worker("mediasource-worker-util.js");
worker.onerror = t.unreached_func("worker error");
worker.onmessage = t.step_func((e) => {
if (e.data.substr(0,6) == "Error:") {
assert_unreached("Worker error: " + e.data);
} else {
const url = e.data;
assert_true(url.match(/^blob:.+/) != null);
video.src = url;
}
});
}, "Test worker MediaSource attachment (currently should fail to attach)");
// TODO(https://crbug.com/878133): Test multiple attachments to same worker
// MediaSource racing each other: precisely one should win the race.
</script>
</body>
</html>
<!DOCTYPE html>
<html>
<title>Test MediaSource object and objectUrl creation and revocation, with MediaSource in dedicated worker</title>
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script>
async_test((t) => {
let worker = new Worker("mediasource-worker-util.js");
worker.onmessage = t.step_func((e) => {
if (e.data.substr(0,6) == "Error:") {
assert_unreached("Worker error: " + e.data);
} else {
const url = e.data;
assert_true(url.match(/^blob:.+/) != null);
URL.revokeObjectURL(url);
t.done();
}
});
}, "Test main context revocation of worker MediaSource object URL");
// Run some tests directly in another dedicated worker and get their results
// merged into those from this page.
fetch_tests_from_worker(new Worker("mediasource-worker-objecturl.js"));
</script>
</html>
importScripts("/resources/testharness.js");
test((t) => {
const ms = new MediaSource();
assert_equals(ms.readyState, "closed");
}, "MediaSource construction succeeds with initial closed readyState in dedicated worker");
test((t) => {
const ms = new MediaSource();
const url = URL.createObjectURL(ms);
assert_true(url != null);
assert_true(url.match(/^blob:.+/) != null);
}, "URL.createObjectURL(mediaSource) in dedicated worker returns a Blob URI");
test((t) => {
const ms = new MediaSource();
const url1 = URL.createObjectURL(ms);
const url2 = URL.createObjectURL(ms);
URL.revokeObjectURL(url1);
URL.revokeObjectURL(url2);
}, "URL.revokeObjectURL(mediaSource) in dedicated worker with two url for same MediaSource");
done();
if (!this.MediaSource)
postMessage("Error: MediaSource API missing from Worker");
let mediaSource = new MediaSource();
let mediaSourceObjectUrl = URL.createObjectURL(mediaSource);
postMessage(mediaSourceObjectUrl);
let sourceBuffer;
let foundSupportedMedia = false;
let mediaMetadata;
let mediaLoad;
// Find supported test media, if any.
let MEDIA_LIST = [
{
url: 'mp4/test.mp4',
type: 'video/mp4; codecs="mp4a.40.2,avc1.4d400d"',
},
{
url: 'webm/test.webm',
type: 'video/webm; codecs="vp8, vorbis"',
},
];
for (let i = 0; i < MEDIA_LIST.length; ++i) {
mediaMetadata = MEDIA_LIST[i];
if (this.MediaSource && MediaSource.isTypeSupported(mediaMetadata.type)) {
foundSupportedMedia = true;
break;
}
}
function loadBinaryAsync(url) {
return new Promise((resolve, reject) => {
let request = new XMLHttpRequest();
request.open("GET", url, true);
request.responseType = 'arraybuffer';
request.onerror = (event) => { reject(event); };
request.onload = () => {
if (request.status != 200) {
reject("Unexpected loadData_ status code : " + request.status);
}
let response = new Uint8Array(request.response);
resolve(response);
};
request.send();
});
}
if (foundSupportedMedia) {
mediaLoad = loadBinaryAsync(mediaMetadata.url);
} else {
postMessage("Error: No supported test media");
}
onmessage = function(evt) {
postMessage("Error: No message expected by Worker");
};
// TODO(https://crbug.com/878133): Enable this path by completing the
// CrossThreadMediaSourceAttachment implementation such that attachment can
// actually succeed and 'sourceopen' be dispatched.
mediaSource.addEventListener("sourceopen", () => {
URL.revokeObjectURL(mediaSourceObjectUrl);
sourceBuffer = mediaSource.addSourceBuffer(mediaMetadata.type);
sourceBuffer.onerror = (err) => {
postMessage("Error: " + err);
};
sourceBuffer.onupdateend = () => {
// Shorten the buffered media and test playback duration to avoid timeouts.
sourceBuffer.remove(0.5, Infinity);
sourceBuffer.onupdateend = () => {
sourceBuffer.duration = 0.5;
mediaSource.endOfStream();
};
};
mediaLoad.then( (mediaData) => { sourceBuffer.appendBuffer(mediaData); },
(err) => { postMessage("Error: " + err) } );
}, { once : true });
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment