| // 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/thread_annotations.h" |
| #include "base/types/pass_key.h" |
| #include "third_party/blink/public/platform/web_time_range.h" |
| #include "third_party/blink/renderer/core/html/track/audio_track.h" |
| #include "third_party/blink/renderer/core/html/track/audio_track_list.h" |
| #include "third_party/blink/renderer/core/html/track/video_track.h" |
| #include "third_party/blink/renderer/core/html/track/video_track_list.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" |
| #include "third_party/blink/renderer/platform/wtf/threading_primitives.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: |
| // For use by Remove{Audio,Video}TracksFromMediaElements' and |
| // AddMainThread{Audio,Video}TrackToMediaElements' internal helpers. |
| enum class TrackAddRemovalType { kAudio, kVideo }; |
| |
| // 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, |
| base::PassKey<URLMediaSource>); |
| |
| // MediaSourceAttachmentSupplement, called by MSE API on worker thread. |
| // These generally require the MSE implementation to issue these calls from |
| // the target of a RunExclusively() callback to ensure thread safety: much of |
| // the MSE API implementation (such as readyState, and knowing whether or not |
| // the underlying WebSourceBuffers and WebMediaSource are still usable, since |
| // they access a main-thread-owned demuxer) needs to use RunExclusively(). |
| // Meanwhile, the main thread element could cause changes to such state (via |
| // this attachment) and therefore also require exclusion using the same |
| // |attachment_state_lock_|. |
| void NotifyDurationChanged(MediaSourceTracer* tracer, double duration) final |
| EXCLUSIVE_LOCKS_REQUIRED(attachment_state_lock_); |
| double GetRecentMediaTime(MediaSourceTracer* tracer) final |
| EXCLUSIVE_LOCKS_REQUIRED(attachment_state_lock_); |
| bool GetElementError(MediaSourceTracer* tracer) final |
| EXCLUSIVE_LOCKS_REQUIRED(attachment_state_lock_); |
| AudioTrackList* CreateAudioTrackList(MediaSourceTracer* tracer) final; |
| VideoTrackList* CreateVideoTrackList(MediaSourceTracer* tracer) final; |
| void AddAudioTrackToMediaElement(MediaSourceTracer* tracer, |
| AudioTrack* track) final; |
| void AddVideoTrackToMediaElement(MediaSourceTracer* tracer, |
| VideoTrack* track) final; |
| void RemoveAudioTracksFromMediaElement(MediaSourceTracer* tracer, |
| Vector<String> audio_ids, |
| bool enqueue_change_event) final |
| EXCLUSIVE_LOCKS_REQUIRED(attachment_state_lock_); |
| void RemoveVideoTracksFromMediaElement(MediaSourceTracer* tracer, |
| Vector<String> video_ids, |
| bool enqueue_change_event) final |
| EXCLUSIVE_LOCKS_REQUIRED(attachment_state_lock_); |
| void AddMainThreadAudioTrackToMediaElement(String id, |
| String kind, |
| String label, |
| String language, |
| bool enabled) final |
| EXCLUSIVE_LOCKS_REQUIRED(attachment_state_lock_); |
| void AddMainThreadVideoTrackToMediaElement(String id, |
| String kind, |
| String label, |
| String language, |
| bool selected) final |
| EXCLUSIVE_LOCKS_REQUIRED(attachment_state_lock_); |
| void OnMediaSourceContextDestroyed() final |
| EXCLUSIVE_LOCKS_REQUIRED(attachment_state_lock_); |
| |
| // Method meant to protect MSE API operations on worker thread MSE API from |
| // colliding with concurrently executing operations from the main thread |
| // running in this attachment. If |abort_if_not_fully_attached| is true, then |
| // conditionally runs |cb| iff the media element is still attached and has not |
| // ever issued a Close operation on this attachment, and if those conditions |
| // fail, |cb| is not run and this method returns false. If |
| // |abort_if_not_fully_attached| is false, then unconditionally runs |cb| and |
| // returns true. If run, |cb| is run synchronously while holding the |
| // attachment's internal |attachment_state_lock_|. Any return values needed by |
| // the caller from |cb| should be passed by pointer, enabling usage of this |
| // helper to provide safety while still retaining synchronous worker-thread |
| // MSE API operation. |
| bool RunExclusively(bool abort_if_not_fully_attached, |
| RunExclusivelyCB cb) final |
| LOCKS_EXCLUDED(attachment_state_lock_); |
| |
| // 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 |
| LOCKS_EXCLUDED(attachment_state_lock_); |
| void CompleteAttachingToMediaElement(MediaSourceTracer* tracer, |
| std::unique_ptr<WebMediaSource>) final |
| LOCKS_EXCLUDED(attachment_state_lock_); |
| |
| void Close(MediaSourceTracer* tracer) final |
| LOCKS_EXCLUDED(attachment_state_lock_); |
| WebTimeRanges BufferedInternal(MediaSourceTracer* tracer) const final |
| LOCKS_EXCLUDED(attachment_state_lock_); |
| WebTimeRanges SeekableInternal(MediaSourceTracer* tracer) const final |
| LOCKS_EXCLUDED(attachment_state_lock_); |
| void OnTrackChanged(MediaSourceTracer* tracer, TrackBase*) final |
| LOCKS_EXCLUDED(attachment_state_lock_); |
| |
| void OnElementTimeUpdate(double time) final |
| LOCKS_EXCLUDED(attachment_state_lock_); |
| void OnElementError() final LOCKS_EXCLUDED(attachment_state_lock_); |
| void OnElementContextDestroyed() final LOCKS_EXCLUDED(attachment_state_lock_); |
| |
| void AssertCrossThreadMutexIsAcquiredForDebugging() final |
| EXCLUSIVE_LOCKS_REQUIRED(attachment_state_lock_); |
| |
| private: |
| ~CrossThreadMediaSourceAttachment() override; |
| |
| void RemoveTracksFromMediaElementInternal(TrackAddRemovalType track_type, |
| Vector<String> track_ids, |
| bool enqueue_change_event) |
| EXCLUSIVE_LOCKS_REQUIRED(attachment_state_lock_); |
| void RemoveTracksFromMediaElementOnMainThread(TrackAddRemovalType track_type, |
| Vector<String> track_ids, |
| bool enqueue_change_event) |
| LOCKS_EXCLUDED(attachment_state_lock_); |
| |
| void AddTrackToMediaElementInternal(TrackAddRemovalType track_type, |
| String id, |
| String kind, |
| String label, |
| String language, |
| bool enable_or_select) |
| EXCLUSIVE_LOCKS_REQUIRED(attachment_state_lock_); |
| void AddTrackToMediaElementOnMainThread(TrackAddRemovalType track_type, |
| String id, |
| String kind, |
| String label, |
| String language, |
| bool enable_or_select) |
| LOCKS_EXCLUDED(attachment_state_lock_); |
| |
| void CompleteAttachingToMediaElementOnWorkerThread( |
| std::unique_ptr<WebMediaSource> web_media_source) |
| LOCKS_EXCLUDED(attachment_state_lock_); |
| |
| void CloseOnWorkerThread() LOCKS_EXCLUDED(attachment_state_lock_); |
| |
| // In this cross-thread implementation, this helper is used to verify |
| // assumption of "liveness" of the attachment while the caller holds |
| // |attachment_state_lock_| |
| // for common operations. |
| void VerifyCalledWhileContextsAliveForDebugging() const |
| EXCLUSIVE_LOCKS_REQUIRED(attachment_state_lock_); |
| |
| mutable Mutex attachment_state_lock_; |
| |
| // 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_ |
| GUARDED_BY(attachment_state_lock_); |
| |
| // Task runner for media tasks on the (dedicated worker) thread context that |
| // owns |registered_media_source_| (and |attached_media_source_| if currently |
| // attached). This is used for servicing main thread element operations that |
| // require operation in the MSE thread context. |
| // This is only valid until |media_source_context_destroyed_| becomes true. |
| scoped_refptr<base::SingleThreadTaskRunner> worker_runner_ |
| GUARDED_BY(attachment_state_lock_); |
| |
| // Task runner for media tasks on the main thread context that owns |
| // |attached_element_| when currently attached. This is used for servicing |
| // worker thread operations that require operation in the main thread context. |
| // This is only valid until |media_element_context_destroyed_| becomes true. |
| scoped_refptr<base::SingleThreadTaskRunner> main_runner_ |
| GUARDED_BY(attachment_state_lock_); |
| |
| // In addition to serving as targets for cross-thread communication during a |
| // live attachment, these two members function to keep Oilpan GC from |
| // collecting either side of the cross-thread attached HTMLME+MSE object group |
| // until explicit detachment. Unlike same-thread attachment's usage of Member |
| // tracing to detect idle unused attached groups, cross-thread idle detection |
| // is not available due to Oilpan's lack of CrossThreadMember. |
| CrossThreadPersistent<MediaSource> attached_media_source_ |
| GUARDED_BY(attachment_state_lock_); |
| CrossThreadPersistent<HTMLMediaElement> attached_element_ |
| GUARDED_BY(attachment_state_lock_); |
| |
| bool media_source_context_destroyed_ GUARDED_BY(attachment_state_lock_); |
| bool media_element_context_destroyed_ GUARDED_BY(attachment_state_lock_); |
| |
| // Set on main thread synchronous to servicing OnElementTimeUpdate(). |
| // Read on worker thread synchronous to servicing GetRecentMediaTime(). |
| // See MediaSourceAttachment::OnElementTimeUpdate() interface comments for |
| // more detail. |
| double recent_element_time_ GUARDED_BY(attachment_state_lock_); |
| |
| // Set on main thread synchronous to servicing OnElementError(). |
| // Read on worker thread synchronous to servicing GetElementError(). |
| // See MediaSourceAttachment::OnElementError() interface comments for more |
| // detail. |
| bool element_has_error_ GUARDED_BY(attachment_state_lock_); |
| |
| // TODO(https://crbug.com/878133): Handle supporting attachment-start success |
| // even if RevokeMediaSourceObjectURLOnAttach is *not* enabled, and this |
| // attachment instance (== object URL) is used sequentially for multiple |
| // attachment lifetimes. Solution could be to ship that feature always-on soon |
| // (making this scenario unsupported also in same-thread), or instead use a |
| // counter here and in any cross-thread attachment task posting to ensure that |
| // the cross-thread posted task is meant for the current attachment lifetime. |
| // For now, this flag is used to prevent sequential usage of this attachment |
| // instance to avoid this problem. (MSE-in-Workers is experimental, and |
| // RevokeMediaSourceObjectURLOnAttach is on-by-default.) |
| bool have_ever_attached_ GUARDED_BY(attachment_state_lock_); |
| |
| // TODO(https://crbug.com/878133): Similarly to |have_ever_attached_|, this |
| // member becomes true once this attachment receives a Close() call. Some |
| // operations, such as preventing usage of the underlying demuxer, or |
| // nullifying cross-thread track notifications, need to know immediately in |
| // the worker context once the asynchronous Close() call has ever occurred. If |
| // support for sequential multiple attachment lifetimes is needed (for |
| // instance, if MSE-in-Workers support is needed when |
| // RevokeMediaSourceObjectURLOnAttach is *not* enabled), then a counter-based |
| // solution may be required instead of this flag. |
| bool have_ever_started_closing_ GUARDED_BY(attachment_state_lock_); |
| |
| DISALLOW_COPY_AND_ASSIGN(CrossThreadMediaSourceAttachment); |
| }; |
| |
| } // namespace blink |
| |
| #endif // THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASOURCE_CROSS_THREAD_MEDIA_SOURCE_ATTACHMENT_H_ |