1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297
|
// 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 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/synchronization/lock.h"
#include "base/task/single_thread_task_runner.h"
#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/attachment_creation_pass_key_provider.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/platform/heap/persistent.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_copier.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.
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 callers of this constructor are restricted to those able
// to obtain an AttachmentCreationPasskeyProvider's pass key. This method is
// expected to only be called in a worker thread context. The raw pointer is
// then adopted into a scoped_refptr by the caller (e.g.,
// URLMediaSource::createObjectUrl will lead to
// MediaSourceRegistryImpl::RegisterURL doing this scoped_refptr adoption;
// separately, MediaSource::handle() does this adoption immediately.)
CrossThreadMediaSourceAttachment(MediaSource* media_source,
AttachmentCreationPassKeyProvider::PassKey);
CrossThreadMediaSourceAttachment(const CrossThreadMediaSourceAttachment&) =
delete;
CrossThreadMediaSourceAttachment& operator=(
const CrossThreadMediaSourceAttachment&) = delete;
// 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_);
base::TimeDelta 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_);
// See MediaSourceAttachmentSupplement for details. Simply, if this returns
// true, then SourceBuffer::RemovedFromMediaSource() can safely access the
// underlying demuxer, so long as the |attachment_state_lock_| is held
// continuously throughout this call and such accesses.
bool FullyAttachedOrSameThread(SourceBufferPassKey) const final
EXCLUSIVE_LOCKS_REQUIRED(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_);
void SendUpdatedInfoToMainThreadCache() 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_);
void UpdateWorkerThreadTimeCache(base::TimeDelta time)
LOCKS_EXCLUDED(attachment_state_lock_);
void HandleElementErrorOnWorkerThread()
LOCKS_EXCLUDED(attachment_state_lock_);
void SendUpdatedInfoToMainThreadCacheInternal(bool has_new_duration,
double new_duration)
EXCLUSIVE_LOCKS_REQUIRED(attachment_state_lock_);
void UpdateMainThreadInfoCache(WebTimeRanges new_buffered,
WebTimeRanges new_seekable,
bool has_new_duration,
double new_duration)
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 base::Lock 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 posting cross-context information from the main thread to
// the dedicated worker worker thread that owns |registered_media_source_|
// (and |attached_media_source_| if currently attached). Rather than using
// kMediaElementEvent task-type for this, we use kPostedMessage to have
// similar scheduling priority as if the app instead postMessage'd the
// information across context while maintaining similar causality. 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 posting cross-context information from the worker thread to
// the main thread that owns |attached_element_| when currently attached.
// Rather than using kMediaElementEvent task-type for this, we use
// kPostedMessage to have similar scheduling priority as if the app instead
// postMessage'd the information across context while maintaining similar
// causality. 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_);
// Updated on worker thread as eventual result of kPostMessage-ing the time
// received in OnElementTimeUpdate() on the main thread. Read on worker thread
// synchronous to servicing GetRecentMediaTime().
// See MediaSourceAttachment::OnElementTimeUpdate() interface comments for
// more detail.
base::TimeDelta recent_element_time_ GUARDED_BY(attachment_state_lock_);
// Updated on worker thread as eventual result of kPostMessage-ing the
// notification of element error received in OnElementError() on the main
// thread. 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_);
WebTimeRanges cached_buffered_;
WebTimeRanges cached_seekable_;
};
} // namespace blink
#endif // THIRD_PARTY_BLINK_RENDERER_MODULES_MEDIASOURCE_CROSS_THREAD_MEDIA_SOURCE_ATTACHMENT_H_
|