Bug 1795723 - Unified extensions UI should support High Contrast Mode. r=ayeddi,deskt...
[gecko.git] / dom / html / HTMLMediaElement.cpp
blob22d6c24146e4806596493c0b01a46f5824398010
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #ifdef XP_WIN
8 # include "objbase.h"
9 #endif
11 #include "mozilla/dom/HTMLMediaElement.h"
13 #include <unordered_map>
15 #include "AudioDeviceInfo.h"
16 #include "AudioStreamTrack.h"
17 #include "AutoplayPolicy.h"
18 #include "ChannelMediaDecoder.h"
19 #include "CrossGraphPort.h"
20 #include "DOMMediaStream.h"
21 #include "DecoderDoctorDiagnostics.h"
22 #include "DecoderDoctorLogger.h"
23 #include "DecoderTraits.h"
24 #include "FrameStatistics.h"
25 #include "GMPCrashHelper.h"
26 #include "GVAutoplayPermissionRequest.h"
27 #ifdef MOZ_ANDROID_HLS_SUPPORT
28 # include "HLSDecoder.h"
29 #endif
30 #include "HTMLMediaElement.h"
31 #include "ImageContainer.h"
32 #include "Layers.h"
33 #include "MP4Decoder.h"
34 #include "MediaContainerType.h"
35 #include "MediaError.h"
36 #include "MediaManager.h"
37 #include "MediaMetadataManager.h"
38 #include "MediaResource.h"
39 #include "MediaShutdownManager.h"
40 #include "MediaSourceDecoder.h"
41 #include "MediaStreamError.h"
42 #include "MediaTrackGraphImpl.h"
43 #include "MediaTrackListener.h"
44 #include "MediaStreamWindowCapturer.h"
45 #include "MediaTrack.h"
46 #include "MediaTrackList.h"
47 #include "Navigator.h"
48 #include "TimeRanges.h"
49 #include "VideoFrameContainer.h"
50 #include "VideoOutput.h"
51 #include "VideoStreamTrack.h"
52 #include "base/basictypes.h"
53 #include "jsapi.h"
54 #include "js/PropertyAndElement.h" // JS_DefineProperty
55 #include "mozilla/ArrayUtils.h"
56 #include "mozilla/AsyncEventDispatcher.h"
57 #include "mozilla/EMEUtils.h"
58 #include "mozilla/EventDispatcher.h"
59 #include "mozilla/FloatingPoint.h"
60 #include "mozilla/MathAlgorithms.h"
61 #include "mozilla/NotNull.h"
62 #include "mozilla/Preferences.h"
63 #include "mozilla/PresShell.h"
64 #include "mozilla/ScopeExit.h"
65 #include "mozilla/Sprintf.h"
66 #include "mozilla/StaticPrefs_media.h"
67 #include "mozilla/SVGObserverUtils.h"
68 #include "mozilla/Telemetry.h"
69 #include "mozilla/dom/AudioTrack.h"
70 #include "mozilla/dom/AudioTrackList.h"
71 #include "mozilla/dom/BlobURLProtocolHandler.h"
72 #include "mozilla/dom/ContentMediaController.h"
73 #include "mozilla/dom/ElementInlines.h"
74 #include "mozilla/dom/FeaturePolicyUtils.h"
75 #include "mozilla/dom/HTMLAudioElement.h"
76 #include "mozilla/dom/HTMLInputElement.h"
77 #include "mozilla/dom/HTMLMediaElementBinding.h"
78 #include "mozilla/dom/HTMLSourceElement.h"
79 #include "mozilla/dom/HTMLVideoElement.h"
80 #include "mozilla/dom/MediaControlUtils.h"
81 #include "mozilla/dom/MediaDevices.h"
82 #include "mozilla/dom/MediaEncryptedEvent.h"
83 #include "mozilla/dom/MediaErrorBinding.h"
84 #include "mozilla/dom/MediaSource.h"
85 #include "mozilla/dom/PlayPromise.h"
86 #include "mozilla/dom/Promise.h"
87 #include "mozilla/dom/TextTrack.h"
88 #include "mozilla/dom/UserActivation.h"
89 #include "mozilla/dom/VideoPlaybackQuality.h"
90 #include "mozilla/dom/VideoTrack.h"
91 #include "mozilla/dom/VideoTrackList.h"
92 #include "mozilla/dom/WakeLock.h"
93 #include "mozilla/dom/WindowGlobalChild.h"
94 #include "mozilla/dom/power/PowerManagerService.h"
95 #include "mozilla/net/UrlClassifierFeatureFactory.h"
96 #include "nsAttrValueInlines.h"
97 #include "nsContentPolicyUtils.h"
98 #include "nsContentUtils.h"
99 #include "nsCycleCollectionParticipant.h"
100 #include "nsDisplayList.h"
101 #include "nsDocShell.h"
102 #include "nsError.h"
103 #include "nsGenericHTMLElement.h"
104 #include "nsGkAtoms.h"
105 #include "nsIAsyncVerifyRedirectCallback.h"
106 #include "nsICachingChannel.h"
107 #include "nsIClassOfService.h"
108 #include "nsIContentPolicy.h"
109 #include "nsIDocShell.h"
110 #include "mozilla/dom/Document.h"
111 #include "nsIFrame.h"
112 #include "nsIHttpChannel.h"
113 #include "nsIObserverService.h"
114 #include "nsIRequest.h"
115 #include "nsIScriptError.h"
116 #include "nsISupportsPrimitives.h"
117 #include "nsIThreadRetargetableStreamListener.h"
118 #include "nsITimer.h"
119 #include "nsJSUtils.h"
120 #include "nsLayoutUtils.h"
121 #include "nsMediaFragmentURIParser.h"
122 #include "nsMimeTypes.h"
123 #include "nsNetUtil.h"
124 #include "nsNodeInfoManager.h"
125 #include "nsPresContext.h"
126 #include "nsQueryObject.h"
127 #include "nsRange.h"
128 #include "nsSize.h"
129 #include "nsThreadUtils.h"
130 #include "nsURIHashKey.h"
131 #include "nsVideoFrame.h"
132 #include "ReferrerInfo.h"
133 #include "xpcpublic.h"
134 #include <algorithm>
135 #include <cmath>
136 #include <limits>
137 #include <type_traits>
139 mozilla::LazyLogModule gMediaElementLog("nsMediaElement");
140 mozilla::LazyLogModule gMediaElementEventsLog("nsMediaElementEvents");
142 extern mozilla::LazyLogModule gAutoplayPermissionLog;
143 #define AUTOPLAY_LOG(msg, ...) \
144 MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
146 // avoid redefined macro in unified build
147 #undef MEDIACONTROL_LOG
148 #define MEDIACONTROL_LOG(msg, ...) \
149 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
150 ("HTMLMediaElement=%p, " msg, this, ##__VA_ARGS__))
152 #undef CONTROLLER_TIMER_LOG
153 #define CONTROLLER_TIMER_LOG(element, msg, ...) \
154 MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
155 ("HTMLMediaElement=%p, " msg, element, ##__VA_ARGS__))
157 #define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg)
158 #define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg)
160 using namespace mozilla::layers;
161 using mozilla::net::nsMediaFragmentURIParser;
162 using namespace mozilla::dom::HTMLMediaElement_Binding;
164 namespace mozilla::dom {
166 using AudibleState = AudioChannelService::AudibleState;
167 using SinkInfoPromise = MediaDevices::SinkInfoPromise;
169 // Number of milliseconds between progress events as defined by spec
170 static const uint32_t PROGRESS_MS = 350;
172 // Number of milliseconds of no data before a stall event is fired as defined by
173 // spec
174 static const uint32_t STALL_MS = 3000;
176 // Used by AudioChannel for suppresssing the volume to this ratio.
177 #define FADED_VOLUME_RATIO 0.25
179 // These constants are arbitrary
180 // Minimum playbackRate for a media
181 static const double MIN_PLAYBACKRATE = 1.0 / 16;
182 // Maximum playbackRate for a media
183 static const double MAX_PLAYBACKRATE = 16.0;
185 static double ClampPlaybackRate(double aPlaybackRate) {
186 MOZ_ASSERT(aPlaybackRate >= 0.0);
188 if (aPlaybackRate == 0.0) {
189 return aPlaybackRate;
191 if (aPlaybackRate < MIN_PLAYBACKRATE) {
192 return MIN_PLAYBACKRATE;
194 if (aPlaybackRate > MAX_PLAYBACKRATE) {
195 return MAX_PLAYBACKRATE;
197 return aPlaybackRate;
200 // Media error values. These need to match the ones in MediaError.webidl.
201 static const unsigned short MEDIA_ERR_ABORTED = 1;
202 static const unsigned short MEDIA_ERR_NETWORK = 2;
203 static const unsigned short MEDIA_ERR_DECODE = 3;
204 static const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
207 * EventBlocker helps media element to postpone the event delivery by storing
208 * the event runner, and execute them once media element decides not to postpone
209 * the event delivery. If media element never resumes the event delivery, then
210 * those runner would be cancelled.
211 * For example, we postpone the event delivery when media element entering to
212 * the bf-cache.
214 class HTMLMediaElement::EventBlocker final : public nsISupports {
215 public:
216 NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL
217 NS_DECL_CYCLE_COLLECTION_CLASS(EventBlocker)
219 explicit EventBlocker(HTMLMediaElement* aElement) : mElement(aElement) {}
221 void SetBlockEventDelivery(bool aShouldBlock) {
222 MOZ_ASSERT(NS_IsMainThread());
223 if (mShouldBlockEventDelivery == aShouldBlock) {
224 return;
226 LOG_EVENT(LogLevel::Debug,
227 ("%p %s event delivery", mElement.get(),
228 mShouldBlockEventDelivery ? "block" : "unblock"));
229 mShouldBlockEventDelivery = aShouldBlock;
230 if (!mShouldBlockEventDelivery) {
231 DispatchPendingMediaEvents();
235 void PostponeEvent(nsMediaEventRunner* aRunner) {
236 MOZ_ASSERT(NS_IsMainThread());
237 // Element has been CCed, which would break the weak pointer.
238 if (!mElement) {
239 return;
241 MOZ_ASSERT(mShouldBlockEventDelivery);
242 MOZ_ASSERT(mElement);
243 LOG_EVENT(LogLevel::Debug,
244 ("%p postpone runner %s for %s", mElement.get(),
245 NS_ConvertUTF16toUTF8(aRunner->Name()).get(),
246 NS_ConvertUTF16toUTF8(aRunner->EventName()).get()));
247 mPendingEventRunners.AppendElement(aRunner);
250 void Shutdown() {
251 MOZ_ASSERT(NS_IsMainThread());
252 for (auto& runner : mPendingEventRunners) {
253 runner->Cancel();
255 mPendingEventRunners.Clear();
258 bool ShouldBlockEventDelivery() const {
259 MOZ_ASSERT(NS_IsMainThread());
260 return mShouldBlockEventDelivery;
263 size_t SizeOfExcludingThis(MallocSizeOf& aMallocSizeOf) const {
264 MOZ_ASSERT(NS_IsMainThread());
265 size_t total = 0;
266 for (const auto& runner : mPendingEventRunners) {
267 total += aMallocSizeOf(runner);
269 return total;
272 private:
273 ~EventBlocker() = default;
275 void DispatchPendingMediaEvents() {
276 MOZ_ASSERT(mElement);
277 for (auto& runner : mPendingEventRunners) {
278 LOG_EVENT(LogLevel::Debug,
279 ("%p execute runner %s for %s", mElement.get(),
280 NS_ConvertUTF16toUTF8(runner->Name()).get(),
281 NS_ConvertUTF16toUTF8(runner->EventName()).get()));
282 GetMainThreadSerialEventTarget()->Dispatch(runner.forget());
284 mPendingEventRunners.Clear();
287 WeakPtr<HTMLMediaElement> mElement;
288 bool mShouldBlockEventDelivery = false;
289 // Contains event runners which should not be run for now because we want
290 // to block all events delivery. They would be dispatched once media element
291 // decides unblocking them.
292 nsTArray<RefPtr<nsMediaEventRunner>> mPendingEventRunners;
295 NS_IMPL_CYCLE_COLLECTION(HTMLMediaElement::EventBlocker, mPendingEventRunners)
296 NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLMediaElement::EventBlocker)
297 NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLMediaElement::EventBlocker)
298 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HTMLMediaElement::EventBlocker)
299 NS_INTERFACE_MAP_ENTRY(nsISupports)
300 NS_INTERFACE_MAP_END
303 * We use MediaControlKeyListener to listen to media control key in order to
304 * play and pause media element when user press media control keys and update
305 * media's playback and audible state to the media controller.
307 * Use `Start()` to start listening event and use `Stop()` to stop listening
308 * event. In addition, notifying any change to media controller MUST be done
309 * after successfully calling `Start()`.
311 class HTMLMediaElement::MediaControlKeyListener final
312 : public ContentMediaControlKeyReceiver {
313 public:
314 NS_INLINE_DECL_REFCOUNTING(MediaControlKeyListener, override)
316 MOZ_INIT_OUTSIDE_CTOR explicit MediaControlKeyListener(
317 HTMLMediaElement* aElement)
318 : mElement(aElement) {
319 MOZ_ASSERT(NS_IsMainThread());
320 MOZ_ASSERT(aElement);
324 * Start listening to the media control keys which would make media being able
325 * to be controlled via pressing media control keys.
327 void Start() {
328 MOZ_ASSERT(NS_IsMainThread());
329 if (IsStarted()) {
330 // We have already been started, do not notify start twice.
331 return;
334 // Fail to init media agent, we are not able to notify the media controller
335 // any update and also are not able to receive media control key events.
336 if (!InitMediaAgent()) {
337 MEDIACONTROL_LOG("Failed to start due to not able to init media agent!");
338 return;
341 NotifyPlaybackStateChanged(MediaPlaybackState::eStarted);
342 // If owner has started playing before the listener starts, we should update
343 // the playing state as well. Eg. media starts inaudily and becomes audible
344 // later.
345 if (!Owner()->Paused()) {
346 NotifyMediaStartedPlaying();
348 if (StaticPrefs::media_mediacontrol_testingevents_enabled()) {
349 auto dispatcher = MakeRefPtr<AsyncEventDispatcher>(
350 Owner(), u"MozStartMediaControl"_ns, CanBubble::eYes,
351 ChromeOnlyDispatch::eYes);
352 dispatcher->PostDOMEvent();
357 * Stop listening to the media control keys which would make media not be able
358 * to be controlled via pressing media control keys. If we haven't started
359 * listening to the media control keys, then nothing would happen.
361 void StopIfNeeded() {
362 MOZ_ASSERT(NS_IsMainThread());
363 if (!IsStarted()) {
364 // We have already been stopped, do not notify stop twice.
365 return;
367 NotifyMediaStoppedPlaying();
368 NotifyPlaybackStateChanged(MediaPlaybackState::eStopped);
370 // Remove ourselves from media agent, which would stop receiving event.
371 mControlAgent->RemoveReceiver(this);
372 mControlAgent = nullptr;
375 bool IsStarted() const { return mState != MediaPlaybackState::eStopped; }
377 bool IsPlaying() const override {
378 return Owner() ? !Owner()->Paused() : false;
382 * Following methods should only be used after starting listener.
384 void NotifyMediaStartedPlaying() {
385 MOZ_ASSERT(NS_IsMainThread());
386 MOZ_ASSERT(IsStarted());
387 if (mState == MediaPlaybackState::eStarted ||
388 mState == MediaPlaybackState::ePaused) {
389 NotifyPlaybackStateChanged(MediaPlaybackState::ePlayed);
390 // If media is `inaudible` in the beginning, then we don't need to notify
391 // the state, because notifying `inaudible` should always come after
392 // notifying `audible`.
393 if (mIsOwnerAudible) {
394 NotifyAudibleStateChanged(MediaAudibleState::eAudible);
399 void NotifyMediaStoppedPlaying() {
400 MOZ_ASSERT(NS_IsMainThread());
401 MOZ_ASSERT(IsStarted());
402 if (mState == MediaPlaybackState::ePlayed) {
403 NotifyPlaybackStateChanged(MediaPlaybackState::ePaused);
404 // As media are going to be paused, so no sound is possible to be heard.
405 if (mIsOwnerAudible) {
406 NotifyAudibleStateChanged(MediaAudibleState::eInaudible);
411 // This method can be called before the listener starts, which would cache
412 // the audible state and update after the listener starts.
413 void UpdateMediaAudibleState(bool aIsOwnerAudible) {
414 MOZ_ASSERT(NS_IsMainThread());
415 if (mIsOwnerAudible == aIsOwnerAudible) {
416 return;
418 mIsOwnerAudible = aIsOwnerAudible;
419 MEDIACONTROL_LOG("Media becomes %s",
420 mIsOwnerAudible ? "audible" : "inaudible");
421 // If media hasn't started playing, it doesn't make sense to update media
422 // audible state. Therefore, in that case we would noitfy the audible state
423 // when media starts playing.
424 if (mState == MediaPlaybackState::ePlayed) {
425 NotifyAudibleStateChanged(mIsOwnerAudible
426 ? MediaAudibleState::eAudible
427 : MediaAudibleState::eInaudible);
431 void SetPictureInPictureModeEnabled(bool aIsEnabled) {
432 MOZ_ASSERT(NS_IsMainThread());
433 if (mIsPictureInPictureEnabled == aIsEnabled) {
434 return;
436 // PIP state changes might happen before the listener starts or stops where
437 // we haven't call `InitMediaAgent()` yet. Eg. Reset the PIP video's src,
438 // then cancel the PIP. In addition, not like playback and audible state
439 // which should be restricted to update via the same agent in order to keep
440 // those states correct in each `ContextMediaInfo`, PIP state can be updated
441 // through any browsing context, so we would use `ContentMediaAgent::Get()`
442 // directly to update PIP state.
443 mIsPictureInPictureEnabled = aIsEnabled;
444 if (RefPtr<IMediaInfoUpdater> updater =
445 ContentMediaAgent::Get(GetCurrentBrowsingContext())) {
446 updater->SetIsInPictureInPictureMode(mOwnerBrowsingContextId,
447 mIsPictureInPictureEnabled);
451 void HandleMediaKey(MediaControlKey aKey) override {
452 MOZ_ASSERT(NS_IsMainThread());
453 MOZ_ASSERT(IsStarted());
454 MEDIACONTROL_LOG("HandleEvent '%s'", ToMediaControlKeyStr(aKey));
455 if (aKey == MediaControlKey::Play) {
456 Owner()->Play();
457 } else if (aKey == MediaControlKey::Pause) {
458 Owner()->Pause();
459 } else {
460 MOZ_ASSERT(aKey == MediaControlKey::Stop,
461 "Not supported key for media element!");
462 Owner()->Pause();
463 StopIfNeeded();
467 void UpdateOwnerBrowsingContextIfNeeded() {
468 // Has not notified any information about the owner context yet.
469 if (!IsStarted()) {
470 return;
473 BrowsingContext* currentBC = GetCurrentBrowsingContext();
474 MOZ_ASSERT(currentBC);
475 // Still in the same browsing context, no need to update.
476 if (currentBC->Id() == mOwnerBrowsingContextId) {
477 return;
479 MEDIACONTROL_LOG("Change browsing context from %" PRIu64 " to %" PRIu64,
480 mOwnerBrowsingContextId, currentBC->Id());
481 // This situation would happen when we start a media in an original browsing
482 // context, then we move it to another browsing context, such as an iframe,
483 // so its owner browsing context would be changed. Therefore, we should
484 // reset the media status for the previous browsing context by calling
485 // `Stop()`, in which the listener would notify `ePaused` (if it's playing)
486 // and `eStop`. Then calls `Start()`, in which the listener would notify
487 // `eStart` to the new browsing context. If the media was playing before,
488 // we would also notify `ePlayed`.
489 bool wasInPlayingState = mState == MediaPlaybackState::ePlayed;
490 StopIfNeeded();
491 Start();
492 if (wasInPlayingState) {
493 NotifyMediaStartedPlaying();
497 private:
498 ~MediaControlKeyListener() = default;
500 // The media can be moved around different browsing contexts, so this context
501 // might be different from the one that we used to initialize
502 // `ContentMediaAgent`.
503 BrowsingContext* GetCurrentBrowsingContext() const {
504 // Owner has been CCed, which would break the link of the weaker pointer.
505 if (!Owner()) {
506 return nullptr;
508 nsPIDOMWindowInner* window = Owner()->OwnerDoc()->GetInnerWindow();
509 return window ? window->GetBrowsingContext() : nullptr;
512 bool InitMediaAgent() {
513 MOZ_ASSERT(NS_IsMainThread());
514 BrowsingContext* currentBC = GetCurrentBrowsingContext();
515 mControlAgent = ContentMediaAgent::Get(currentBC);
516 if (!mControlAgent) {
517 return false;
519 MOZ_ASSERT(currentBC);
520 mOwnerBrowsingContextId = currentBC->Id();
521 MEDIACONTROL_LOG("Init agent in browsing context %" PRIu64,
522 mOwnerBrowsingContextId);
523 mControlAgent->AddReceiver(this);
524 return true;
527 HTMLMediaElement* Owner() const {
528 // `mElement` would be clear during CC unlinked, but it would only happen
529 // after stopping the listener.
530 MOZ_ASSERT(mElement || !IsStarted());
531 return mElement.get();
534 void NotifyPlaybackStateChanged(MediaPlaybackState aState) {
535 MOZ_ASSERT(NS_IsMainThread());
536 MOZ_ASSERT(mControlAgent);
537 MEDIACONTROL_LOG("NotifyMediaState from state='%s' to state='%s'",
538 ToMediaPlaybackStateStr(mState),
539 ToMediaPlaybackStateStr(aState));
540 MOZ_ASSERT(mState != aState, "Should not notify same state again!");
541 mState = aState;
542 mControlAgent->NotifyMediaPlaybackChanged(mOwnerBrowsingContextId, mState);
545 void NotifyAudibleStateChanged(MediaAudibleState aState) {
546 MOZ_ASSERT(NS_IsMainThread());
547 MOZ_ASSERT(IsStarted());
548 mControlAgent->NotifyMediaAudibleChanged(mOwnerBrowsingContextId, aState);
551 MediaPlaybackState mState = MediaPlaybackState::eStopped;
552 WeakPtr<HTMLMediaElement> mElement;
553 RefPtr<ContentMediaAgent> mControlAgent;
554 bool mIsPictureInPictureEnabled = false;
555 bool mIsOwnerAudible = false;
556 MOZ_INIT_OUTSIDE_CTOR uint64_t mOwnerBrowsingContextId;
559 class HTMLMediaElement::MediaStreamTrackListener
560 : public DOMMediaStream::TrackListener {
561 public:
562 explicit MediaStreamTrackListener(HTMLMediaElement* aElement)
563 : mElement(aElement) {}
565 void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override {
566 if (!mElement) {
567 return;
569 mElement->NotifyMediaStreamTrackAdded(aTrack);
572 void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override {
573 if (!mElement) {
574 return;
576 mElement->NotifyMediaStreamTrackRemoved(aTrack);
579 void OnActive() {
580 MOZ_ASSERT(mElement);
582 // mediacapture-main says:
583 // Note that once ended equals true the HTMLVideoElement will not play media
584 // even if new MediaStreamTracks are added to the MediaStream (causing it to
585 // return to the active state) unless autoplay is true or the web
586 // application restarts the element, e.g., by calling play().
588 // This is vague on exactly how to go from becoming active to playing, when
589 // autoplaying. However, per the media element spec, to play an autoplaying
590 // media element, we must load the source and reach readyState
591 // HAVE_ENOUGH_DATA [1]. Hence, a MediaStream being assigned to a media
592 // element and becoming active runs the load algorithm, so that it can
593 // eventually be played.
595 // [1]
596 // https://html.spec.whatwg.org/multipage/media.html#ready-states:event-media-play
598 LOG(LogLevel::Debug, ("%p, mSrcStream %p became active, checking if we "
599 "need to run the load algorithm",
600 mElement.get(), mElement->mSrcStream.get()));
601 if (!mElement->IsPlaybackEnded()) {
602 return;
604 if (!mElement->Autoplay()) {
605 return;
607 LOG(LogLevel::Info, ("%p, mSrcStream %p became active on autoplaying, "
608 "ended element. Reloading.",
609 mElement.get(), mElement->mSrcStream.get()));
610 mElement->DoLoad();
613 void NotifyActive() override {
614 if (!mElement) {
615 return;
618 if (!mElement->IsVideo()) {
619 // Audio elements use NotifyAudible().
620 return;
623 OnActive();
626 void NotifyAudible() override {
627 if (!mElement) {
628 return;
631 if (mElement->IsVideo()) {
632 // Video elements use NotifyActive().
633 return;
636 OnActive();
639 void OnInactive() {
640 MOZ_ASSERT(mElement);
642 if (mElement->IsPlaybackEnded()) {
643 return;
645 LOG(LogLevel::Debug, ("%p, mSrcStream %p became inactive", mElement.get(),
646 mElement->mSrcStream.get()));
648 mElement->PlaybackEnded();
651 void NotifyInactive() override {
652 if (!mElement) {
653 return;
656 if (!mElement->IsVideo()) {
657 // Audio elements use NotifyInaudible().
658 return;
661 OnInactive();
664 void NotifyInaudible() override {
665 if (!mElement) {
666 return;
669 if (mElement->IsVideo()) {
670 // Video elements use NotifyInactive().
671 return;
674 OnInactive();
677 protected:
678 const WeakPtr<HTMLMediaElement> mElement;
682 * Helper class that manages audio and video outputs for all enabled tracks in a
683 * media element. It also manages calculating the current time when playing a
684 * MediaStream.
686 class HTMLMediaElement::MediaStreamRenderer
687 : public DOMMediaStream::TrackListener {
688 public:
689 NS_INLINE_DECL_REFCOUNTING(MediaStreamRenderer)
691 MediaStreamRenderer(AbstractThread* aMainThread,
692 VideoFrameContainer* aVideoContainer,
693 FirstFrameVideoOutput* aFirstFrameVideoOutput,
694 void* aAudioOutputKey)
695 : mVideoContainer(aVideoContainer),
696 mAudioOutputKey(aAudioOutputKey),
697 mWatchManager(this, aMainThread),
698 mFirstFrameVideoOutput(aFirstFrameVideoOutput) {
699 if (mFirstFrameVideoOutput) {
700 mWatchManager.Watch(mFirstFrameVideoOutput->mFirstFrameRendered,
701 &MediaStreamRenderer::SetFirstFrameRendered);
705 void Shutdown() {
706 for (const auto& t : mAudioTracks.Clone()) {
707 if (t) {
708 RemoveTrack(t->AsAudioStreamTrack());
711 if (mVideoTrack) {
712 RemoveTrack(mVideoTrack->AsVideoStreamTrack());
714 mWatchManager.Shutdown();
715 mFirstFrameVideoOutput = nullptr;
718 void UpdateGraphTime() {
719 mGraphTime =
720 mGraphTimeDummy->mTrack->Graph()->CurrentTime() - *mGraphTimeOffset;
723 void SetFirstFrameRendered() {
724 if (!mFirstFrameVideoOutput) {
725 return;
727 if (mVideoTrack) {
728 mVideoTrack->AsVideoStreamTrack()->RemoveVideoOutput(
729 mFirstFrameVideoOutput);
731 mWatchManager.Unwatch(mFirstFrameVideoOutput->mFirstFrameRendered,
732 &MediaStreamRenderer::SetFirstFrameRendered);
733 mFirstFrameVideoOutput = nullptr;
736 void SetProgressingCurrentTime(bool aProgress) {
737 if (aProgress == mProgressingCurrentTime) {
738 return;
741 MOZ_DIAGNOSTIC_ASSERT(mGraphTimeDummy);
742 mProgressingCurrentTime = aProgress;
743 MediaTrackGraph* graph = mGraphTimeDummy->mTrack->Graph();
744 if (mProgressingCurrentTime) {
745 mGraphTimeOffset = Some(graph->CurrentTime().Ref() - mGraphTime);
746 mWatchManager.Watch(graph->CurrentTime(),
747 &MediaStreamRenderer::UpdateGraphTime);
748 } else {
749 mWatchManager.Unwatch(graph->CurrentTime(),
750 &MediaStreamRenderer::UpdateGraphTime);
754 void Start() {
755 if (mRendering) {
756 return;
759 mRendering = true;
761 if (!mGraphTimeDummy) {
762 return;
765 for (const auto& t : mAudioTracks) {
766 if (t) {
767 t->AsAudioStreamTrack()->AddAudioOutput(mAudioOutputKey,
768 mAudioOutputSink);
769 t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey,
770 mAudioOutputVolume);
774 if (mVideoTrack) {
775 mVideoTrack->AsVideoStreamTrack()->AddVideoOutput(mVideoContainer);
779 void Stop() {
780 if (!mRendering) {
781 return;
784 mRendering = false;
786 if (!mGraphTimeDummy) {
787 return;
790 for (const auto& t : mAudioTracks) {
791 if (t) {
792 t->AsAudioStreamTrack()->RemoveAudioOutput(mAudioOutputKey);
796 if (mVideoTrack) {
797 mVideoTrack->AsVideoStreamTrack()->RemoveVideoOutput(mVideoContainer);
801 void SetAudioOutputVolume(float aVolume) {
802 if (mAudioOutputVolume == aVolume) {
803 return;
805 mAudioOutputVolume = aVolume;
806 if (!mRendering) {
807 return;
809 for (const auto& t : mAudioTracks) {
810 if (t) {
811 t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey,
812 mAudioOutputVolume);
817 RefPtr<GenericPromise::AllPromiseType> SetAudioOutputDevice(
818 AudioDeviceInfo* aSink) {
819 MOZ_ASSERT(aSink);
820 MOZ_ASSERT(mAudioOutputSink != aSink);
822 mAudioOutputSink = aSink;
824 if (!mRendering) {
825 return GenericPromise::AllPromiseType::CreateAndResolve(nsTArray<bool>(),
826 __func__);
829 nsTArray<RefPtr<GenericPromise>> promises;
830 for (const auto& t : mAudioTracks) {
831 t->AsAudioStreamTrack()->RemoveAudioOutput(mAudioOutputKey);
832 promises.AppendElement(t->AsAudioStreamTrack()->AddAudioOutput(
833 mAudioOutputKey, mAudioOutputSink));
834 t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey,
835 mAudioOutputVolume);
837 if (!promises.Length()) {
838 // Not active track, save it for later
839 return GenericPromise::AllPromiseType::CreateAndResolve(nsTArray<bool>(),
840 __func__);
843 return GenericPromise::All(GetCurrentSerialEventTarget(), promises);
846 void AddTrack(AudioStreamTrack* aTrack) {
847 MOZ_DIAGNOSTIC_ASSERT(!mAudioTracks.Contains(aTrack));
848 mAudioTracks.AppendElement(aTrack);
849 EnsureGraphTimeDummy();
850 if (mRendering) {
851 aTrack->AddAudioOutput(mAudioOutputKey, mAudioOutputSink);
852 aTrack->SetAudioOutputVolume(mAudioOutputKey, mAudioOutputVolume);
855 void AddTrack(VideoStreamTrack* aTrack) {
856 MOZ_DIAGNOSTIC_ASSERT(!mVideoTrack);
857 if (!mVideoContainer) {
858 return;
860 mVideoTrack = aTrack;
861 EnsureGraphTimeDummy();
862 if (mFirstFrameVideoOutput) {
863 // Add the first frame output even if we are rendering. It will only
864 // accept one frame. If we are rendering, then the main output will
865 // overwrite that with the same frame (and possibly more frames).
866 aTrack->AddVideoOutput(mFirstFrameVideoOutput);
868 if (mRendering) {
869 aTrack->AddVideoOutput(mVideoContainer);
873 void RemoveTrack(AudioStreamTrack* aTrack) {
874 MOZ_DIAGNOSTIC_ASSERT(mAudioTracks.Contains(aTrack));
875 if (mRendering) {
876 aTrack->RemoveAudioOutput(mAudioOutputKey);
878 mAudioTracks.RemoveElement(aTrack);
880 void RemoveTrack(VideoStreamTrack* aTrack) {
881 MOZ_DIAGNOSTIC_ASSERT(mVideoTrack == aTrack);
882 if (!mVideoContainer) {
883 return;
885 if (mFirstFrameVideoOutput) {
886 aTrack->RemoveVideoOutput(mFirstFrameVideoOutput);
888 if (mRendering) {
889 aTrack->RemoveVideoOutput(mVideoContainer);
891 mVideoTrack = nullptr;
894 double CurrentTime() const {
895 if (!mGraphTimeDummy) {
896 return 0.0;
899 return mGraphTimeDummy->mTrack->GraphImpl()->MediaTimeToSeconds(mGraphTime);
902 Watchable<GraphTime>& CurrentGraphTime() { return mGraphTime; }
904 // Set if we're rendering video.
905 const RefPtr<VideoFrameContainer> mVideoContainer;
907 // Set if we're rendering audio, nullptr otherwise.
908 void* const mAudioOutputKey;
910 private:
911 ~MediaStreamRenderer() { Shutdown(); }
913 void EnsureGraphTimeDummy() {
914 if (mGraphTimeDummy) {
915 return;
918 MediaTrackGraph* graph = nullptr;
919 for (const auto& t : mAudioTracks) {
920 if (t && !t->Ended()) {
921 graph = t->Graph();
922 break;
926 if (!graph && mVideoTrack && !mVideoTrack->Ended()) {
927 graph = mVideoTrack->Graph();
930 if (!graph) {
931 return;
934 // This dummy keeps `graph` alive and ensures access to it.
935 mGraphTimeDummy = MakeRefPtr<SharedDummyTrack>(
936 graph->CreateSourceTrack(MediaSegment::AUDIO));
939 // True when all tracks are being rendered, i.e., when the media element is
940 // playing.
941 bool mRendering = false;
943 // True while we're progressing mGraphTime. False otherwise.
944 bool mProgressingCurrentTime = false;
946 // The audio output volume for all audio tracks.
947 float mAudioOutputVolume = 1.0f;
949 // The sink device for all audio tracks.
950 RefPtr<AudioDeviceInfo> mAudioOutputSink;
952 // WatchManager for mGraphTime.
953 WatchManager<MediaStreamRenderer> mWatchManager;
955 // A dummy MediaTrack to guarantee a MediaTrackGraph is kept alive while
956 // we're actively rendering, so we can track the graph's current time. Set
957 // when the first track is added, never unset.
958 RefPtr<SharedDummyTrack> mGraphTimeDummy;
960 // Watchable that relays the graph's currentTime updates to the media element
961 // only while we're rendering. This is the current time of the rendering in
962 // GraphTime units.
963 Watchable<GraphTime> mGraphTime = {0, "MediaStreamRenderer::mGraphTime"};
965 // Nothing until a track has been added. Then, the current GraphTime at the
966 // time when we were last Start()ed.
967 Maybe<GraphTime> mGraphTimeOffset;
969 // Currently enabled (and rendered) audio tracks.
970 nsTArray<WeakPtr<MediaStreamTrack>> mAudioTracks;
972 // Currently selected (and rendered) video track.
973 WeakPtr<MediaStreamTrack> mVideoTrack;
975 // Holds a reference to the first-frame-getting video output attached to
976 // mVideoTrack. Set by the constructor, unset when the media element tells us
977 // it has rendered the first frame.
978 RefPtr<FirstFrameVideoOutput> mFirstFrameVideoOutput;
981 class HTMLMediaElement::MediaElementTrackSource
982 : public MediaStreamTrackSource,
983 public MediaStreamTrackSource::Sink,
984 public MediaStreamTrackConsumer {
985 public:
986 NS_DECL_ISUPPORTS_INHERITED
987 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaElementTrackSource,
988 MediaStreamTrackSource)
990 /* MediaDecoder track source */
991 MediaElementTrackSource(nsISerialEventTarget* aMainThreadEventTarget,
992 ProcessedMediaTrack* aTrack, nsIPrincipal* aPrincipal,
993 OutputMuteState aMuteState, bool aHasAlpha)
994 : MediaStreamTrackSource(aPrincipal, nsString()),
995 mMainThreadEventTarget(aMainThreadEventTarget),
996 mTrack(aTrack),
997 mIntendedElementMuteState(aMuteState),
998 mElementMuteState(aMuteState),
999 mMediaDecoderHasAlpha(Some(aHasAlpha)) {
1000 MOZ_ASSERT(mTrack);
1003 /* MediaStream track source */
1004 MediaElementTrackSource(nsISerialEventTarget* aMainThreadEventTarget,
1005 MediaStreamTrack* aCapturedTrack,
1006 MediaStreamTrackSource* aCapturedTrackSource,
1007 ProcessedMediaTrack* aTrack, MediaInputPort* aPort,
1008 OutputMuteState aMuteState)
1009 : MediaStreamTrackSource(aCapturedTrackSource->GetPrincipal(),
1010 nsString()),
1011 mMainThreadEventTarget(aMainThreadEventTarget),
1012 mCapturedTrack(aCapturedTrack),
1013 mCapturedTrackSource(aCapturedTrackSource),
1014 mTrack(aTrack),
1015 mPort(aPort),
1016 mIntendedElementMuteState(aMuteState),
1017 mElementMuteState(aMuteState) {
1018 MOZ_ASSERT(mTrack);
1019 MOZ_ASSERT(mCapturedTrack);
1020 MOZ_ASSERT(mCapturedTrackSource);
1021 MOZ_ASSERT(mPort);
1023 mCapturedTrack->AddConsumer(this);
1024 mCapturedTrackSource->RegisterSink(this);
1027 void SetEnabled(bool aEnabled) {
1028 if (!mTrack) {
1029 return;
1031 mTrack->SetDisabledTrackMode(aEnabled ? DisabledTrackMode::ENABLED
1032 : DisabledTrackMode::SILENCE_FREEZE);
1035 void SetPrincipal(RefPtr<nsIPrincipal> aPrincipal) {
1036 mPrincipal = std::move(aPrincipal);
1037 MediaStreamTrackSource::PrincipalChanged();
1040 void SetMutedByElement(OutputMuteState aMuteState) {
1041 if (mIntendedElementMuteState == aMuteState) {
1042 return;
1044 mIntendedElementMuteState = aMuteState;
1045 mMainThreadEventTarget->Dispatch(NS_NewRunnableFunction(
1046 "MediaElementTrackSource::SetMutedByElement",
1047 [self = RefPtr<MediaElementTrackSource>(this), this, aMuteState] {
1048 mElementMuteState = aMuteState;
1049 MediaStreamTrackSource::MutedChanged(Muted());
1050 }));
1053 void Destroy() override {
1054 if (mCapturedTrack) {
1055 mCapturedTrack->RemoveConsumer(this);
1056 mCapturedTrack = nullptr;
1058 if (mCapturedTrackSource) {
1059 mCapturedTrackSource->UnregisterSink(this);
1060 mCapturedTrackSource = nullptr;
1062 if (mTrack && !mTrack->IsDestroyed()) {
1063 mTrack->Destroy();
1065 if (mPort) {
1066 mPort->Destroy();
1067 mPort = nullptr;
1071 MediaSourceEnum GetMediaSource() const override {
1072 return MediaSourceEnum::Other;
1075 void Stop() override {
1076 // Do nothing. There may appear new output streams
1077 // that need tracks sourced from this source, so we
1078 // cannot destroy things yet.
1082 * Do not keep the track source alive. The source lifetime is controlled by
1083 * its associated tracks.
1085 bool KeepsSourceAlive() const override { return false; }
1088 * Do not keep the track source on. It is controlled by its associated tracks.
1090 bool Enabled() const override { return false; }
1092 void Disable() override {}
1094 void Enable() override {}
1096 void PrincipalChanged() override {
1097 if (!mCapturedTrackSource) {
1098 // This could happen during shutdown.
1099 return;
1102 SetPrincipal(mCapturedTrackSource->GetPrincipal());
1105 void MutedChanged(bool aNewState) override {
1106 MediaStreamTrackSource::MutedChanged(Muted());
1109 void OverrideEnded() override {
1110 Destroy();
1111 MediaStreamTrackSource::OverrideEnded();
1114 void NotifyEnabledChanged(MediaStreamTrack* aTrack, bool aEnabled) override {
1115 MediaStreamTrackSource::MutedChanged(Muted());
1118 bool Muted() const {
1119 return mElementMuteState == OutputMuteState::Muted ||
1120 (mCapturedTrack &&
1121 (mCapturedTrack->Muted() || !mCapturedTrack->Enabled()));
1124 bool HasAlpha() const override {
1125 if (mCapturedTrack) {
1126 return mCapturedTrack->AsVideoStreamTrack()
1127 ? mCapturedTrack->AsVideoStreamTrack()->HasAlpha()
1128 : false;
1130 return mMediaDecoderHasAlpha.valueOr(false);
1133 ProcessedMediaTrack* Track() const { return mTrack; }
1135 private:
1136 virtual ~MediaElementTrackSource() { Destroy(); };
1138 const RefPtr<nsISerialEventTarget> mMainThreadEventTarget;
1139 RefPtr<MediaStreamTrack> mCapturedTrack;
1140 RefPtr<MediaStreamTrackSource> mCapturedTrackSource;
1141 const RefPtr<ProcessedMediaTrack> mTrack;
1142 RefPtr<MediaInputPort> mPort;
1143 // The mute state as intended by the media element.
1144 OutputMuteState mIntendedElementMuteState;
1145 // The mute state as applied to this track source. It is applied async, so
1146 // needs to be tracked separately from the intended state.
1147 OutputMuteState mElementMuteState;
1148 // Some<bool> if this is a MediaDecoder track source.
1149 const Maybe<bool> mMediaDecoderHasAlpha;
1152 HTMLMediaElement::OutputMediaStream::OutputMediaStream(
1153 RefPtr<DOMMediaStream> aStream, bool aCapturingAudioOnly,
1154 bool aFinishWhenEnded)
1155 : mStream(std::move(aStream)),
1156 mCapturingAudioOnly(aCapturingAudioOnly),
1157 mFinishWhenEnded(aFinishWhenEnded) {}
1158 HTMLMediaElement::OutputMediaStream::~OutputMediaStream() = default;
1160 void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
1161 HTMLMediaElement::OutputMediaStream& aField,
1162 const char* aName, uint32_t aFlags) {
1163 ImplCycleCollectionTraverse(aCallback, aField.mStream, "mStream", aFlags);
1164 ImplCycleCollectionTraverse(aCallback, aField.mLiveTracks, "mLiveTracks",
1165 aFlags);
1166 ImplCycleCollectionTraverse(aCallback, aField.mFinishWhenEndedLoadingSrc,
1167 "mFinishWhenEndedLoadingSrc", aFlags);
1168 ImplCycleCollectionTraverse(aCallback, aField.mFinishWhenEndedAttrStream,
1169 "mFinishWhenEndedAttrStream", aFlags);
1170 ImplCycleCollectionTraverse(aCallback, aField.mFinishWhenEndedMediaSource,
1171 "mFinishWhenEndedMediaSource", aFlags);
1174 void ImplCycleCollectionUnlink(HTMLMediaElement::OutputMediaStream& aField) {
1175 ImplCycleCollectionUnlink(aField.mStream);
1176 ImplCycleCollectionUnlink(aField.mLiveTracks);
1177 ImplCycleCollectionUnlink(aField.mFinishWhenEndedLoadingSrc);
1178 ImplCycleCollectionUnlink(aField.mFinishWhenEndedAttrStream);
1179 ImplCycleCollectionUnlink(aField.mFinishWhenEndedMediaSource);
1182 NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::MediaElementTrackSource,
1183 MediaStreamTrackSource)
1184 NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::MediaElementTrackSource,
1185 MediaStreamTrackSource)
1186 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
1187 HTMLMediaElement::MediaElementTrackSource)
1188 NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
1189 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::MediaElementTrackSource)
1190 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(
1191 HTMLMediaElement::MediaElementTrackSource, MediaStreamTrackSource)
1192 tmp->Destroy();
1193 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCapturedTrack)
1194 NS_IMPL_CYCLE_COLLECTION_UNLINK(mCapturedTrackSource)
1195 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1196 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
1197 HTMLMediaElement::MediaElementTrackSource, MediaStreamTrackSource)
1198 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCapturedTrack)
1199 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCapturedTrackSource)
1200 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1203 * There is a reference cycle involving this class: MediaLoadListener
1204 * holds a reference to the HTMLMediaElement, which holds a reference
1205 * to an nsIChannel, which holds a reference to this listener.
1206 * We break the reference cycle in OnStartRequest by clearing mElement.
1208 class HTMLMediaElement::MediaLoadListener final
1209 : public nsIStreamListener,
1210 public nsIChannelEventSink,
1211 public nsIInterfaceRequestor,
1212 public nsIObserver,
1213 public nsIThreadRetargetableStreamListener {
1214 ~MediaLoadListener() = default;
1216 NS_DECL_THREADSAFE_ISUPPORTS
1217 NS_DECL_NSIREQUESTOBSERVER
1218 NS_DECL_NSISTREAMLISTENER
1219 NS_DECL_NSICHANNELEVENTSINK
1220 NS_DECL_NSIOBSERVER
1221 NS_DECL_NSIINTERFACEREQUESTOR
1222 NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
1224 public:
1225 explicit MediaLoadListener(HTMLMediaElement* aElement)
1226 : mElement(aElement), mLoadID(aElement->GetCurrentLoadID()) {
1227 MOZ_ASSERT(mElement, "Must pass an element to call back");
1230 private:
1231 RefPtr<HTMLMediaElement> mElement;
1232 nsCOMPtr<nsIStreamListener> mNextListener;
1233 const uint32_t mLoadID;
1236 NS_IMPL_ISUPPORTS(HTMLMediaElement::MediaLoadListener, nsIRequestObserver,
1237 nsIStreamListener, nsIChannelEventSink, nsIInterfaceRequestor,
1238 nsIObserver, nsIThreadRetargetableStreamListener)
1240 NS_IMETHODIMP
1241 HTMLMediaElement::MediaLoadListener::Observe(nsISupports* aSubject,
1242 const char* aTopic,
1243 const char16_t* aData) {
1244 nsContentUtils::UnregisterShutdownObserver(this);
1246 // Clear mElement to break cycle so we don't leak on shutdown
1247 mElement = nullptr;
1248 return NS_OK;
1251 NS_IMETHODIMP
1252 HTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest* aRequest) {
1253 nsContentUtils::UnregisterShutdownObserver(this);
1255 if (!mElement) {
1256 // We've been notified by the shutdown observer, and are shutting down.
1257 return NS_BINDING_ABORTED;
1260 // The element is only needed until we've had a chance to call
1261 // InitializeDecoderForChannel. So make sure mElement is cleared here.
1262 RefPtr<HTMLMediaElement> element;
1263 element.swap(mElement);
1265 if (mLoadID != element->GetCurrentLoadID()) {
1266 // The channel has been cancelled before we had a chance to create
1267 // a decoder. Abort, don't dispatch an "error" event, as the new load
1268 // may not be in an error state.
1269 return NS_BINDING_ABORTED;
1272 // Don't continue to load if the request failed or has been canceled.
1273 nsresult status;
1274 nsresult rv = aRequest->GetStatus(&status);
1275 NS_ENSURE_SUCCESS(rv, rv);
1276 if (NS_FAILED(status)) {
1277 if (element) {
1278 // Handle media not loading error because source was a tracking URL (or
1279 // fingerprinting, cryptomining, etc).
1280 // We make a note of this media node by including it in a dedicated
1281 // array of blocked tracking nodes under its parent document.
1282 if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
1283 status)) {
1284 element->OwnerDoc()->AddBlockedNodeByClassifier(element);
1286 element->NotifyLoadError(
1287 nsPrintfCString("%u: %s", uint32_t(status), "Request failed"));
1289 return status;
1292 nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
1293 bool succeeded;
1294 if (hc && NS_SUCCEEDED(hc->GetRequestSucceeded(&succeeded)) && !succeeded) {
1295 uint32_t responseStatus = 0;
1296 Unused << hc->GetResponseStatus(&responseStatus);
1297 nsAutoCString statusText;
1298 Unused << hc->GetResponseStatusText(statusText);
1299 element->NotifyLoadError(
1300 nsPrintfCString("%u: %s", responseStatus, statusText.get()));
1302 nsAutoString code;
1303 code.AppendInt(responseStatus);
1304 nsAutoString src;
1305 element->GetCurrentSrc(src);
1306 AutoTArray<nsString, 2> params = {code, src};
1307 element->ReportLoadError("MediaLoadHttpError", params);
1308 return NS_BINDING_ABORTED;
1311 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
1312 if (channel &&
1313 NS_SUCCEEDED(rv = element->InitializeDecoderForChannel(
1314 channel, getter_AddRefs(mNextListener))) &&
1315 mNextListener) {
1316 rv = mNextListener->OnStartRequest(aRequest);
1317 } else {
1318 // If InitializeDecoderForChannel() returned an error, fire a network error.
1319 if (NS_FAILED(rv) && !mNextListener) {
1320 // Load failed, attempt to load the next candidate resource. If there
1321 // are none, this will trigger a MEDIA_ERR_SRC_NOT_SUPPORTED error.
1322 element->NotifyLoadError("Failed to init decoder"_ns);
1324 // If InitializeDecoderForChannel did not return a listener (but may
1325 // have otherwise succeeded), we abort the connection since we aren't
1326 // interested in keeping the channel alive ourselves.
1327 rv = NS_BINDING_ABORTED;
1330 return rv;
1333 NS_IMETHODIMP
1334 HTMLMediaElement::MediaLoadListener::OnStopRequest(nsIRequest* aRequest,
1335 nsresult aStatus) {
1336 if (mNextListener) {
1337 return mNextListener->OnStopRequest(aRequest, aStatus);
1339 return NS_OK;
1342 NS_IMETHODIMP
1343 HTMLMediaElement::MediaLoadListener::OnDataAvailable(nsIRequest* aRequest,
1344 nsIInputStream* aStream,
1345 uint64_t aOffset,
1346 uint32_t aCount) {
1347 if (!mNextListener) {
1348 NS_ERROR(
1349 "Must have a chained listener; OnStartRequest should have "
1350 "canceled this request");
1351 return NS_BINDING_ABORTED;
1353 return mNextListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
1356 NS_IMETHODIMP
1357 HTMLMediaElement::MediaLoadListener::AsyncOnChannelRedirect(
1358 nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
1359 nsIAsyncVerifyRedirectCallback* cb) {
1360 // TODO is this really correct?? See bug #579329.
1361 if (mElement) {
1362 mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
1364 nsCOMPtr<nsIChannelEventSink> sink = do_QueryInterface(mNextListener);
1365 if (sink) {
1366 return sink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, cb);
1368 cb->OnRedirectVerifyCallback(NS_OK);
1369 return NS_OK;
1372 NS_IMETHODIMP
1373 HTMLMediaElement::MediaLoadListener::CheckListenerChain() {
1374 MOZ_ASSERT(mNextListener);
1375 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetable =
1376 do_QueryInterface(mNextListener);
1377 if (retargetable) {
1378 return retargetable->CheckListenerChain();
1380 return NS_ERROR_NO_INTERFACE;
1383 NS_IMETHODIMP
1384 HTMLMediaElement::MediaLoadListener::GetInterface(const nsIID& aIID,
1385 void** aResult) {
1386 return QueryInterface(aIID, aResult);
1389 void HTMLMediaElement::ReportLoadError(const char* aMsg,
1390 const nsTArray<nsString>& aParams) {
1391 ReportToConsole(nsIScriptError::warningFlag, aMsg, aParams);
1394 void HTMLMediaElement::ReportToConsole(
1395 uint32_t aErrorFlags, const char* aMsg,
1396 const nsTArray<nsString>& aParams) const {
1397 nsContentUtils::ReportToConsole(aErrorFlags, "Media"_ns, OwnerDoc(),
1398 nsContentUtils::eDOM_PROPERTIES, aMsg,
1399 aParams);
1402 class HTMLMediaElement::AudioChannelAgentCallback final
1403 : public nsIAudioChannelAgentCallback {
1404 public:
1405 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
1406 NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgentCallback)
1408 explicit AudioChannelAgentCallback(HTMLMediaElement* aOwner)
1409 : mOwner(aOwner),
1410 mAudioChannelVolume(1.0),
1411 mPlayingThroughTheAudioChannel(false),
1412 mIsOwnerAudible(IsOwnerAudible()),
1413 mIsShutDown(false) {
1414 MOZ_ASSERT(mOwner);
1415 MaybeCreateAudioChannelAgent();
1418 void UpdateAudioChannelPlayingState() {
1419 MOZ_ASSERT(!mIsShutDown);
1420 bool playingThroughTheAudioChannel = IsPlayingThroughTheAudioChannel();
1422 if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
1423 if (!MaybeCreateAudioChannelAgent()) {
1424 return;
1427 mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
1428 if (mPlayingThroughTheAudioChannel) {
1429 StartAudioChannelAgent();
1430 } else {
1431 StopAudioChanelAgent();
1436 void NotifyPlayStateChanged() {
1437 MOZ_ASSERT(!mIsShutDown);
1438 UpdateAudioChannelPlayingState();
1441 NS_IMETHODIMP WindowVolumeChanged(float aVolume, bool aMuted) override {
1442 MOZ_ASSERT(mAudioChannelAgent);
1444 MOZ_LOG(
1445 AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1446 ("HTMLMediaElement::AudioChannelAgentCallback, WindowVolumeChanged, "
1447 "this = %p, aVolume = %f, aMuted = %s\n",
1448 this, aVolume, aMuted ? "true" : "false"));
1450 if (mAudioChannelVolume != aVolume) {
1451 mAudioChannelVolume = aVolume;
1452 mOwner->SetVolumeInternal();
1455 const uint32_t muted = mOwner->mMuted;
1456 if (aMuted && !mOwner->ComputedMuted()) {
1457 mOwner->SetMutedInternal(muted | MUTED_BY_AUDIO_CHANNEL);
1458 } else if (!aMuted && mOwner->ComputedMuted()) {
1459 mOwner->SetMutedInternal(muted & ~MUTED_BY_AUDIO_CHANNEL);
1462 return NS_OK;
1465 NS_IMETHODIMP WindowSuspendChanged(SuspendTypes aSuspend) override {
1466 // Currently this method is only be used for delaying autoplay, and we've
1467 // separated related codes to `MediaPlaybackDelayPolicy`.
1468 return NS_OK;
1471 NS_IMETHODIMP WindowAudioCaptureChanged(bool aCapture) override {
1472 MOZ_ASSERT(mAudioChannelAgent);
1473 AudioCaptureTrackChangeIfNeeded();
1474 return NS_OK;
1477 void AudioCaptureTrackChangeIfNeeded() {
1478 MOZ_ASSERT(!mIsShutDown);
1479 if (!IsPlayingStarted()) {
1480 return;
1483 MOZ_ASSERT(mAudioChannelAgent);
1484 bool isCapturing = mAudioChannelAgent->IsWindowAudioCapturingEnabled();
1485 mOwner->AudioCaptureTrackChange(isCapturing);
1488 void NotifyAudioPlaybackChanged(AudibleChangedReasons aReason) {
1489 MOZ_ASSERT(!mIsShutDown);
1490 AudibleState newAudibleState = IsOwnerAudible();
1491 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1492 ("HTMLMediaElement::AudioChannelAgentCallback, "
1493 "NotifyAudioPlaybackChanged, this=%p, current=%s, new=%s",
1494 this, AudibleStateToStr(mIsOwnerAudible),
1495 AudibleStateToStr(newAudibleState)));
1496 if (mIsOwnerAudible == newAudibleState) {
1497 return;
1500 mIsOwnerAudible = newAudibleState;
1501 if (IsPlayingStarted()) {
1502 mAudioChannelAgent->NotifyStartedAudible(mIsOwnerAudible, aReason);
1506 void Shutdown() {
1507 MOZ_ASSERT(!mIsShutDown);
1508 if (mAudioChannelAgent && mAudioChannelAgent->IsPlayingStarted()) {
1509 StopAudioChanelAgent();
1511 mAudioChannelAgent = nullptr;
1512 mIsShutDown = true;
1515 float GetEffectiveVolume() const {
1516 MOZ_ASSERT(!mIsShutDown);
1517 return static_cast<float>(mOwner->Volume()) * mAudioChannelVolume;
1520 private:
1521 ~AudioChannelAgentCallback() { MOZ_ASSERT(mIsShutDown); };
1523 bool MaybeCreateAudioChannelAgent() {
1524 if (mAudioChannelAgent) {
1525 return true;
1528 mAudioChannelAgent = new AudioChannelAgent();
1529 nsresult rv =
1530 mAudioChannelAgent->Init(mOwner->OwnerDoc()->GetInnerWindow(), this);
1531 if (NS_WARN_IF(NS_FAILED(rv))) {
1532 mAudioChannelAgent = nullptr;
1533 MOZ_LOG(
1534 AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1535 ("HTMLMediaElement::AudioChannelAgentCallback, Fail to initialize "
1536 "the audio channel agent, this = %p\n",
1537 this));
1538 return false;
1541 return true;
1544 void StartAudioChannelAgent() {
1545 MOZ_ASSERT(mAudioChannelAgent);
1546 MOZ_ASSERT(!mAudioChannelAgent->IsPlayingStarted());
1547 if (NS_WARN_IF(NS_FAILED(
1548 mAudioChannelAgent->NotifyStartedPlaying(IsOwnerAudible())))) {
1549 return;
1551 mAudioChannelAgent->PullInitialUpdate();
1554 void StopAudioChanelAgent() {
1555 MOZ_ASSERT(mAudioChannelAgent);
1556 MOZ_ASSERT(mAudioChannelAgent->IsPlayingStarted());
1557 mAudioChannelAgent->NotifyStoppedPlaying();
1558 // If we have started audio capturing before, we have to tell media element
1559 // to clear the output capturing track.
1560 mOwner->AudioCaptureTrackChange(false);
1563 bool IsPlayingStarted() {
1564 if (MaybeCreateAudioChannelAgent()) {
1565 return mAudioChannelAgent->IsPlayingStarted();
1567 return false;
1570 AudibleState IsOwnerAudible() const {
1571 // paused media doesn't produce any sound.
1572 if (mOwner->mPaused) {
1573 return AudibleState::eNotAudible;
1575 return mOwner->IsAudible() ? AudibleState::eAudible
1576 : AudibleState::eNotAudible;
1579 bool IsPlayingThroughTheAudioChannel() const {
1580 // If we have an error, we are not playing.
1581 if (mOwner->GetError()) {
1582 return false;
1585 // We should consider any bfcached page or inactive document as non-playing.
1586 if (!mOwner->OwnerDoc()->IsActive()) {
1587 return false;
1590 // Media is suspended by the docshell.
1591 if (mOwner->ShouldBeSuspendedByInactiveDocShell()) {
1592 return false;
1595 // Are we paused
1596 if (mOwner->mPaused) {
1597 return false;
1600 // No audio track
1601 if (!mOwner->HasAudio()) {
1602 return false;
1605 // A loop always is playing
1606 if (mOwner->HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) {
1607 return true;
1610 // If we are actually playing...
1611 if (mOwner->IsCurrentlyPlaying()) {
1612 return true;
1615 // If we are playing an external stream.
1616 if (mOwner->mSrcAttrStream) {
1617 return true;
1620 return false;
1623 RefPtr<AudioChannelAgent> mAudioChannelAgent;
1624 HTMLMediaElement* mOwner;
1626 // The audio channel volume
1627 float mAudioChannelVolume;
1628 // Is this media element playing?
1629 bool mPlayingThroughTheAudioChannel;
1630 // Indicate whether media element is audible for users.
1631 AudibleState mIsOwnerAudible;
1632 bool mIsShutDown;
1635 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::AudioChannelAgentCallback)
1637 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
1638 HTMLMediaElement::AudioChannelAgentCallback)
1639 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent)
1640 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1642 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(
1643 HTMLMediaElement::AudioChannelAgentCallback)
1644 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent)
1645 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1647 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
1648 HTMLMediaElement::AudioChannelAgentCallback)
1649 NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
1650 NS_INTERFACE_MAP_END
1652 NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLMediaElement::AudioChannelAgentCallback)
1653 NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLMediaElement::AudioChannelAgentCallback)
1655 class HTMLMediaElement::ChannelLoader final {
1656 public:
1657 NS_INLINE_DECL_REFCOUNTING(ChannelLoader);
1659 void LoadInternal(HTMLMediaElement* aElement) {
1660 if (mCancelled) {
1661 return;
1664 // determine what security checks need to be performed in AsyncOpen().
1665 nsSecurityFlags securityFlags =
1666 aElement->ShouldCheckAllowOrigin()
1667 ? nsILoadInfo::SEC_REQUIRE_CORS_INHERITS_SEC_CONTEXT
1668 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_INHERITS_SEC_CONTEXT;
1670 if (aElement->GetCORSMode() == CORS_USE_CREDENTIALS) {
1671 securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
1674 securityFlags |= nsILoadInfo::SEC_ALLOW_CHROME;
1676 MOZ_ASSERT(
1677 aElement->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
1678 nsContentPolicyType contentPolicyType =
1679 aElement->IsHTMLElement(nsGkAtoms::audio)
1680 ? nsIContentPolicy::TYPE_INTERNAL_AUDIO
1681 : nsIContentPolicy::TYPE_INTERNAL_VIDEO;
1683 // If aElement has 'triggeringprincipal' attribute, we will use the value as
1684 // triggeringPrincipal for the channel, otherwise it will default to use
1685 // aElement->NodePrincipal().
1686 // This function returns true when aElement has 'triggeringprincipal', so if
1687 // setAttrs is true we will override the origin attributes on the channel
1688 // later.
1689 nsCOMPtr<nsIPrincipal> triggeringPrincipal;
1690 bool setAttrs = nsContentUtils::QueryTriggeringPrincipal(
1691 aElement, aElement->mLoadingSrcTriggeringPrincipal,
1692 getter_AddRefs(triggeringPrincipal));
1694 nsCOMPtr<nsILoadGroup> loadGroup = aElement->GetDocumentLoadGroup();
1695 nsCOMPtr<nsIChannel> channel;
1696 nsresult rv = NS_NewChannelWithTriggeringPrincipal(
1697 getter_AddRefs(channel), aElement->mLoadingSrc,
1698 static_cast<Element*>(aElement), triggeringPrincipal, securityFlags,
1699 contentPolicyType,
1700 nullptr, // aPerformanceStorage
1701 loadGroup,
1702 nullptr, // aCallbacks
1703 nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
1704 nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE |
1705 nsIChannel::LOAD_CALL_CONTENT_SNIFFERS);
1707 if (NS_FAILED(rv)) {
1708 // Notify load error so the element will try next resource candidate.
1709 aElement->NotifyLoadError("Fail to create channel"_ns);
1710 return;
1713 nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1714 if (setAttrs) {
1715 // The function simply returns NS_OK, so we ignore the return value.
1716 Unused << loadInfo->SetOriginAttributes(
1717 triggeringPrincipal->OriginAttributesRef());
1719 loadInfo->SetIsMediaRequest(true);
1720 loadInfo->SetIsMediaInitialRequest(true);
1722 nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
1723 if (cos) {
1724 if (aElement->mUseUrgentStartForChannel) {
1725 cos->AddClassFlags(nsIClassOfService::UrgentStart);
1727 // Reset the flag to avoid loading again without initiated by user
1728 // interaction.
1729 aElement->mUseUrgentStartForChannel = false;
1732 // Unconditionally disable throttling since we want the media to fluently
1733 // play even when we switch the tab to background.
1734 cos->AddClassFlags(nsIClassOfService::DontThrottle);
1737 // The listener holds a strong reference to us. This creates a
1738 // reference cycle, once we've set mChannel, which is manually broken
1739 // in the listener's OnStartRequest method after it is finished with
1740 // the element. The cycle will also be broken if we get a shutdown
1741 // notification before OnStartRequest fires. Necko guarantees that
1742 // OnStartRequest will eventually fire if we don't shut down first.
1743 RefPtr<MediaLoadListener> loadListener = new MediaLoadListener(aElement);
1745 channel->SetNotificationCallbacks(loadListener);
1747 nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(channel);
1748 if (hc) {
1749 // Use a byte range request from the start of the resource.
1750 // This enables us to detect if the stream supports byte range
1751 // requests, and therefore seeking, early.
1752 rv = hc->SetRequestHeader("Range"_ns, "bytes=0-"_ns, false);
1753 MOZ_ASSERT(NS_SUCCEEDED(rv));
1754 aElement->SetRequestHeaders(hc);
1757 rv = channel->AsyncOpen(loadListener);
1758 if (NS_FAILED(rv)) {
1759 // Notify load error so the element will try next resource candidate.
1760 aElement->NotifyLoadError("Failed to open channel"_ns);
1761 return;
1764 // Else the channel must be open and starting to download. If it encounters
1765 // a non-catastrophic failure, it will set a new task to continue loading
1766 // another candidate. It's safe to set it as mChannel now.
1767 mChannel = channel;
1769 // loadListener will be unregistered either on shutdown or when
1770 // OnStartRequest for the channel we just opened fires.
1771 nsContentUtils::RegisterShutdownObserver(loadListener);
1774 nsresult Load(HTMLMediaElement* aElement) {
1775 MOZ_ASSERT(aElement);
1776 // Per bug 1235183 comment 8, we can't spin the event loop from stable
1777 // state. Defer NS_NewChannel() to a new regular runnable.
1778 return aElement->MainThreadEventTarget()->Dispatch(
1779 NewRunnableMethod<HTMLMediaElement*>("ChannelLoader::LoadInternal",
1780 this, &ChannelLoader::LoadInternal,
1781 aElement));
1784 void Cancel() {
1785 mCancelled = true;
1786 if (mChannel) {
1787 mChannel->CancelWithReason(NS_BINDING_ABORTED,
1788 "HTMLMediaElement::ChannelLoader::Cancel"_ns);
1789 mChannel = nullptr;
1793 void Done() {
1794 MOZ_ASSERT(mChannel);
1795 // Decoder successfully created, the decoder now owns the MediaResource
1796 // which owns the channel.
1797 mChannel = nullptr;
1800 nsresult Redirect(nsIChannel* aChannel, nsIChannel* aNewChannel,
1801 uint32_t aFlags) {
1802 NS_ASSERTION(aChannel == mChannel, "Channels should match!");
1803 mChannel = aNewChannel;
1805 // Handle forwarding of Range header so that the intial detection
1806 // of seeking support (via result code 206) works across redirects.
1807 nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
1808 NS_ENSURE_STATE(http);
1810 constexpr auto rangeHdr = "Range"_ns;
1812 nsAutoCString rangeVal;
1813 if (NS_SUCCEEDED(http->GetRequestHeader(rangeHdr, rangeVal))) {
1814 NS_ENSURE_STATE(!rangeVal.IsEmpty());
1816 http = do_QueryInterface(aNewChannel);
1817 NS_ENSURE_STATE(http);
1819 nsresult rv = http->SetRequestHeader(rangeHdr, rangeVal, false);
1820 NS_ENSURE_SUCCESS(rv, rv);
1823 return NS_OK;
1826 private:
1827 ~ChannelLoader() { MOZ_ASSERT(!mChannel); }
1828 // Holds a reference to the first channel we open to the media resource.
1829 // Once the decoder is created, control over the channel passes to the
1830 // decoder, and we null out this reference. We must store this in case
1831 // we need to cancel the channel before control of it passes to the decoder.
1832 nsCOMPtr<nsIChannel> mChannel;
1834 bool mCancelled = false;
1837 class HTMLMediaElement::ErrorSink {
1838 public:
1839 explicit ErrorSink(HTMLMediaElement* aOwner) : mOwner(aOwner) {
1840 MOZ_ASSERT(mOwner);
1843 void SetError(uint16_t aErrorCode, const nsACString& aErrorDetails) {
1844 // Since we have multiple paths calling into DecodeError, e.g.
1845 // MediaKeys::Terminated and EMEH264Decoder::Error. We should take the 1st
1846 // one only in order not to fire multiple 'error' events.
1847 if (mError) {
1848 return;
1851 if (!IsValidErrorCode(aErrorCode)) {
1852 NS_ASSERTION(false, "Undefined MediaError codes!");
1853 return;
1856 mError = new MediaError(mOwner, aErrorCode, aErrorDetails);
1857 mOwner->DispatchAsyncEvent(u"error"_ns);
1858 if (mOwner->ReadyState() == HAVE_NOTHING &&
1859 aErrorCode == MEDIA_ERR_ABORTED) {
1860 // https://html.spec.whatwg.org/multipage/embedded-content.html#media-data-processing-steps-list
1861 // "If the media data fetching process is aborted by the user"
1862 mOwner->DispatchAsyncEvent(u"abort"_ns);
1863 mOwner->ChangeNetworkState(NETWORK_EMPTY);
1864 mOwner->DispatchAsyncEvent(u"emptied"_ns);
1865 if (mOwner->mDecoder) {
1866 mOwner->ShutdownDecoder();
1868 } else if (aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED) {
1869 mOwner->ChangeNetworkState(NETWORK_NO_SOURCE);
1870 } else {
1871 mOwner->ChangeNetworkState(NETWORK_IDLE);
1875 void ResetError() { mError = nullptr; }
1877 RefPtr<MediaError> mError;
1879 private:
1880 bool IsValidErrorCode(const uint16_t& aErrorCode) const {
1881 return (aErrorCode == MEDIA_ERR_DECODE || aErrorCode == MEDIA_ERR_NETWORK ||
1882 aErrorCode == MEDIA_ERR_ABORTED ||
1883 aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED);
1886 // Media elememt's life cycle would be longer than error sink, so we use the
1887 // raw pointer and this class would only be referenced by media element.
1888 HTMLMediaElement* mOwner;
1891 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement)
1893 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement,
1894 nsGenericHTMLElement)
1895 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource)
1896 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcMediaSource)
1897 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcStream)
1898 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcAttrStream)
1899 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourcePointer)
1900 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadBlockedDoc)
1901 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceLoadCandidate)
1902 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelWrapper)
1903 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mErrorSink->mError)
1904 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams)
1905 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputTrackSources);
1906 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed);
1907 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
1908 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
1909 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
1910 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
1911 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingMediaKeys)
1912 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack)
1913 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayPromises)
1914 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSeekDOMPromise)
1915 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSetMediaKeysDOMPromise)
1916 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventBlocker)
1917 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1919 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement,
1920 nsGenericHTMLElement)
1921 tmp->RemoveMutationObserver(tmp);
1922 if (tmp->mSrcStream) {
1923 // Need to unhook everything that EndSrcMediaStreamPlayback would normally
1924 // do, without creating any new strong references.
1925 if (tmp->mSelectedVideoStreamTrack) {
1926 tmp->mSelectedVideoStreamTrack->RemovePrincipalChangeObserver(tmp);
1928 if (tmp->mMediaStreamRenderer) {
1929 tmp->mMediaStreamRenderer->Shutdown();
1930 // We null out mMediaStreamRenderer here since Shutdown() will shut down
1931 // its WatchManager, and UpdateSrcStreamPotentiallyPlaying() contains a
1932 // guard for this.
1933 tmp->mMediaStreamRenderer = nullptr;
1935 if (tmp->mSecondaryMediaStreamRenderer) {
1936 tmp->mSecondaryMediaStreamRenderer->Shutdown();
1937 tmp->mSecondaryMediaStreamRenderer = nullptr;
1939 if (tmp->mMediaStreamTrackListener) {
1940 tmp->mSrcStream->UnregisterTrackListener(
1941 tmp->mMediaStreamTrackListener.get());
1944 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcStream)
1945 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcAttrStream)
1946 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource)
1947 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcMediaSource)
1948 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourcePointer)
1949 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoadBlockedDoc)
1950 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate)
1951 if (tmp->mAudioChannelWrapper) {
1952 tmp->mAudioChannelWrapper->Shutdown();
1954 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelWrapper)
1955 NS_IMPL_CYCLE_COLLECTION_UNLINK(mErrorSink->mError)
1956 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams)
1957 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputTrackSources)
1958 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed)
1959 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
1960 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
1961 NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
1962 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
1963 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingMediaKeys)
1964 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
1965 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayPromises)
1966 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSeekDOMPromise)
1967 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetMediaKeysDOMPromise)
1968 if (tmp->mMediaControlKeyListener) {
1969 tmp->mMediaControlKeyListener->StopIfNeeded();
1971 if (tmp->mEventBlocker) {
1972 tmp->mEventBlocker->Shutdown();
1974 NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
1975 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1977 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLMediaElement,
1978 nsGenericHTMLElement)
1980 void HTMLMediaElement::AddSizeOfExcludingThis(nsWindowSizes& aSizes,
1981 size_t* aNodeSize) const {
1982 nsGenericHTMLElement::AddSizeOfExcludingThis(aSizes, aNodeSize);
1984 // There are many other fields that might be worth reporting, but as seen in
1985 // bug 1595603, the event we postpone to dispatch can grow to be very large
1986 // sometimes, so at least report that.
1987 if (mEventBlocker) {
1988 *aNodeSize +=
1989 mEventBlocker->SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
1993 void HTMLMediaElement::ContentRemoved(nsIContent* aChild,
1994 nsIContent* aPreviousSibling) {
1995 if (aChild == mSourcePointer) {
1996 mSourcePointer = aPreviousSibling;
2000 already_AddRefed<MediaSource> HTMLMediaElement::GetMozMediaSourceObject()
2001 const {
2002 RefPtr<MediaSource> source = mMediaSource;
2003 return source.forget();
2006 already_AddRefed<Promise> HTMLMediaElement::MozRequestDebugInfo(
2007 ErrorResult& aRv) {
2008 RefPtr<Promise> promise = CreateDOMPromise(aRv);
2009 if (NS_WARN_IF(aRv.Failed())) {
2010 return nullptr;
2012 auto result = MakeUnique<dom::HTMLMediaElementDebugInfo>();
2013 if (mMediaKeys) {
2014 GetEMEInfo(result->mEMEInfo);
2016 if (mVideoFrameContainer) {
2017 result->mCompositorDroppedFrames =
2018 mVideoFrameContainer->GetDroppedImageCount();
2020 if (mDecoder) {
2021 mDecoder->RequestDebugInfo(result->mDecoder)
2022 ->Then(
2023 mAbstractMainThread, __func__,
2024 [promise, ptr = std::move(result)]() {
2025 promise->MaybeResolve(ptr.get());
2027 []() {
2028 MOZ_ASSERT_UNREACHABLE("Unexpected RequestDebugInfo() rejection");
2030 } else {
2031 promise->MaybeResolve(result.get());
2033 return promise.forget();
2036 /* static */
2037 void HTMLMediaElement::MozEnableDebugLog(const GlobalObject&) {
2038 DecoderDoctorLogger::EnableLogging();
2041 already_AddRefed<Promise> HTMLMediaElement::MozRequestDebugLog(
2042 ErrorResult& aRv) {
2043 RefPtr<Promise> promise = CreateDOMPromise(aRv);
2044 if (NS_WARN_IF(aRv.Failed())) {
2045 return nullptr;
2048 DecoderDoctorLogger::RetrieveMessages(this)->Then(
2049 mAbstractMainThread, __func__,
2050 [promise](const nsACString& aString) {
2051 promise->MaybeResolve(NS_ConvertUTF8toUTF16(aString));
2053 [promise](nsresult rv) { promise->MaybeReject(rv); });
2055 return promise.forget();
2058 void HTMLMediaElement::SetVisible(bool aVisible) {
2059 mForcedHidden = !aVisible;
2060 if (mDecoder) {
2061 mDecoder->SetForcedHidden(!aVisible);
2065 bool HTMLMediaElement::IsVideoDecodingSuspended() const {
2066 return mDecoder && mDecoder->IsVideoDecodingSuspended();
2069 double HTMLMediaElement::TotalVideoPlayTime() const {
2070 return mDecoder ? mDecoder->GetTotalVideoPlayTimeInSeconds() : -1.0;
2073 double HTMLMediaElement::TotalVideoHDRPlayTime() const {
2074 return mDecoder ? mDecoder->GetTotalVideoHDRPlayTimeInSeconds() : -1.0;
2077 double HTMLMediaElement::VisiblePlayTime() const {
2078 return mDecoder ? mDecoder->GetVisibleVideoPlayTimeInSeconds() : -1.0;
2081 double HTMLMediaElement::InvisiblePlayTime() const {
2082 return mDecoder ? mDecoder->GetInvisibleVideoPlayTimeInSeconds() : -1.0;
2085 double HTMLMediaElement::TotalAudioPlayTime() const {
2086 return mDecoder ? mDecoder->GetTotalAudioPlayTimeInSeconds() : -1.0;
2089 double HTMLMediaElement::AudiblePlayTime() const {
2090 return mDecoder ? mDecoder->GetAudiblePlayTimeInSeconds() : -1.0;
2093 double HTMLMediaElement::InaudiblePlayTime() const {
2094 return mDecoder ? mDecoder->GetInaudiblePlayTimeInSeconds() : -1.0;
2097 double HTMLMediaElement::MutedPlayTime() const {
2098 return mDecoder ? mDecoder->GetMutedPlayTimeInSeconds() : -1.0;
2101 double HTMLMediaElement::VideoDecodeSuspendedTime() const {
2102 return mDecoder ? mDecoder->GetVideoDecodeSuspendedTimeInSeconds() : -1.0;
2105 void HTMLMediaElement::SetFormatDiagnosticsReportForMimeType(
2106 const nsAString& aMimeType, DecoderDoctorReportType aType) {
2107 DecoderDoctorDiagnostics diagnostics;
2108 diagnostics.SetDecoderDoctorReportType(aType);
2109 diagnostics.StoreFormatDiagnostics(OwnerDoc(), aMimeType, false /* can play*/,
2110 __func__);
2113 void HTMLMediaElement::SetDecodeError(const nsAString& aError,
2114 ErrorResult& aRv) {
2115 // The reason we use this map-ish structure is because we can't use
2116 // `CR.NS_ERROR.*` directly in test. In order to use them in test, we have to
2117 // add them into `xpc.msg`. As we won't use `CR.NS_ERROR.*` in the production
2118 // code, adding them to `xpc.msg` seems an overdesign and adding maintenance
2119 // effort (exposing them in CR also needs to add a description, which is
2120 // useless because we won't show them to users)
2121 static struct {
2122 const char* mName;
2123 nsresult mResult;
2124 } kSupportedErrorList[] = {
2125 {"NS_ERROR_DOM_MEDIA_ABORT_ERR", NS_ERROR_DOM_MEDIA_ABORT_ERR},
2126 {"NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR",
2127 NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR},
2128 {"NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR",
2129 NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR},
2130 {"NS_ERROR_DOM_MEDIA_DECODE_ERR", NS_ERROR_DOM_MEDIA_DECODE_ERR},
2131 {"NS_ERROR_DOM_MEDIA_FATAL_ERR", NS_ERROR_DOM_MEDIA_FATAL_ERR},
2132 {"NS_ERROR_DOM_MEDIA_METADATA_ERR", NS_ERROR_DOM_MEDIA_METADATA_ERR},
2133 {"NS_ERROR_DOM_MEDIA_OVERFLOW_ERR", NS_ERROR_DOM_MEDIA_OVERFLOW_ERR},
2134 {"NS_ERROR_DOM_MEDIA_MEDIASINK_ERR", NS_ERROR_DOM_MEDIA_MEDIASINK_ERR},
2135 {"NS_ERROR_DOM_MEDIA_DEMUXER_ERR", NS_ERROR_DOM_MEDIA_DEMUXER_ERR},
2136 {"NS_ERROR_DOM_MEDIA_CDM_ERR", NS_ERROR_DOM_MEDIA_CDM_ERR},
2137 {"NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR",
2138 NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR}};
2139 for (auto& error : kSupportedErrorList) {
2140 if (strcmp(error.mName, NS_ConvertUTF16toUTF8(aError).get()) == 0) {
2141 DecoderDoctorDiagnostics diagnostics;
2142 diagnostics.StoreDecodeError(OwnerDoc(), error.mResult, u""_ns, __func__);
2143 return;
2146 aRv.Throw(NS_ERROR_FAILURE);
2149 void HTMLMediaElement::SetAudioSinkFailedStartup() {
2150 DecoderDoctorDiagnostics diagnostics;
2151 diagnostics.StoreEvent(OwnerDoc(),
2152 {DecoderDoctorEvent::eAudioSinkStartup,
2153 NS_ERROR_DOM_MEDIA_CUBEB_INITIALIZATION_ERR},
2154 __func__);
2157 already_AddRefed<layers::Image> HTMLMediaElement::GetCurrentImage() {
2158 MarkAsTainted();
2160 // TODO: In bug 1345404, handle case when video decoder is already suspended.
2161 ImageContainer* container = GetImageContainer();
2162 if (!container) {
2163 return nullptr;
2166 AutoLockImage lockImage(container);
2167 RefPtr<layers::Image> image = lockImage.GetImage(TimeStamp::Now());
2168 return image.forget();
2171 bool HTMLMediaElement::HasSuspendTaint() const {
2172 MOZ_ASSERT(!mDecoder || (mDecoder->HasSuspendTaint() == mHasSuspendTaint));
2173 return mHasSuspendTaint;
2176 already_AddRefed<DOMMediaStream> HTMLMediaElement::GetSrcObject() const {
2177 return do_AddRef(mSrcAttrStream);
2180 void HTMLMediaElement::SetSrcObject(DOMMediaStream& aValue) {
2181 SetSrcObject(&aValue);
2184 void HTMLMediaElement::SetSrcObject(DOMMediaStream* aValue) {
2185 for (auto& outputStream : mOutputStreams) {
2186 if (aValue == outputStream.mStream) {
2187 ReportToConsole(nsIScriptError::warningFlag,
2188 "MediaElementStreamCaptureCycle");
2189 return;
2192 mSrcAttrStream = aValue;
2193 UpdateAudioChannelPlayingState();
2194 DoLoad();
2197 bool HTMLMediaElement::Ended() {
2198 return (mDecoder && mDecoder->IsEnded()) ||
2199 (mSrcStream && mSrcStreamReportPlaybackEnded);
2202 void HTMLMediaElement::GetCurrentSrc(nsAString& aCurrentSrc) {
2203 nsAutoCString src;
2204 GetCurrentSpec(src);
2205 CopyUTF8toUTF16(src, aCurrentSrc);
2208 nsresult HTMLMediaElement::OnChannelRedirect(nsIChannel* aChannel,
2209 nsIChannel* aNewChannel,
2210 uint32_t aFlags) {
2211 MOZ_ASSERT(mChannelLoader);
2212 return mChannelLoader->Redirect(aChannel, aNewChannel, aFlags);
2215 void HTMLMediaElement::ShutdownDecoder() {
2216 RemoveMediaElementFromURITable();
2217 NS_ASSERTION(mDecoder, "Must have decoder to shut down");
2219 mWaitingForKeyListener.DisconnectIfExists();
2220 if (mMediaSource) {
2221 mMediaSource->CompletePendingTransactions();
2223 mDecoder->Shutdown();
2224 DDUNLINKCHILD(mDecoder.get());
2225 mDecoder = nullptr;
2228 void HTMLMediaElement::AbortExistingLoads() {
2229 // Abort any already-running instance of the resource selection algorithm.
2230 mLoadWaitStatus = NOT_WAITING;
2232 // Set a new load ID. This will cause events which were enqueued
2233 // with a different load ID to silently be cancelled.
2234 mCurrentLoadID++;
2236 // Immediately reject or resolve the already-dispatched
2237 // nsResolveOrRejectPendingPlayPromisesRunners. These runners won't be
2238 // executed again later since the mCurrentLoadID had been changed.
2239 for (auto& runner : mPendingPlayPromisesRunners) {
2240 runner->ResolveOrReject();
2242 mPendingPlayPromisesRunners.Clear();
2244 if (mChannelLoader) {
2245 mChannelLoader->Cancel();
2246 mChannelLoader = nullptr;
2249 bool fireTimeUpdate = false;
2251 if (mDecoder) {
2252 fireTimeUpdate = mDecoder->GetCurrentTime() != 0.0;
2253 ShutdownDecoder();
2255 if (mSrcStream) {
2256 EndSrcMediaStreamPlayback();
2259 if (mMediaSource) {
2260 OwnerDoc()->RemoveMediaElementWithMSE();
2263 RemoveMediaElementFromURITable();
2264 mLoadingSrcTriggeringPrincipal = nullptr;
2265 DDLOG(DDLogCategory::Property, "loading_src", "");
2266 DDUNLINKCHILD(mMediaSource.get());
2267 mMediaSource = nullptr;
2269 if (mNetworkState == NETWORK_LOADING || mNetworkState == NETWORK_IDLE) {
2270 DispatchAsyncEvent(u"abort"_ns);
2273 bool hadVideo = HasVideo();
2274 mErrorSink->ResetError();
2275 mCurrentPlayRangeStart = -1.0;
2276 mPlayed = new TimeRanges(ToSupports(OwnerDoc()));
2277 mLoadedDataFired = false;
2278 mAutoplaying = true;
2279 mIsLoadingFromSourceChildren = false;
2280 mSuspendedAfterFirstFrame = false;
2281 mAllowSuspendAfterFirstFrame = true;
2282 mHaveQueuedSelectResource = false;
2283 mSuspendedForPreloadNone = false;
2284 mDownloadSuspendedByCache = false;
2285 mMediaInfo = MediaInfo();
2286 mIsEncrypted = false;
2287 mPendingEncryptedInitData.Reset();
2288 mWaitingForKey = NOT_WAITING_FOR_KEY;
2289 mSourcePointer = nullptr;
2290 mIsBlessed = false;
2291 SetAudibleState(false);
2293 mTags = nullptr;
2295 if (mNetworkState != NETWORK_EMPTY) {
2296 NS_ASSERTION(!mDecoder && !mSrcStream,
2297 "How did someone setup a new stream/decoder already?");
2299 DispatchAsyncEvent(u"emptied"_ns);
2301 // ChangeNetworkState() will call UpdateAudioChannelPlayingState()
2302 // indirectly which depends on mPaused. So we need to update mPaused first.
2303 if (!mPaused) {
2304 mPaused = true;
2305 PlayPromise::RejectPromises(TakePendingPlayPromises(),
2306 NS_ERROR_DOM_MEDIA_ABORT_ERR);
2308 ChangeNetworkState(NETWORK_EMPTY);
2309 RemoveMediaTracks();
2310 UpdateOutputTrackSources();
2311 ChangeReadyState(HAVE_NOTHING);
2313 // TODO: Apply the rules for text track cue rendering Bug 865407
2314 if (mTextTrackManager) {
2315 mTextTrackManager->GetTextTracks()->SetCuesInactive();
2318 if (fireTimeUpdate) {
2319 // Since we destroyed the decoder above, the current playback position
2320 // will now be reported as 0. The playback position was non-zero when
2321 // we destroyed the decoder, so fire a timeupdate event so that the
2322 // change will be reflected in the controls.
2323 FireTimeUpdate(TimeupdateType::eMandatory);
2325 UpdateAudioChannelPlayingState();
2328 if (IsVideo() && hadVideo) {
2329 // Ensure we render transparent black after resetting video resolution.
2330 Maybe<nsIntSize> size = Some(nsIntSize(0, 0));
2331 Invalidate(true, size, false);
2334 // As aborting current load would stop current playback, so we have no need to
2335 // resume a paused media element.
2336 ClearResumeDelayedMediaPlaybackAgentIfNeeded();
2338 mMediaControlKeyListener->StopIfNeeded();
2340 // We may have changed mPaused, mAutoplaying, and other
2341 // things which can affect AddRemoveSelfReference
2342 AddRemoveSelfReference();
2344 mIsRunningSelectResource = false;
2346 AssertReadyStateIsNothing();
2349 void HTMLMediaElement::NoSupportedMediaSourceError(
2350 const nsACString& aErrorDetails) {
2351 if (mDecoder) {
2352 ShutdownDecoder();
2355 bool isSameOriginLoad = false;
2356 nsresult rv = NS_ERROR_NOT_AVAILABLE;
2357 if (mSrcAttrTriggeringPrincipal && mLoadingSrc) {
2358 rv = mSrcAttrTriggeringPrincipal->IsSameOrigin(mLoadingSrc,
2359 &isSameOriginLoad);
2362 if (NS_SUCCEEDED(rv) && !isSameOriginLoad) {
2363 // aErrorDetails can include sensitive details like MimeType or HTTP Status
2364 // Code. In case we're loading a 3rd party resource we should not leak this
2365 // and pass a Generic Error Message
2366 mErrorSink->SetError(MEDIA_ERR_SRC_NOT_SUPPORTED,
2367 "Failed to open media"_ns);
2368 } else {
2369 mErrorSink->SetError(MEDIA_ERR_SRC_NOT_SUPPORTED, aErrorDetails);
2372 RemoveMediaTracks();
2373 ChangeDelayLoadStatus(false);
2374 UpdateAudioChannelPlayingState();
2375 PlayPromise::RejectPromises(TakePendingPlayPromises(),
2376 NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR);
2379 // Runs a "synchronous section", a function that must run once the event loop
2380 // has reached a "stable state"
2381 // http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
2382 void HTMLMediaElement::RunInStableState(nsIRunnable* aRunnable) {
2383 if (mShuttingDown) {
2384 return;
2387 nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction(
2388 "HTMLMediaElement::RunInStableState",
2389 [self = RefPtr<HTMLMediaElement>(this), loadId = GetCurrentLoadID(),
2390 runnable = RefPtr<nsIRunnable>(aRunnable)]() {
2391 if (self->GetCurrentLoadID() != loadId) {
2392 return;
2394 runnable->Run();
2396 nsContentUtils::RunInStableState(task.forget());
2399 void HTMLMediaElement::QueueLoadFromSourceTask() {
2400 if (!mIsLoadingFromSourceChildren || mShuttingDown) {
2401 return;
2404 if (mDecoder) {
2405 // Reset readyState to HAVE_NOTHING since we're going to load a new decoder.
2406 ShutdownDecoder();
2407 ChangeReadyState(HAVE_NOTHING);
2410 AssertReadyStateIsNothing();
2412 ChangeDelayLoadStatus(true);
2413 ChangeNetworkState(NETWORK_LOADING);
2414 RefPtr<Runnable> r =
2415 NewRunnableMethod("HTMLMediaElement::LoadFromSourceChildren", this,
2416 &HTMLMediaElement::LoadFromSourceChildren);
2417 RunInStableState(r);
2420 void HTMLMediaElement::QueueSelectResourceTask() {
2421 // Don't allow multiple async select resource calls to be queued.
2422 if (mHaveQueuedSelectResource) return;
2423 mHaveQueuedSelectResource = true;
2424 ChangeNetworkState(NETWORK_NO_SOURCE);
2425 RefPtr<Runnable> r =
2426 NewRunnableMethod("HTMLMediaElement::SelectResourceWrapper", this,
2427 &HTMLMediaElement::SelectResourceWrapper);
2428 RunInStableState(r);
2431 static bool HasSourceChildren(nsIContent* aElement) {
2432 for (nsIContent* child = aElement->GetFirstChild(); child;
2433 child = child->GetNextSibling()) {
2434 if (child->IsHTMLElement(nsGkAtoms::source)) {
2435 return true;
2438 return false;
2441 static nsCString DocumentOrigin(Document* aDoc) {
2442 if (!aDoc) {
2443 return "null"_ns;
2445 nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
2446 if (!principal) {
2447 return "null"_ns;
2449 nsCString origin;
2450 if (NS_FAILED(principal->GetOrigin(origin))) {
2451 return "null"_ns;
2453 return origin;
2456 void HTMLMediaElement::Load() {
2457 LOG(LogLevel::Debug,
2458 ("%p Load() hasSrcAttrStream=%d hasSrcAttr=%d hasSourceChildren=%d "
2459 "handlingInput=%d hasAutoplayAttr=%d AllowedToPlay=%d "
2460 "ownerDoc=%p (%s) ownerDocUserActivated=%d "
2461 "muted=%d volume=%f",
2462 this, !!mSrcAttrStream, HasAttr(kNameSpaceID_None, nsGkAtoms::src),
2463 HasSourceChildren(this), UserActivation::IsHandlingUserInput(),
2464 HasAttr(nsGkAtoms::autoplay), AllowedToPlay(), OwnerDoc(),
2465 DocumentOrigin(OwnerDoc()).get(),
2466 OwnerDoc()->HasBeenUserGestureActivated(), mMuted, mVolume));
2468 if (mIsRunningLoadMethod) {
2469 return;
2472 mIsDoingExplicitLoad = true;
2473 DoLoad();
2476 void HTMLMediaElement::DoLoad() {
2477 // Check if media is allowed for the docshell.
2478 nsCOMPtr<nsIDocShell> docShell = OwnerDoc()->GetDocShell();
2479 if (docShell && !docShell->GetAllowMedia()) {
2480 LOG(LogLevel::Debug, ("%p Media not allowed", this));
2481 return;
2484 if (mIsRunningLoadMethod) {
2485 return;
2488 if (UserActivation::IsHandlingUserInput()) {
2489 // Detect if user has interacted with element so that play will not be
2490 // blocked when initiated by a script. This enables sites to capture user
2491 // intent to play by calling load() in the click handler of a "catalog
2492 // view" of a gallery of videos.
2493 mIsBlessed = true;
2494 // Mark the channel as urgent-start when autoplay so that it will play the
2495 // media from src after loading enough resource.
2496 if (HasAttr(nsGkAtoms::autoplay)) {
2497 mUseUrgentStartForChannel = true;
2501 SetPlayedOrSeeked(false);
2502 mIsRunningLoadMethod = true;
2503 AbortExistingLoads();
2504 SetPlaybackRate(mDefaultPlaybackRate, IgnoreErrors());
2505 QueueSelectResourceTask();
2506 ResetState();
2507 mIsRunningLoadMethod = false;
2510 void HTMLMediaElement::ResetState() {
2511 // There might be a pending MediaDecoder::PlaybackPositionChanged() which
2512 // will overwrite |mMediaInfo.mVideo.mDisplay| in UpdateMediaSize() to give
2513 // staled videoWidth and videoHeight. We have to call ForgetElement() here
2514 // such that the staled callbacks won't reach us.
2515 if (mVideoFrameContainer) {
2516 mVideoFrameContainer->ForgetElement();
2517 mVideoFrameContainer = nullptr;
2519 if (mMediaStreamRenderer) {
2520 // mMediaStreamRenderer, has a strong reference to mVideoFrameContainer.
2521 mMediaStreamRenderer->Shutdown();
2522 mMediaStreamRenderer = nullptr;
2524 if (mSecondaryMediaStreamRenderer) {
2525 // mSecondaryMediaStreamRenderer, has a strong reference to
2526 // the secondary VideoFrameContainer.
2527 mSecondaryMediaStreamRenderer->Shutdown();
2528 mSecondaryMediaStreamRenderer = nullptr;
2532 void HTMLMediaElement::SelectResourceWrapper() {
2533 SelectResource();
2534 MaybeBeginCloningVisually();
2535 mIsRunningSelectResource = false;
2536 mHaveQueuedSelectResource = false;
2537 mIsDoingExplicitLoad = false;
2540 void HTMLMediaElement::SelectResource() {
2541 if (!mSrcAttrStream && !HasAttr(kNameSpaceID_None, nsGkAtoms::src) &&
2542 !HasSourceChildren(this)) {
2543 // The media element has neither a src attribute nor any source
2544 // element children, abort the load.
2545 ChangeNetworkState(NETWORK_EMPTY);
2546 ChangeDelayLoadStatus(false);
2547 return;
2550 ChangeDelayLoadStatus(true);
2552 ChangeNetworkState(NETWORK_LOADING);
2553 DispatchAsyncEvent(u"loadstart"_ns);
2555 // Delay setting mIsRunningSeletResource until after UpdatePreloadAction
2556 // so that we don't lose our state change by bailing out of the preload
2557 // state update
2558 UpdatePreloadAction();
2559 mIsRunningSelectResource = true;
2561 // If we have a 'src' attribute, use that exclusively.
2562 nsAutoString src;
2563 if (mSrcAttrStream) {
2564 SetupSrcMediaStreamPlayback(mSrcAttrStream);
2565 } else if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
2566 nsCOMPtr<nsIURI> uri;
2567 MediaResult rv = NewURIFromString(src, getter_AddRefs(uri));
2568 if (NS_SUCCEEDED(rv)) {
2569 LOG(LogLevel::Debug, ("%p Trying load from src=%s", this,
2570 NS_ConvertUTF16toUTF8(src).get()));
2571 NS_ASSERTION(
2572 !mIsLoadingFromSourceChildren,
2573 "Should think we're not loading from source children by default");
2575 RemoveMediaElementFromURITable();
2576 if (!mSrcMediaSource) {
2577 mLoadingSrc = uri;
2578 } else {
2579 mLoadingSrc = nullptr;
2581 mLoadingSrcTriggeringPrincipal = mSrcAttrTriggeringPrincipal;
2582 DDLOG(DDLogCategory::Property, "loading_src",
2583 nsCString(NS_ConvertUTF16toUTF8(src)));
2584 bool hadMediaSource = !!mMediaSource;
2585 mMediaSource = mSrcMediaSource;
2586 if (mMediaSource && !hadMediaSource) {
2587 OwnerDoc()->AddMediaElementWithMSE();
2589 DDLINKCHILD("mediasource", mMediaSource.get());
2590 UpdatePreloadAction();
2591 if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE && !mMediaSource) {
2592 // preload:none media, suspend the load here before we make any
2593 // network requests.
2594 SuspendLoad();
2595 return;
2598 rv = LoadResource();
2599 if (NS_SUCCEEDED(rv)) {
2600 return;
2602 } else {
2603 AutoTArray<nsString, 1> params = {src};
2604 ReportLoadError("MediaLoadInvalidURI", params);
2605 rv = MediaResult(rv.Code(), "MediaLoadInvalidURI");
2607 // The media element has neither a src attribute nor a source element child:
2608 // set the networkState to NETWORK_EMPTY, and abort these steps; the
2609 // synchronous section ends.
2610 mMainThreadEventTarget->Dispatch(NewRunnableMethod<nsCString>(
2611 "HTMLMediaElement::NoSupportedMediaSourceError", this,
2612 &HTMLMediaElement::NoSupportedMediaSourceError, rv.Description()));
2613 } else {
2614 // Otherwise, the source elements will be used.
2615 mIsLoadingFromSourceChildren = true;
2616 LoadFromSourceChildren();
2620 void HTMLMediaElement::NotifyLoadError(const nsACString& aErrorDetails) {
2621 if (!mIsLoadingFromSourceChildren) {
2622 LOG(LogLevel::Debug, ("NotifyLoadError(), no supported media error"));
2623 NoSupportedMediaSourceError(aErrorDetails);
2624 } else if (mSourceLoadCandidate) {
2625 DispatchAsyncSourceError(mSourceLoadCandidate);
2626 QueueLoadFromSourceTask();
2627 } else {
2628 NS_WARNING("Should know the source we were loading from!");
2632 void HTMLMediaElement::NotifyMediaTrackAdded(dom::MediaTrack* aTrack) {
2633 // The set of tracks changed.
2634 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources);
2637 void HTMLMediaElement::NotifyMediaTrackRemoved(dom::MediaTrack* aTrack) {
2638 // The set of tracks changed.
2639 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources);
2642 void HTMLMediaElement::NotifyMediaTrackEnabled(dom::MediaTrack* aTrack) {
2643 MOZ_ASSERT(aTrack);
2644 if (!aTrack) {
2645 return;
2647 #ifdef DEBUG
2648 nsString id;
2649 aTrack->GetId(id);
2651 LOG(LogLevel::Debug, ("MediaElement %p %sTrack with id %s enabled", this,
2652 aTrack->AsAudioTrack() ? "Audio" : "Video",
2653 NS_ConvertUTF16toUTF8(id).get()));
2654 #endif
2656 MOZ_ASSERT((aTrack->AsAudioTrack() && aTrack->AsAudioTrack()->Enabled()) ||
2657 (aTrack->AsVideoTrack() && aTrack->AsVideoTrack()->Selected()));
2659 if (aTrack->AsAudioTrack()) {
2660 SetMutedInternal(mMuted & ~MUTED_BY_AUDIO_TRACK);
2661 } else if (aTrack->AsVideoTrack()) {
2662 if (!IsVideo()) {
2663 MOZ_ASSERT(false);
2664 return;
2666 mDisableVideo = false;
2667 } else {
2668 MOZ_ASSERT(false, "Unknown track type");
2671 if (mSrcStream) {
2672 if (AudioTrack* t = aTrack->AsAudioTrack()) {
2673 if (mMediaStreamRenderer) {
2674 mMediaStreamRenderer->AddTrack(t->GetAudioStreamTrack());
2676 } else if (VideoTrack* t = aTrack->AsVideoTrack()) {
2677 MOZ_ASSERT(!mSelectedVideoStreamTrack);
2679 mSelectedVideoStreamTrack = t->GetVideoStreamTrack();
2680 mSelectedVideoStreamTrack->AddPrincipalChangeObserver(this);
2681 if (mMediaStreamRenderer) {
2682 mMediaStreamRenderer->AddTrack(mSelectedVideoStreamTrack);
2684 if (mSecondaryMediaStreamRenderer) {
2685 mSecondaryMediaStreamRenderer->AddTrack(mSelectedVideoStreamTrack);
2687 if (mMediaInfo.HasVideo()) {
2688 mMediaInfo.mVideo.SetAlpha(mSelectedVideoStreamTrack->HasAlpha());
2690 nsContentUtils::CombineResourcePrincipals(
2691 &mSrcStreamVideoPrincipal, mSelectedVideoStreamTrack->GetPrincipal());
2695 // The set of enabled/selected tracks changed.
2696 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources);
2699 void HTMLMediaElement::NotifyMediaTrackDisabled(dom::MediaTrack* aTrack) {
2700 MOZ_ASSERT(aTrack);
2701 if (!aTrack) {
2702 return;
2705 nsString id;
2706 aTrack->GetId(id);
2708 LOG(LogLevel::Debug, ("MediaElement %p %sTrack with id %s disabled", this,
2709 aTrack->AsAudioTrack() ? "Audio" : "Video",
2710 NS_ConvertUTF16toUTF8(id).get()));
2712 MOZ_ASSERT((!aTrack->AsAudioTrack() || !aTrack->AsAudioTrack()->Enabled()) &&
2713 (!aTrack->AsVideoTrack() || !aTrack->AsVideoTrack()->Selected()));
2715 if (AudioTrack* t = aTrack->AsAudioTrack()) {
2716 if (mSrcStream) {
2717 if (mMediaStreamRenderer) {
2718 mMediaStreamRenderer->RemoveTrack(t->GetAudioStreamTrack());
2721 // If we don't have any live tracks, we don't need to mute MediaElement.
2722 MOZ_DIAGNOSTIC_ASSERT(AudioTracks(), "Element can't have been unlinked");
2723 if (AudioTracks()->Length() > 0) {
2724 bool shouldMute = true;
2725 for (uint32_t i = 0; i < AudioTracks()->Length(); ++i) {
2726 if ((*AudioTracks())[i]->Enabled()) {
2727 shouldMute = false;
2728 break;
2732 if (shouldMute) {
2733 SetMutedInternal(mMuted | MUTED_BY_AUDIO_TRACK);
2736 } else if (aTrack->AsVideoTrack()) {
2737 if (mSrcStream) {
2738 MOZ_DIAGNOSTIC_ASSERT(mSelectedVideoStreamTrack ==
2739 aTrack->AsVideoTrack()->GetVideoStreamTrack());
2740 if (mMediaStreamRenderer) {
2741 mMediaStreamRenderer->RemoveTrack(mSelectedVideoStreamTrack);
2743 if (mSecondaryMediaStreamRenderer) {
2744 mSecondaryMediaStreamRenderer->RemoveTrack(mSelectedVideoStreamTrack);
2746 mSelectedVideoStreamTrack->RemovePrincipalChangeObserver(this);
2747 mSelectedVideoStreamTrack = nullptr;
2751 // The set of enabled/selected tracks changed.
2752 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources);
2755 void HTMLMediaElement::DealWithFailedElement(nsIContent* aSourceElement) {
2756 if (mShuttingDown) {
2757 return;
2760 DispatchAsyncSourceError(aSourceElement);
2761 mMainThreadEventTarget->Dispatch(
2762 NewRunnableMethod("HTMLMediaElement::QueueLoadFromSourceTask", this,
2763 &HTMLMediaElement::QueueLoadFromSourceTask));
2766 void HTMLMediaElement::LoadFromSourceChildren() {
2767 NS_ASSERTION(mDelayingLoadEvent,
2768 "Should delay load event (if in document) during load");
2769 NS_ASSERTION(mIsLoadingFromSourceChildren,
2770 "Must remember we're loading from source children");
2772 AddMutationObserverUnlessExists(this);
2774 RemoveMediaTracks();
2776 while (true) {
2777 Element* child = GetNextSource();
2778 if (!child) {
2779 // Exhausted candidates, wait for more candidates to be appended to
2780 // the media element.
2781 mLoadWaitStatus = WAITING_FOR_SOURCE;
2782 ChangeNetworkState(NETWORK_NO_SOURCE);
2783 ChangeDelayLoadStatus(false);
2784 ReportLoadError("MediaLoadExhaustedCandidates");
2785 return;
2788 // Must have src attribute.
2789 nsAutoString src;
2790 if (!child->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
2791 ReportLoadError("MediaLoadSourceMissingSrc");
2792 DealWithFailedElement(child);
2793 return;
2796 // If we have a type attribute, it must be a supported type.
2797 nsAutoString type;
2798 if (child->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type) &&
2799 !type.IsEmpty()) {
2800 DecoderDoctorDiagnostics diagnostics;
2801 CanPlayStatus canPlay = GetCanPlay(type, &diagnostics);
2802 diagnostics.StoreFormatDiagnostics(OwnerDoc(), type,
2803 canPlay != CANPLAY_NO, __func__);
2804 if (canPlay == CANPLAY_NO) {
2805 // Check that at least one other source child exists and report that
2806 // we will try to load that one next.
2807 nsIContent* nextChild = mSourcePointer->GetNextSibling();
2808 AutoTArray<nsString, 2> params = {type, src};
2810 while (nextChild) {
2811 if (nextChild && nextChild->IsHTMLElement(nsGkAtoms::source)) {
2812 ReportLoadError("MediaLoadUnsupportedTypeAttributeLoadingNextChild",
2813 params);
2814 break;
2817 nextChild = nextChild->GetNextSibling();
2820 if (!nextChild) {
2821 ReportLoadError("MediaLoadUnsupportedTypeAttribute", params);
2824 DealWithFailedElement(child);
2825 return;
2828 HTMLSourceElement* childSrc = HTMLSourceElement::FromNode(child);
2829 LOG(LogLevel::Debug,
2830 ("%p Trying load from <source>=%s type=%s", this,
2831 NS_ConvertUTF16toUTF8(src).get(), NS_ConvertUTF16toUTF8(type).get()));
2833 nsCOMPtr<nsIURI> uri;
2834 NewURIFromString(src, getter_AddRefs(uri));
2835 if (!uri) {
2836 AutoTArray<nsString, 1> params = {src};
2837 ReportLoadError("MediaLoadInvalidURI", params);
2838 DealWithFailedElement(child);
2839 return;
2842 RemoveMediaElementFromURITable();
2843 mLoadingSrc = uri;
2844 mLoadingSrcTriggeringPrincipal = childSrc->GetSrcTriggeringPrincipal();
2845 DDLOG(DDLogCategory::Property, "loading_src",
2846 nsCString(NS_ConvertUTF16toUTF8(src)));
2847 bool hadMediaSource = !!mMediaSource;
2848 mMediaSource = childSrc->GetSrcMediaSource();
2849 if (mMediaSource && !hadMediaSource) {
2850 OwnerDoc()->AddMediaElementWithMSE();
2852 DDLINKCHILD("mediasource", mMediaSource.get());
2853 NS_ASSERTION(mNetworkState == NETWORK_LOADING,
2854 "Network state should be loading");
2856 if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE && !mMediaSource) {
2857 // preload:none media, suspend the load here before we make any
2858 // network requests.
2859 SuspendLoad();
2860 return;
2863 if (NS_SUCCEEDED(LoadResource())) {
2864 return;
2867 // If we fail to load, loop back and try loading the next resource.
2868 DispatchAsyncSourceError(child);
2870 MOZ_ASSERT_UNREACHABLE("Execution should not reach here!");
2873 void HTMLMediaElement::SuspendLoad() {
2874 mSuspendedForPreloadNone = true;
2875 ChangeNetworkState(NETWORK_IDLE);
2876 ChangeDelayLoadStatus(false);
2879 void HTMLMediaElement::ResumeLoad(PreloadAction aAction) {
2880 NS_ASSERTION(mSuspendedForPreloadNone,
2881 "Must be halted for preload:none to resume from preload:none "
2882 "suspended load.");
2883 mSuspendedForPreloadNone = false;
2884 mPreloadAction = aAction;
2885 ChangeDelayLoadStatus(true);
2886 ChangeNetworkState(NETWORK_LOADING);
2887 if (!mIsLoadingFromSourceChildren) {
2888 // We were loading from the element's src attribute.
2889 MediaResult rv = LoadResource();
2890 if (NS_FAILED(rv)) {
2891 NoSupportedMediaSourceError(rv.Description());
2893 } else {
2894 // We were loading from a child <source> element. Try to resume the
2895 // load of that child, and if that fails, try the next child.
2896 if (NS_FAILED(LoadResource())) {
2897 LoadFromSourceChildren();
2902 bool HTMLMediaElement::AllowedToPlay() const {
2903 return AutoplayPolicy::IsAllowedToPlay(*this);
2906 uint32_t HTMLMediaElement::GetPreloadDefault() const {
2907 if (mMediaSource) {
2908 return HTMLMediaElement::PRELOAD_ATTR_METADATA;
2910 if (OnCellularConnection()) {
2911 return Preferences::GetInt("media.preload.default.cellular",
2912 HTMLMediaElement::PRELOAD_ATTR_NONE);
2914 return Preferences::GetInt("media.preload.default",
2915 HTMLMediaElement::PRELOAD_ATTR_METADATA);
2918 uint32_t HTMLMediaElement::GetPreloadDefaultAuto() const {
2919 if (OnCellularConnection()) {
2920 return Preferences::GetInt("media.preload.auto.cellular",
2921 HTMLMediaElement::PRELOAD_ATTR_METADATA);
2923 return Preferences::GetInt("media.preload.auto",
2924 HTMLMediaElement::PRELOAD_ENOUGH);
2927 void HTMLMediaElement::UpdatePreloadAction() {
2928 PreloadAction nextAction = PRELOAD_UNDEFINED;
2929 // If autoplay is set, or we're playing, we should always preload data,
2930 // as we'll need it to play.
2931 if ((AllowedToPlay() && HasAttr(nsGkAtoms::autoplay)) || !mPaused) {
2932 nextAction = HTMLMediaElement::PRELOAD_ENOUGH;
2933 } else {
2934 // Find the appropriate preload action by looking at the attribute.
2935 const nsAttrValue* val =
2936 mAttrs.GetAttr(nsGkAtoms::preload, kNameSpaceID_None);
2937 // MSE doesn't work if preload is none, so it ignores the pref when src is
2938 // from MSE.
2939 uint32_t preloadDefault = GetPreloadDefault();
2940 uint32_t preloadAuto = GetPreloadDefaultAuto();
2941 if (!val) {
2942 // Attribute is not set. Use the preload action specified by the
2943 // media.preload.default pref, or just preload metadata if not present.
2944 nextAction = static_cast<PreloadAction>(preloadDefault);
2945 } else if (val->Type() == nsAttrValue::eEnum) {
2946 PreloadAttrValue attr =
2947 static_cast<PreloadAttrValue>(val->GetEnumValue());
2948 if (attr == HTMLMediaElement::PRELOAD_ATTR_EMPTY ||
2949 attr == HTMLMediaElement::PRELOAD_ATTR_AUTO) {
2950 nextAction = static_cast<PreloadAction>(preloadAuto);
2951 } else if (attr == HTMLMediaElement::PRELOAD_ATTR_METADATA) {
2952 nextAction = HTMLMediaElement::PRELOAD_METADATA;
2953 } else if (attr == HTMLMediaElement::PRELOAD_ATTR_NONE) {
2954 nextAction = HTMLMediaElement::PRELOAD_NONE;
2956 } else {
2957 // Use the suggested "missing value default" of "metadata", or the value
2958 // specified by the media.preload.default, if present.
2959 nextAction = static_cast<PreloadAction>(preloadDefault);
2963 if (nextAction == HTMLMediaElement::PRELOAD_NONE && mIsDoingExplicitLoad) {
2964 nextAction = HTMLMediaElement::PRELOAD_METADATA;
2967 mPreloadAction = nextAction;
2969 if (nextAction == HTMLMediaElement::PRELOAD_ENOUGH) {
2970 if (mSuspendedForPreloadNone) {
2971 // Our load was previouly suspended due to the media having preload
2972 // value "none". The preload value has changed to preload:auto, so
2973 // resume the load.
2974 ResumeLoad(PRELOAD_ENOUGH);
2975 } else {
2976 // Preload as much of the video as we can, i.e. don't suspend after
2977 // the first frame.
2978 StopSuspendingAfterFirstFrame();
2981 } else if (nextAction == HTMLMediaElement::PRELOAD_METADATA) {
2982 // Ensure that the video can be suspended after first frame.
2983 mAllowSuspendAfterFirstFrame = true;
2984 if (mSuspendedForPreloadNone) {
2985 // Our load was previouly suspended due to the media having preload
2986 // value "none". The preload value has changed to preload:metadata, so
2987 // resume the load. We'll pause the load again after we've read the
2988 // metadata.
2989 ResumeLoad(PRELOAD_METADATA);
2994 MediaResult HTMLMediaElement::LoadResource() {
2995 NS_ASSERTION(mDelayingLoadEvent,
2996 "Should delay load event (if in document) during load");
2998 if (mChannelLoader) {
2999 mChannelLoader->Cancel();
3000 mChannelLoader = nullptr;
3003 // Set the media element's CORS mode only when loading a resource
3004 mCORSMode = AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin));
3006 HTMLMediaElement* other = LookupMediaElementURITable(mLoadingSrc);
3007 if (other && other->mDecoder) {
3008 // Clone it.
3009 // TODO: remove the cast by storing ChannelMediaDecoder in the URI table.
3010 nsresult rv = InitializeDecoderAsClone(
3011 static_cast<ChannelMediaDecoder*>(other->mDecoder.get()));
3012 if (NS_SUCCEEDED(rv)) return rv;
3015 if (mMediaSource) {
3016 MediaDecoderInit decoderInit(
3017 this, this, mMuted ? 0.0 : mVolume, mPreservesPitch,
3018 ClampPlaybackRate(mPlaybackRate),
3019 mPreloadAction == HTMLMediaElement::PRELOAD_METADATA, mHasSuspendTaint,
3020 HasAttr(kNameSpaceID_None, nsGkAtoms::loop),
3021 MediaContainerType(MEDIAMIMETYPE("application/x.mediasource")));
3023 RefPtr<MediaSourceDecoder> decoder = new MediaSourceDecoder(decoderInit);
3024 if (!mMediaSource->Attach(decoder)) {
3025 // TODO: Handle failure: run "If the media data cannot be fetched at
3026 // all, due to network errors, causing the user agent to give up
3027 // trying to fetch the resource" section of resource fetch algorithm.
3028 decoder->Shutdown();
3029 return MediaResult(NS_ERROR_FAILURE, "Failed to attach MediaSource");
3031 ChangeDelayLoadStatus(false);
3032 nsresult rv = decoder->Load(mMediaSource->GetPrincipal());
3033 if (NS_FAILED(rv)) {
3034 decoder->Shutdown();
3035 LOG(LogLevel::Debug,
3036 ("%p Failed to load for decoder %p", this, decoder.get()));
3037 return MediaResult(rv, "Fail to load decoder");
3039 rv = FinishDecoderSetup(decoder);
3040 return MediaResult(rv, "Failed to set up decoder");
3043 AssertReadyStateIsNothing();
3045 RefPtr<ChannelLoader> loader = new ChannelLoader;
3046 nsresult rv = loader->Load(this);
3047 if (NS_SUCCEEDED(rv)) {
3048 mChannelLoader = std::move(loader);
3050 return MediaResult(rv, "Failed to load channel");
3053 nsresult HTMLMediaElement::LoadWithChannel(nsIChannel* aChannel,
3054 nsIStreamListener** aListener) {
3055 NS_ENSURE_ARG_POINTER(aChannel);
3056 NS_ENSURE_ARG_POINTER(aListener);
3058 *aListener = nullptr;
3060 // Make sure we don't reenter during synchronous abort events.
3061 if (mIsRunningLoadMethod) return NS_OK;
3062 mIsRunningLoadMethod = true;
3063 AbortExistingLoads();
3064 mIsRunningLoadMethod = false;
3066 mLoadingSrcTriggeringPrincipal = nullptr;
3067 nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(mLoadingSrc));
3068 NS_ENSURE_SUCCESS(rv, rv);
3070 ChangeDelayLoadStatus(true);
3071 rv = InitializeDecoderForChannel(aChannel, aListener);
3072 if (NS_FAILED(rv)) {
3073 ChangeDelayLoadStatus(false);
3074 return rv;
3077 SetPlaybackRate(mDefaultPlaybackRate, IgnoreErrors());
3078 DispatchAsyncEvent(u"loadstart"_ns);
3080 return NS_OK;
3083 bool HTMLMediaElement::Seeking() const {
3084 return mDecoder && mDecoder->IsSeeking();
3087 double HTMLMediaElement::CurrentTime() const {
3088 if (mMediaStreamRenderer) {
3089 return mMediaStreamRenderer->CurrentTime();
3092 if (mDefaultPlaybackStartPosition == 0.0 && mDecoder) {
3093 return mDecoder->GetCurrentTime();
3096 return mDefaultPlaybackStartPosition;
3099 void HTMLMediaElement::FastSeek(double aTime, ErrorResult& aRv) {
3100 LOG(LogLevel::Debug, ("%p FastSeek(%f) called by JS", this, aTime));
3101 Seek(aTime, SeekTarget::PrevSyncPoint, IgnoreErrors());
3104 already_AddRefed<Promise> HTMLMediaElement::SeekToNextFrame(ErrorResult& aRv) {
3105 /* This will cause JIT code to be kept around longer, to help performance
3106 * when using SeekToNextFrame to iterate through every frame of a video.
3108 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
3110 if (win) {
3111 if (JSObject* obj = win->AsGlobal()->GetGlobalJSObject()) {
3112 js::NotifyAnimationActivity(obj);
3116 Seek(CurrentTime(), SeekTarget::NextFrame, aRv);
3117 if (aRv.Failed()) {
3118 return nullptr;
3121 mSeekDOMPromise = CreateDOMPromise(aRv);
3122 if (NS_WARN_IF(aRv.Failed())) {
3123 return nullptr;
3126 return do_AddRef(mSeekDOMPromise);
3129 void HTMLMediaElement::SetCurrentTime(double aCurrentTime, ErrorResult& aRv) {
3130 LOG(LogLevel::Debug,
3131 ("%p SetCurrentTime(%f) called by JS", this, aCurrentTime));
3132 Seek(aCurrentTime, SeekTarget::Accurate, IgnoreErrors());
3136 * Check if aValue is inside a range of aRanges, and if so returns true
3137 * and puts the range index in aIntervalIndex. If aValue is not
3138 * inside a range, returns false, and aIntervalIndex
3139 * is set to the index of the range which starts immediately after aValue
3140 * (and can be aRanges.Length() if aValue is after the last range).
3142 static bool IsInRanges(TimeRanges& aRanges, double aValue,
3143 uint32_t& aIntervalIndex) {
3144 uint32_t length = aRanges.Length();
3146 for (uint32_t i = 0; i < length; i++) {
3147 double start = aRanges.Start(i);
3148 if (start > aValue) {
3149 aIntervalIndex = i;
3150 return false;
3152 double end = aRanges.End(i);
3153 if (aValue <= end) {
3154 aIntervalIndex = i;
3155 return true;
3158 aIntervalIndex = length;
3159 return false;
3162 void HTMLMediaElement::Seek(double aTime, SeekTarget::Type aSeekType,
3163 ErrorResult& aRv) {
3164 // Note: Seek is called both by synchronous code that expects errors thrown in
3165 // aRv, as well as asynchronous code that expects a promise. Make sure all
3166 // synchronous errors are returned using aRv, not promise rejections.
3168 // aTime should be non-NaN.
3169 MOZ_ASSERT(!mozilla::IsNaN(aTime));
3171 // Seeking step1, Set the media element's show poster flag to false.
3172 // https://html.spec.whatwg.org/multipage/media.html#dom-media-seek
3173 mShowPoster = false;
3175 // Detect if user has interacted with element by seeking so that
3176 // play will not be blocked when initiated by a script.
3177 if (UserActivation::IsHandlingUserInput()) {
3178 mIsBlessed = true;
3181 StopSuspendingAfterFirstFrame();
3183 if (mSrcAttrStream) {
3184 // do nothing since media streams have an empty Seekable range.
3185 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
3186 return;
3189 if (mPlayed && mCurrentPlayRangeStart != -1.0) {
3190 double rangeEndTime = CurrentTime();
3191 LOG(LogLevel::Debug, ("%p Adding \'played\' a range : [%f, %f]", this,
3192 mCurrentPlayRangeStart, rangeEndTime));
3193 // Multiple seek without playing, or seek while playing.
3194 if (mCurrentPlayRangeStart != rangeEndTime) {
3195 mPlayed->Add(mCurrentPlayRangeStart, rangeEndTime);
3197 // Reset the current played range start time. We'll re-set it once
3198 // the seek completes.
3199 mCurrentPlayRangeStart = -1.0;
3202 if (mReadyState == HAVE_NOTHING) {
3203 mDefaultPlaybackStartPosition = aTime;
3204 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
3205 return;
3208 if (!mDecoder) {
3209 // mDecoder must always be set in order to reach this point.
3210 NS_ASSERTION(mDecoder, "SetCurrentTime failed: no decoder");
3211 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
3212 return;
3215 // Clamp the seek target to inside the seekable ranges.
3216 media::TimeIntervals seekableIntervals = mDecoder->GetSeekable();
3217 if (seekableIntervals.IsInvalid()) {
3218 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
3219 return;
3221 RefPtr<TimeRanges> seekable =
3222 new TimeRanges(ToSupports(OwnerDoc()), seekableIntervals);
3223 uint32_t length = seekable->Length();
3224 if (length == 0) {
3225 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
3226 return;
3229 // If the position we want to seek to is not in a seekable range, we seek
3230 // to the closest position in the seekable ranges instead. If two positions
3231 // are equally close, we seek to the closest position from the currentTime.
3232 // See seeking spec, point 7 :
3233 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#seeking
3234 uint32_t range = 0;
3235 bool isInRange = IsInRanges(*seekable, aTime, range);
3236 if (!isInRange) {
3237 if (range == 0) {
3238 // aTime is before the first range in |seekable|, the closest point we can
3239 // seek to is the start of the first range.
3240 aTime = seekable->Start(0);
3241 } else if (range == length) {
3242 // Seek target is after the end last range in seekable data.
3243 // Clamp the seek target to the end of the last seekable range.
3244 aTime = seekable->End(length - 1);
3245 } else {
3246 double leftBound = seekable->End(range - 1);
3247 double rightBound = seekable->Start(range);
3248 double distanceLeft = Abs(leftBound - aTime);
3249 double distanceRight = Abs(rightBound - aTime);
3250 if (distanceLeft == distanceRight) {
3251 double currentTime = CurrentTime();
3252 distanceLeft = Abs(leftBound - currentTime);
3253 distanceRight = Abs(rightBound - currentTime);
3255 aTime = (distanceLeft < distanceRight) ? leftBound : rightBound;
3259 // TODO: The spec requires us to update the current time to reflect the
3260 // actual seek target before beginning the synchronous section, but
3261 // that requires changing all MediaDecoderReaders to support telling
3262 // us the fastSeek target, and it's currently not possible to get
3263 // this information as we don't yet control the demuxer for all
3264 // MediaDecoderReaders.
3266 mPlayingBeforeSeek = IsPotentiallyPlaying();
3268 // The media backend is responsible for dispatching the timeupdate
3269 // event if it changes the playback position as a result of the seek.
3270 LOG(LogLevel::Debug, ("%p SetCurrentTime(%f) starting seek", this, aTime));
3271 mDecoder->Seek(aTime, aSeekType);
3273 // We changed whether we're seeking so we need to AddRemoveSelfReference.
3274 AddRemoveSelfReference();
3277 double HTMLMediaElement::Duration() const {
3278 if (mSrcStream) {
3279 if (mSrcStreamPlaybackEnded) {
3280 return CurrentTime();
3282 return std::numeric_limits<double>::infinity();
3285 if (mDecoder) {
3286 return mDecoder->GetDuration();
3289 return std::numeric_limits<double>::quiet_NaN();
3292 already_AddRefed<TimeRanges> HTMLMediaElement::Seekable() const {
3293 media::TimeIntervals seekable =
3294 mDecoder ? mDecoder->GetSeekable() : media::TimeIntervals();
3295 RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()), seekable);
3296 return ranges.forget();
3299 already_AddRefed<TimeRanges> HTMLMediaElement::Played() {
3300 RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()));
3302 uint32_t timeRangeCount = 0;
3303 if (mPlayed) {
3304 timeRangeCount = mPlayed->Length();
3306 for (uint32_t i = 0; i < timeRangeCount; i++) {
3307 double begin = mPlayed->Start(i);
3308 double end = mPlayed->End(i);
3309 ranges->Add(begin, end);
3312 if (mCurrentPlayRangeStart != -1.0) {
3313 double now = CurrentTime();
3314 if (mCurrentPlayRangeStart != now) {
3315 ranges->Add(mCurrentPlayRangeStart, now);
3319 ranges->Normalize();
3320 return ranges.forget();
3323 void HTMLMediaElement::Pause(ErrorResult& aRv) {
3324 LOG(LogLevel::Debug, ("%p Pause() called by JS", this));
3325 if (mNetworkState == NETWORK_EMPTY) {
3326 LOG(LogLevel::Debug, ("Loading due to Pause()"));
3327 DoLoad();
3329 PauseInternal();
3332 void HTMLMediaElement::PauseInternal() {
3333 if (mDecoder && mNetworkState != NETWORK_EMPTY) {
3334 mDecoder->Pause();
3336 bool oldPaused = mPaused;
3337 mPaused = true;
3338 mAutoplaying = false;
3339 // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
3340 AddRemoveSelfReference();
3341 UpdateSrcMediaStreamPlaying();
3342 if (mAudioChannelWrapper) {
3343 mAudioChannelWrapper->NotifyPlayStateChanged();
3346 // We don't need to resume media which is paused explicitly by user.
3347 ClearResumeDelayedMediaPlaybackAgentIfNeeded();
3349 if (!oldPaused) {
3350 FireTimeUpdate(TimeupdateType::eMandatory);
3351 DispatchAsyncEvent(u"pause"_ns);
3352 AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_ABORT_ERR);
3356 void HTMLMediaElement::SetVolume(double aVolume, ErrorResult& aRv) {
3357 LOG(LogLevel::Debug, ("%p SetVolume(%f) called by JS", this, aVolume));
3359 if (aVolume < 0.0 || aVolume > 1.0) {
3360 aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
3361 return;
3364 if (aVolume == mVolume) return;
3366 mVolume = aVolume;
3368 // Here we want just to update the volume.
3369 SetVolumeInternal();
3371 DispatchAsyncEvent(u"volumechange"_ns);
3373 // We allow inaudible autoplay. But changing our volume may make this
3374 // media audible. So pause if we are no longer supposed to be autoplaying.
3375 PauseIfShouldNotBePlaying();
3378 void HTMLMediaElement::MozGetMetadata(JSContext* aCx,
3379 JS::MutableHandle<JSObject*> aResult,
3380 ErrorResult& aRv) {
3381 if (mReadyState < HAVE_METADATA) {
3382 aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
3383 return;
3386 JS::Rooted<JSObject*> tags(aCx, JS_NewPlainObject(aCx));
3387 if (!tags) {
3388 aRv.Throw(NS_ERROR_FAILURE);
3389 return;
3391 if (mTags) {
3392 for (const auto& entry : *mTags) {
3393 nsString wideValue;
3394 CopyUTF8toUTF16(entry.GetData(), wideValue);
3395 JS::Rooted<JSString*> string(aCx,
3396 JS_NewUCStringCopyZ(aCx, wideValue.Data()));
3397 if (!string || !JS_DefineProperty(aCx, tags, entry.GetKey().Data(),
3398 string, JSPROP_ENUMERATE)) {
3399 NS_WARNING("couldn't create metadata object!");
3400 aRv.Throw(NS_ERROR_FAILURE);
3401 return;
3406 aResult.set(tags);
3409 void HTMLMediaElement::SetMutedInternal(uint32_t aMuted) {
3410 uint32_t oldMuted = mMuted;
3411 mMuted = aMuted;
3413 if (!!aMuted == !!oldMuted) {
3414 return;
3417 SetVolumeInternal();
3420 void HTMLMediaElement::PauseIfShouldNotBePlaying() {
3421 if (GetPaused()) {
3422 return;
3424 if (!AllowedToPlay()) {
3425 AUTOPLAY_LOG("pause because not allowed to play, element=%p", this);
3426 ErrorResult rv;
3427 Pause(rv);
3431 void HTMLMediaElement::SetVolumeInternal() {
3432 float effectiveVolume = ComputedVolume();
3434 if (mDecoder) {
3435 mDecoder->SetVolume(effectiveVolume);
3436 } else if (mMediaStreamRenderer) {
3437 mMediaStreamRenderer->SetAudioOutputVolume(effectiveVolume);
3440 NotifyAudioPlaybackChanged(
3441 AudioChannelService::AudibleChangedReasons::eVolumeChanged);
3444 void HTMLMediaElement::SetMuted(bool aMuted) {
3445 LOG(LogLevel::Debug, ("%p SetMuted(%d) called by JS", this, aMuted));
3446 if (aMuted == Muted()) {
3447 return;
3450 if (aMuted) {
3451 SetMutedInternal(mMuted | MUTED_BY_CONTENT);
3452 } else {
3453 SetMutedInternal(mMuted & ~MUTED_BY_CONTENT);
3456 DispatchAsyncEvent(u"volumechange"_ns);
3458 // We allow inaudible autoplay. But changing our mute status may make this
3459 // media audible. So pause if we are no longer supposed to be autoplaying.
3460 PauseIfShouldNotBePlaying();
3463 void HTMLMediaElement::GetAllEnabledMediaTracks(
3464 nsTArray<RefPtr<MediaTrack>>& aTracks) {
3465 if (AudioTrackList* tracks = AudioTracks()) {
3466 for (size_t i = 0; i < tracks->Length(); ++i) {
3467 AudioTrack* track = (*tracks)[i];
3468 if (track->Enabled()) {
3469 aTracks.AppendElement(track);
3473 if (IsVideo()) {
3474 if (VideoTrackList* tracks = VideoTracks()) {
3475 for (size_t i = 0; i < tracks->Length(); ++i) {
3476 VideoTrack* track = (*tracks)[i];
3477 if (track->Selected()) {
3478 aTracks.AppendElement(track);
3485 void HTMLMediaElement::SetCapturedOutputStreamsEnabled(bool aEnabled) {
3486 for (const auto& entry : mOutputTrackSources.Values()) {
3487 entry->SetEnabled(aEnabled);
3491 HTMLMediaElement::OutputMuteState HTMLMediaElement::OutputTracksMuted() {
3492 return mPaused || mReadyState <= HAVE_CURRENT_DATA ? OutputMuteState::Muted
3493 : OutputMuteState::Unmuted;
3496 void HTMLMediaElement::UpdateOutputTracksMuting() {
3497 for (const auto& entry : mOutputTrackSources.Values()) {
3498 entry->SetMutedByElement(OutputTracksMuted());
3502 void HTMLMediaElement::AddOutputTrackSourceToOutputStream(
3503 MediaElementTrackSource* aSource, OutputMediaStream& aOutputStream,
3504 AddTrackMode aMode) {
3505 if (aOutputStream.mStream == mSrcStream) {
3506 // Cycle detected. This can happen since tracks are added async.
3507 // We avoid forwarding it to the output here or we'd get into an infloop.
3508 LOG(LogLevel::Warning,
3509 ("NOT adding output track source %p to output stream "
3510 "%p -- cycle detected",
3511 aSource, aOutputStream.mStream.get()));
3512 return;
3515 LOG(LogLevel::Debug, ("Adding output track source %p to output stream %p",
3516 aSource, aOutputStream.mStream.get()));
3518 RefPtr<MediaStreamTrack> domTrack;
3519 if (aSource->Track()->mType == MediaSegment::AUDIO) {
3520 domTrack = new AudioStreamTrack(
3521 aOutputStream.mStream->GetOwner(), aSource->Track(), aSource,
3522 MediaStreamTrackState::Live, aSource->Muted());
3523 } else {
3524 domTrack = new VideoStreamTrack(
3525 aOutputStream.mStream->GetOwner(), aSource->Track(), aSource,
3526 MediaStreamTrackState::Live, aSource->Muted());
3529 aOutputStream.mLiveTracks.AppendElement(domTrack);
3531 switch (aMode) {
3532 case AddTrackMode::ASYNC:
3533 mMainThreadEventTarget->Dispatch(
3534 NewRunnableMethod<StoreRefPtrPassByPtr<MediaStreamTrack>>(
3535 "DOMMediaStream::AddTrackInternal", aOutputStream.mStream,
3536 &DOMMediaStream::AddTrackInternal, domTrack));
3537 break;
3538 case AddTrackMode::SYNC:
3539 aOutputStream.mStream->AddTrackInternal(domTrack);
3540 break;
3541 default:
3542 MOZ_CRASH("Unexpected mode");
3545 LOG(LogLevel::Debug,
3546 ("Created capture %s track %p",
3547 domTrack->AsAudioStreamTrack() ? "audio" : "video", domTrack.get()));
3550 void HTMLMediaElement::UpdateOutputTrackSources() {
3551 // This updates the track sources in mOutputTrackSources so they're in sync
3552 // with the tracks being currently played, and state saying whether we should
3553 // be capturing tracks. This method is long so here is a breakdown:
3554 // - Figure out the tracks that should be captured
3555 // - Diff those against currently captured tracks (mOutputTrackSources), into
3556 // tracks-to-add, and tracks-to-remove
3557 // - Remove the tracks in tracks-to-remove and dispatch "removetrack" and
3558 // "ended" events for them
3559 // - If playback has ended, or there is no longer a media provider object,
3560 // remove any OutputMediaStreams that have the finish-when-ended flag set
3561 // - Create track sources for, and add to OutputMediaStreams, the tracks in
3562 // tracks-to-add
3564 const bool shouldHaveTrackSources = mTracksCaptured.Ref() &&
3565 !IsPlaybackEnded() &&
3566 mReadyState >= HAVE_METADATA;
3568 // Add track sources for all enabled/selected MediaTracks.
3569 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
3570 if (!window) {
3571 return;
3574 if (mDecoder) {
3575 if (!mTracksCaptured.Ref()) {
3576 mDecoder->SetOutputCaptureState(MediaDecoder::OutputCaptureState::None);
3577 } else if (!AudioTracks() || !VideoTracks() || !shouldHaveTrackSources) {
3578 // We've been unlinked, or tracks are not yet known.
3579 mDecoder->SetOutputCaptureState(MediaDecoder::OutputCaptureState::Halt);
3580 } else {
3581 mDecoder->SetOutputCaptureState(MediaDecoder::OutputCaptureState::Capture,
3582 mTracksCaptured.Ref().get());
3586 // Start with all MediaTracks
3587 AutoTArray<RefPtr<MediaTrack>, 4> mediaTracksToAdd;
3588 if (shouldHaveTrackSources) {
3589 GetAllEnabledMediaTracks(mediaTracksToAdd);
3592 // ...and all MediaElementTrackSources.
3593 auto trackSourcesToRemove =
3594 ToTArray<AutoTArray<nsString, 4>>(mOutputTrackSources.Keys());
3596 // Then work out the differences.
3597 mediaTracksToAdd.RemoveLastElements(
3598 mediaTracksToAdd.end() -
3599 std::remove_if(mediaTracksToAdd.begin(), mediaTracksToAdd.end(),
3600 [this, &trackSourcesToRemove](const auto& track) {
3601 const bool remove =
3602 mOutputTrackSources.GetWeak(track->GetId());
3603 if (remove) {
3604 trackSourcesToRemove.RemoveElement(track->GetId());
3606 return remove;
3607 }));
3609 // First remove stale track sources.
3610 for (const auto& id : trackSourcesToRemove) {
3611 RefPtr<MediaElementTrackSource> source = mOutputTrackSources.GetWeak(id);
3613 LOG(LogLevel::Debug, ("Removing output track source %p for track %s",
3614 source.get(), NS_ConvertUTF16toUTF8(id).get()));
3616 if (mDecoder) {
3617 mDecoder->RemoveOutputTrack(source->Track());
3620 // The source of this track just ended. Force-notify that it ended.
3621 // If we bounce it to the MediaTrackGraph it might not be picked up,
3622 // for instance if the MediaInputPort was destroyed in the same
3623 // iteration as it was added.
3624 mMainThreadEventTarget->Dispatch(
3625 NewRunnableMethod("MediaElementTrackSource::OverrideEnded", source,
3626 &MediaElementTrackSource::OverrideEnded));
3628 // Remove the track from the MediaStream after it ended.
3629 for (OutputMediaStream& ms : mOutputStreams) {
3630 if (source->Track()->mType == MediaSegment::VIDEO &&
3631 ms.mCapturingAudioOnly) {
3632 continue;
3634 DebugOnly<size_t> length = ms.mLiveTracks.Length();
3635 ms.mLiveTracks.RemoveElementsBy(
3636 [&](const RefPtr<MediaStreamTrack>& aTrack) {
3637 if (&aTrack->GetSource() != source) {
3638 return false;
3640 mMainThreadEventTarget->Dispatch(
3641 NewRunnableMethod<RefPtr<MediaStreamTrack>>(
3642 "DOMMediaStream::RemoveTrackInternal", ms.mStream,
3643 &DOMMediaStream::RemoveTrackInternal, aTrack));
3644 return true;
3646 MOZ_ASSERT(ms.mLiveTracks.Length() == length - 1);
3649 mOutputTrackSources.Remove(id);
3652 // Then update finish-when-ended output streams as needed.
3653 for (size_t i = mOutputStreams.Length(); i-- > 0;) {
3654 if (!mOutputStreams[i].mFinishWhenEnded) {
3655 continue;
3658 if (!mOutputStreams[i].mFinishWhenEndedLoadingSrc &&
3659 !mOutputStreams[i].mFinishWhenEndedAttrStream &&
3660 !mOutputStreams[i].mFinishWhenEndedMediaSource) {
3661 // This finish-when-ended stream has not seen any source loaded yet.
3662 // Update the loading src if it's time.
3663 if (!IsPlaybackEnded()) {
3664 if (mLoadingSrc) {
3665 mOutputStreams[i].mFinishWhenEndedLoadingSrc = mLoadingSrc;
3666 } else if (mSrcAttrStream) {
3667 mOutputStreams[i].mFinishWhenEndedAttrStream = mSrcAttrStream;
3668 } else if (mSrcMediaSource) {
3669 mOutputStreams[i].mFinishWhenEndedMediaSource = mSrcMediaSource;
3672 continue;
3675 // Discard finish-when-ended output streams with a loading src set as
3676 // needed.
3677 if (!IsPlaybackEnded() &&
3678 mLoadingSrc == mOutputStreams[i].mFinishWhenEndedLoadingSrc) {
3679 continue;
3681 if (!IsPlaybackEnded() &&
3682 mSrcAttrStream == mOutputStreams[i].mFinishWhenEndedAttrStream) {
3683 continue;
3685 if (!IsPlaybackEnded() &&
3686 mSrcMediaSource == mOutputStreams[i].mFinishWhenEndedMediaSource) {
3687 continue;
3689 LOG(LogLevel::Debug,
3690 ("Playback ended or source changed. Discarding stream %p",
3691 mOutputStreams[i].mStream.get()));
3692 mOutputStreams.RemoveElementAt(i);
3693 if (mOutputStreams.IsEmpty()) {
3694 mTracksCaptured = nullptr;
3695 // mTracksCaptured is one of the Watchables triggering this method.
3696 // Unsetting it here means we'll run through this method again very soon.
3697 return;
3701 // Finally add new MediaTracks.
3702 for (const auto& mediaTrack : mediaTracksToAdd) {
3703 nsAutoString id;
3704 mediaTrack->GetId(id);
3706 MediaSegment::Type type;
3707 if (mediaTrack->AsAudioTrack()) {
3708 type = MediaSegment::AUDIO;
3709 } else if (mediaTrack->AsVideoTrack()) {
3710 type = MediaSegment::VIDEO;
3711 } else {
3712 MOZ_CRASH("Unknown track type");
3715 RefPtr<ProcessedMediaTrack> track;
3716 RefPtr<MediaElementTrackSource> source;
3717 if (mDecoder) {
3718 track = mTracksCaptured.Ref()->mTrack->Graph()->CreateForwardedInputTrack(
3719 type);
3720 RefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
3721 if (!principal || IsCORSSameOrigin()) {
3722 principal = NodePrincipal();
3724 source = MakeAndAddRef<MediaElementTrackSource>(
3725 mMainThreadEventTarget, track, principal, OutputTracksMuted(),
3726 type == MediaSegment::VIDEO
3727 ? HTMLVideoElement::FromNode(this)->HasAlpha()
3728 : false);
3729 mDecoder->AddOutputTrack(track);
3730 } else if (mSrcStream) {
3731 MediaStreamTrack* inputTrack;
3732 if (AudioTrack* t = mediaTrack->AsAudioTrack()) {
3733 inputTrack = t->GetAudioStreamTrack();
3734 } else if (VideoTrack* t = mediaTrack->AsVideoTrack()) {
3735 inputTrack = t->GetVideoStreamTrack();
3736 } else {
3737 MOZ_CRASH("Unknown track type");
3739 MOZ_ASSERT(inputTrack);
3740 if (!inputTrack) {
3741 NS_ERROR("Input track not found in source stream");
3742 return;
3744 MOZ_DIAGNOSTIC_ASSERT(!inputTrack->Ended());
3746 track = inputTrack->Graph()->CreateForwardedInputTrack(type);
3747 RefPtr<MediaInputPort> port = inputTrack->ForwardTrackContentsTo(track);
3748 source = MakeAndAddRef<MediaElementTrackSource>(
3749 mMainThreadEventTarget, inputTrack, &inputTrack->GetSource(), track,
3750 port, OutputTracksMuted());
3752 // Track is muted initially, so we don't leak data if it's added while
3753 // paused and an MTG iteration passes before the mute comes into effect.
3754 source->SetEnabled(mSrcStreamIsPlaying);
3755 } else {
3756 MOZ_CRASH("Unknown source");
3759 LOG(LogLevel::Debug, ("Adding output track source %p for track %s",
3760 source.get(), NS_ConvertUTF16toUTF8(id).get()));
3762 track->QueueSetAutoend(false);
3763 MOZ_DIAGNOSTIC_ASSERT(!mOutputTrackSources.Contains(id));
3764 mOutputTrackSources.InsertOrUpdate(id, RefPtr{source});
3766 // Add the new track source to any existing output streams
3767 for (OutputMediaStream& ms : mOutputStreams) {
3768 if (source->Track()->mType == MediaSegment::VIDEO &&
3769 ms.mCapturingAudioOnly) {
3770 // If the output stream is for audio only we ignore video sources.
3771 continue;
3773 AddOutputTrackSourceToOutputStream(source, ms);
3778 bool HTMLMediaElement::CanBeCaptured(StreamCaptureType aCaptureType) {
3779 // Don't bother capturing when the document has gone away
3780 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
3781 if (!window) {
3782 return false;
3785 // Prevent capturing restricted video
3786 if (aCaptureType == StreamCaptureType::CAPTURE_ALL_TRACKS &&
3787 ContainsRestrictedContent()) {
3788 return false;
3790 return true;
3793 already_AddRefed<DOMMediaStream> HTMLMediaElement::CaptureStreamInternal(
3794 StreamCaptureBehavior aFinishBehavior, StreamCaptureType aStreamCaptureType,
3795 MediaTrackGraph* aGraph) {
3796 MOZ_ASSERT(CanBeCaptured(aStreamCaptureType));
3798 LogVisibility(CallerAPI::CAPTURE_STREAM);
3799 MarkAsTainted();
3801 if (mTracksCaptured.Ref() &&
3802 aGraph != mTracksCaptured.Ref()->mTrack->Graph()) {
3803 return nullptr;
3806 if (!mTracksCaptured.Ref()) {
3807 // This is the first output stream, or there are no tracks. If the former,
3808 // start capturing all tracks. If the latter, they will be added later.
3809 mTracksCaptured = MakeRefPtr<SharedDummyTrack>(
3810 aGraph->CreateSourceTrack(MediaSegment::AUDIO));
3811 UpdateOutputTrackSources();
3814 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
3815 OutputMediaStream* out = mOutputStreams.EmplaceBack(
3816 MakeRefPtr<DOMMediaStream>(window),
3817 aStreamCaptureType == StreamCaptureType::CAPTURE_AUDIO,
3818 aFinishBehavior == StreamCaptureBehavior::FINISH_WHEN_ENDED);
3820 if (aFinishBehavior == StreamCaptureBehavior::FINISH_WHEN_ENDED &&
3821 !mOutputTrackSources.IsEmpty()) {
3822 // This output stream won't receive any more tracks when playback of the
3823 // current src of this media element ends, or when the src of this media
3824 // element changes. If we're currently playing something (i.e., if there are
3825 // tracks currently captured), set the current src on the output stream so
3826 // this can be tracked. If we're not playing anything,
3827 // UpdateOutputTrackSources will set the current src when it becomes
3828 // available later.
3829 if (mLoadingSrc) {
3830 out->mFinishWhenEndedLoadingSrc = mLoadingSrc;
3832 if (mSrcAttrStream) {
3833 out->mFinishWhenEndedAttrStream = mSrcAttrStream;
3835 if (mSrcMediaSource) {
3836 out->mFinishWhenEndedMediaSource = mSrcMediaSource;
3838 MOZ_ASSERT(out->mFinishWhenEndedLoadingSrc ||
3839 out->mFinishWhenEndedAttrStream ||
3840 out->mFinishWhenEndedMediaSource);
3843 if (aStreamCaptureType == StreamCaptureType::CAPTURE_AUDIO) {
3844 if (mSrcStream) {
3845 // We don't support applying volume and mute to the captured stream, when
3846 // capturing a MediaStream.
3847 ReportToConsole(nsIScriptError::errorFlag,
3848 "MediaElementAudioCaptureOfMediaStreamError");
3851 // mAudioCaptured tells the user that the audio played by this media element
3852 // is being routed to the captureStreams *instead* of being played to
3853 // speakers.
3854 mAudioCaptured = true;
3857 for (const RefPtr<MediaElementTrackSource>& source :
3858 mOutputTrackSources.Values()) {
3859 if (source->Track()->mType == MediaSegment::VIDEO) {
3860 // Only add video tracks if we're a video element and the output stream
3861 // wants video.
3862 if (!IsVideo()) {
3863 continue;
3865 if (out->mCapturingAudioOnly) {
3866 continue;
3869 AddOutputTrackSourceToOutputStream(source, *out, AddTrackMode::SYNC);
3872 return do_AddRef(out->mStream);
3875 already_AddRefed<DOMMediaStream> HTMLMediaElement::CaptureAudio(
3876 ErrorResult& aRv, MediaTrackGraph* aGraph) {
3877 MOZ_RELEASE_ASSERT(aGraph);
3879 if (!CanBeCaptured(StreamCaptureType::CAPTURE_AUDIO)) {
3880 aRv.Throw(NS_ERROR_FAILURE);
3881 return nullptr;
3884 RefPtr<DOMMediaStream> stream =
3885 CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED,
3886 StreamCaptureType::CAPTURE_AUDIO, aGraph);
3887 if (!stream) {
3888 aRv.Throw(NS_ERROR_FAILURE);
3889 return nullptr;
3892 return stream.forget();
3895 RefPtr<GenericNonExclusivePromise> HTMLMediaElement::GetAllowedToPlayPromise() {
3896 MOZ_ASSERT(NS_IsMainThread());
3897 MOZ_ASSERT(!mOutputStreams.IsEmpty(),
3898 "This method should only be called during stream capturing!");
3899 if (AllowedToPlay()) {
3900 AUTOPLAY_LOG("MediaElement %p has allowed to play, resolve promise", this);
3901 return GenericNonExclusivePromise::CreateAndResolve(true, __func__);
3903 AUTOPLAY_LOG("create allow-to-play promise for MediaElement %p", this);
3904 return mAllowedToPlayPromise.Ensure(__func__);
3907 already_AddRefed<DOMMediaStream> HTMLMediaElement::MozCaptureStream(
3908 ErrorResult& aRv) {
3909 MediaTrackGraph::GraphDriverType graphDriverType =
3910 HasAudio() ? MediaTrackGraph::AUDIO_THREAD_DRIVER
3911 : MediaTrackGraph::SYSTEM_THREAD_DRIVER;
3913 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
3914 if (!window) {
3915 aRv.Throw(NS_ERROR_FAILURE);
3916 return nullptr;
3919 if (!CanBeCaptured(StreamCaptureType::CAPTURE_ALL_TRACKS)) {
3920 aRv.Throw(NS_ERROR_FAILURE);
3921 return nullptr;
3924 MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
3925 graphDriverType, window, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE,
3926 MediaTrackGraph::DEFAULT_OUTPUT_DEVICE);
3928 RefPtr<DOMMediaStream> stream =
3929 CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED,
3930 StreamCaptureType::CAPTURE_ALL_TRACKS, graph);
3931 if (!stream) {
3932 aRv.Throw(NS_ERROR_FAILURE);
3933 return nullptr;
3936 return stream.forget();
3939 already_AddRefed<DOMMediaStream> HTMLMediaElement::MozCaptureStreamUntilEnded(
3940 ErrorResult& aRv) {
3941 MediaTrackGraph::GraphDriverType graphDriverType =
3942 HasAudio() ? MediaTrackGraph::AUDIO_THREAD_DRIVER
3943 : MediaTrackGraph::SYSTEM_THREAD_DRIVER;
3945 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
3946 if (!window) {
3947 aRv.Throw(NS_ERROR_FAILURE);
3948 return nullptr;
3951 if (!CanBeCaptured(StreamCaptureType::CAPTURE_ALL_TRACKS)) {
3952 aRv.Throw(NS_ERROR_FAILURE);
3953 return nullptr;
3956 MediaTrackGraph* graph = MediaTrackGraph::GetInstance(
3957 graphDriverType, window, MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE,
3958 MediaTrackGraph::DEFAULT_OUTPUT_DEVICE);
3960 RefPtr<DOMMediaStream> stream =
3961 CaptureStreamInternal(StreamCaptureBehavior::FINISH_WHEN_ENDED,
3962 StreamCaptureType::CAPTURE_ALL_TRACKS, graph);
3963 if (!stream) {
3964 aRv.Throw(NS_ERROR_FAILURE);
3965 return nullptr;
3968 return stream.forget();
3971 class MediaElementSetForURI : public nsURIHashKey {
3972 public:
3973 explicit MediaElementSetForURI(const nsIURI* aKey) : nsURIHashKey(aKey) {}
3974 MediaElementSetForURI(MediaElementSetForURI&& aOther) noexcept
3975 : nsURIHashKey(std::move(aOther)),
3976 mElements(std::move(aOther.mElements)) {}
3977 nsTArray<HTMLMediaElement*> mElements;
3980 using MediaElementURITable = nsTHashtable<MediaElementSetForURI>;
3981 // Elements in this table must have non-null mDecoder and mLoadingSrc, and those
3982 // can't change while the element is in the table. The table is keyed by
3983 // the element's mLoadingSrc. Each entry has a list of all elements with the
3984 // same mLoadingSrc.
3985 static MediaElementURITable* gElementTable;
3987 #ifdef DEBUG
3988 static bool URISafeEquals(nsIURI* a1, nsIURI* a2) {
3989 if (!a1 || !a2) {
3990 // Consider two empty URIs *not* equal!
3991 return false;
3993 bool equal = false;
3994 nsresult rv = a1->Equals(a2, &equal);
3995 return NS_SUCCEEDED(rv) && equal;
3997 // Returns the number of times aElement appears in the media element table
3998 // for aURI. If this returns other than 0 or 1, there's a bug somewhere!
3999 static unsigned MediaElementTableCount(HTMLMediaElement* aElement,
4000 nsIURI* aURI) {
4001 if (!gElementTable || !aElement) {
4002 return 0;
4004 uint32_t uriCount = 0;
4005 uint32_t otherCount = 0;
4006 for (const auto& entry : *gElementTable) {
4007 uint32_t count = 0;
4008 for (const auto& elem : entry.mElements) {
4009 if (elem == aElement) {
4010 count++;
4013 if (URISafeEquals(aURI, entry.GetKey())) {
4014 uriCount = count;
4015 } else {
4016 otherCount += count;
4019 NS_ASSERTION(otherCount == 0, "Should not have entries for unknown URIs");
4020 return uriCount;
4022 #endif
4024 void HTMLMediaElement::AddMediaElementToURITable() {
4025 NS_ASSERTION(mDecoder, "Call this only with decoder Load called");
4026 NS_ASSERTION(
4027 MediaElementTableCount(this, mLoadingSrc) == 0,
4028 "Should not have entry for element in element table before addition");
4029 if (!gElementTable) {
4030 gElementTable = new MediaElementURITable();
4032 MediaElementSetForURI* entry = gElementTable->PutEntry(mLoadingSrc);
4033 entry->mElements.AppendElement(this);
4034 NS_ASSERTION(
4035 MediaElementTableCount(this, mLoadingSrc) == 1,
4036 "Should have a single entry for element in element table after addition");
4039 void HTMLMediaElement::RemoveMediaElementFromURITable() {
4040 if (!mDecoder || !mLoadingSrc || !gElementTable) {
4041 return;
4043 MediaElementSetForURI* entry = gElementTable->GetEntry(mLoadingSrc);
4044 if (!entry) {
4045 return;
4047 entry->mElements.RemoveElement(this);
4048 if (entry->mElements.IsEmpty()) {
4049 gElementTable->RemoveEntry(entry);
4050 if (gElementTable->Count() == 0) {
4051 delete gElementTable;
4052 gElementTable = nullptr;
4055 NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
4056 "After remove, should no longer have an entry in element table");
4059 HTMLMediaElement* HTMLMediaElement::LookupMediaElementURITable(nsIURI* aURI) {
4060 if (!gElementTable) {
4061 return nullptr;
4063 MediaElementSetForURI* entry = gElementTable->GetEntry(aURI);
4064 if (!entry) {
4065 return nullptr;
4067 for (uint32_t i = 0; i < entry->mElements.Length(); ++i) {
4068 HTMLMediaElement* elem = entry->mElements[i];
4069 bool equal;
4070 // Look for elements that have the same principal and CORS mode.
4071 // Ditto for anything else that could cause us to send different headers.
4072 if (NS_SUCCEEDED(elem->NodePrincipal()->Equals(NodePrincipal(), &equal)) &&
4073 equal && elem->mCORSMode == mCORSMode) {
4074 // See SetupDecoder() below. We only add a element to the table when
4075 // mDecoder is a ChannelMediaDecoder.
4076 auto* decoder = static_cast<ChannelMediaDecoder*>(elem->mDecoder.get());
4077 NS_ASSERTION(decoder, "Decoder gone");
4078 if (decoder->CanClone()) {
4079 return elem;
4083 return nullptr;
4086 class HTMLMediaElement::ShutdownObserver : public nsIObserver {
4087 enum class Phase : int8_t { Init, Subscribed, Unsubscribed };
4089 public:
4090 NS_DECL_ISUPPORTS
4092 NS_IMETHOD Observe(nsISupports*, const char* aTopic,
4093 const char16_t*) override {
4094 if (mPhase != Phase::Subscribed) {
4095 // Bail out if we are not subscribed for this might be called even after
4096 // |nsContentUtils::UnregisterShutdownObserver(this)|.
4097 return NS_OK;
4099 MOZ_DIAGNOSTIC_ASSERT(mWeak);
4100 if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
4101 mWeak->NotifyShutdownEvent();
4103 return NS_OK;
4105 void Subscribe(HTMLMediaElement* aPtr) {
4106 MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Init);
4107 MOZ_DIAGNOSTIC_ASSERT(!mWeak);
4108 mWeak = aPtr;
4109 nsContentUtils::RegisterShutdownObserver(this);
4110 mPhase = Phase::Subscribed;
4112 void Unsubscribe() {
4113 MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Subscribed);
4114 MOZ_DIAGNOSTIC_ASSERT(mWeak);
4115 MOZ_DIAGNOSTIC_ASSERT(!mAddRefed,
4116 "ReleaseMediaElement should have been called first");
4117 mWeak = nullptr;
4118 nsContentUtils::UnregisterShutdownObserver(this);
4119 mPhase = Phase::Unsubscribed;
4121 void AddRefMediaElement() {
4122 MOZ_DIAGNOSTIC_ASSERT(mWeak);
4123 MOZ_DIAGNOSTIC_ASSERT(!mAddRefed, "Should only ever AddRef once");
4124 mWeak->AddRef();
4125 mAddRefed = true;
4127 void ReleaseMediaElement() {
4128 MOZ_DIAGNOSTIC_ASSERT(mWeak);
4129 MOZ_DIAGNOSTIC_ASSERT(mAddRefed, "Should only release after AddRef");
4130 mWeak->Release();
4131 mAddRefed = false;
4134 private:
4135 virtual ~ShutdownObserver() {
4136 MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Unsubscribed);
4137 MOZ_DIAGNOSTIC_ASSERT(!mWeak);
4138 MOZ_DIAGNOSTIC_ASSERT(!mAddRefed,
4139 "ReleaseMediaElement should have been called first");
4141 // Guaranteed to be valid by HTMLMediaElement.
4142 HTMLMediaElement* mWeak = nullptr;
4143 Phase mPhase = Phase::Init;
4144 bool mAddRefed = false;
4147 NS_IMPL_ISUPPORTS(HTMLMediaElement::ShutdownObserver, nsIObserver)
4149 class HTMLMediaElement::TitleChangeObserver final : public nsIObserver {
4150 public:
4151 NS_DECL_ISUPPORTS
4153 explicit TitleChangeObserver(HTMLMediaElement* aElement)
4154 : mElement(aElement) {
4155 MOZ_ASSERT(NS_IsMainThread());
4156 MOZ_ASSERT(aElement);
4159 NS_IMETHOD Observe(nsISupports*, const char* aTopic,
4160 const char16_t*) override {
4161 if (mElement) {
4162 mElement->UpdateStreamName();
4165 return NS_OK;
4168 void Subscribe() {
4169 nsCOMPtr<nsIObserverService> observerService =
4170 mozilla::services::GetObserverService();
4171 if (observerService) {
4172 observerService->AddObserver(this, "document-title-changed", false);
4176 void Unsubscribe() {
4177 nsCOMPtr<nsIObserverService> observerService =
4178 mozilla::services::GetObserverService();
4179 if (observerService) {
4180 observerService->RemoveObserver(this, "document-title-changed");
4184 private:
4185 ~TitleChangeObserver() = default;
4187 WeakPtr<HTMLMediaElement> mElement;
4190 NS_IMPL_ISUPPORTS(HTMLMediaElement::TitleChangeObserver, nsIObserver)
4192 HTMLMediaElement::HTMLMediaElement(
4193 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
4194 : nsGenericHTMLElement(std::move(aNodeInfo)),
4195 mWatchManager(this,
4196 OwnerDoc()->AbstractMainThreadFor(TaskCategory::Other)),
4197 mMainThreadEventTarget(OwnerDoc()->EventTargetFor(TaskCategory::Other)),
4198 mAbstractMainThread(
4199 OwnerDoc()->AbstractMainThreadFor(TaskCategory::Other)),
4200 mShutdownObserver(new ShutdownObserver),
4201 mTitleChangeObserver(new TitleChangeObserver(this)),
4202 mEventBlocker(new EventBlocker(this)),
4203 mPlayed(new TimeRanges(ToSupports(OwnerDoc()))),
4204 mTracksCaptured(nullptr, "HTMLMediaElement::mTracksCaptured"),
4205 mErrorSink(new ErrorSink(this)),
4206 mAudioChannelWrapper(new AudioChannelAgentCallback(this)),
4207 mSink(std::pair(nsString(), RefPtr<AudioDeviceInfo>())),
4208 mShowPoster(IsVideo()),
4209 mMediaControlKeyListener(new MediaControlKeyListener(this)) {
4210 MOZ_ASSERT(mMainThreadEventTarget);
4211 MOZ_ASSERT(mAbstractMainThread);
4212 // Please don't add anything to this constructor or the initialization
4213 // list that can cause AddRef to be called. This prevents subclasses
4214 // from overriding AddRef in a way that works with our refcount
4215 // logging mechanisms. Put these things inside of the ::Init method
4216 // instead.
4219 void HTMLMediaElement::Init() {
4220 MOZ_ASSERT(mRefCnt == 0 && !mRefCnt.IsPurple(),
4221 "HTMLMediaElement::Init called when AddRef has been called "
4222 "at least once already, probably in the constructor. Please "
4223 "see the documentation in the HTMLMediaElement constructor.");
4224 MOZ_ASSERT(!mRefCnt.IsPurple());
4226 mAudioTrackList = new AudioTrackList(OwnerDoc()->GetParentObject(), this);
4227 mVideoTrackList = new VideoTrackList(OwnerDoc()->GetParentObject(), this);
4229 DecoderDoctorLogger::LogConstruction(this);
4231 mWatchManager.Watch(mPaused, &HTMLMediaElement::UpdateWakeLock);
4232 mWatchManager.Watch(mPaused, &HTMLMediaElement::UpdateOutputTracksMuting);
4233 mWatchManager.Watch(
4234 mPaused, &HTMLMediaElement::NotifyMediaControlPlaybackStateChanged);
4235 mWatchManager.Watch(mReadyState, &HTMLMediaElement::UpdateOutputTracksMuting);
4237 mWatchManager.Watch(mTracksCaptured,
4238 &HTMLMediaElement::UpdateOutputTrackSources);
4239 mWatchManager.Watch(mReadyState, &HTMLMediaElement::UpdateOutputTrackSources);
4241 mWatchManager.Watch(mDownloadSuspendedByCache,
4242 &HTMLMediaElement::UpdateReadyStateInternal);
4243 mWatchManager.Watch(mFirstFrameLoaded,
4244 &HTMLMediaElement::UpdateReadyStateInternal);
4245 mWatchManager.Watch(mSrcStreamPlaybackEnded,
4246 &HTMLMediaElement::UpdateReadyStateInternal);
4248 ErrorResult rv;
4250 double defaultVolume = Preferences::GetFloat("media.default_volume", 1.0);
4251 SetVolume(defaultVolume, rv);
4253 RegisterActivityObserver();
4254 NotifyOwnerDocumentActivityChanged();
4256 // We initialize the MediaShutdownManager as the HTMLMediaElement is always
4257 // constructed on the main thread, and not during stable state.
4258 // (MediaShutdownManager make use of nsIAsyncShutdownClient which is written
4259 // in JS)
4260 MediaShutdownManager::InitStatics();
4262 #if defined(MOZ_WIDGET_ANDROID)
4263 GVAutoplayPermissionRequestor::AskForPermissionIfNeeded(
4264 OwnerDoc()->GetInnerWindow());
4265 #endif
4267 OwnerDoc()->SetDocTreeHadMedia();
4268 mShutdownObserver->Subscribe(this);
4269 mInitialized = true;
4272 HTMLMediaElement::~HTMLMediaElement() {
4273 MOZ_ASSERT(mInitialized,
4274 "HTMLMediaElement must be initialized before it is destroyed.");
4275 NS_ASSERTION(
4276 !mHasSelfReference,
4277 "How can we be destroyed if we're still holding a self reference?");
4279 mWatchManager.Shutdown();
4281 mShutdownObserver->Unsubscribe();
4283 mTitleChangeObserver->Unsubscribe();
4285 if (mVideoFrameContainer) {
4286 mVideoFrameContainer->ForgetElement();
4288 UnregisterActivityObserver();
4290 mSetCDMRequest.DisconnectIfExists();
4291 mAllowedToPlayPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
4293 if (mDecoder) {
4294 ShutdownDecoder();
4296 if (mProgressTimer) {
4297 StopProgress();
4299 if (mSrcStream) {
4300 EndSrcMediaStreamPlayback();
4303 NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
4304 "Destroyed media element should no longer be in element table");
4306 if (mChannelLoader) {
4307 mChannelLoader->Cancel();
4310 if (mAudioChannelWrapper) {
4311 mAudioChannelWrapper->Shutdown();
4312 mAudioChannelWrapper = nullptr;
4315 if (mResumeDelayedPlaybackAgent) {
4316 mResumePlaybackRequest.DisconnectIfExists();
4317 mResumeDelayedPlaybackAgent = nullptr;
4320 mMediaControlKeyListener->StopIfNeeded();
4321 mMediaControlKeyListener = nullptr;
4323 WakeLockRelease();
4325 DecoderDoctorLogger::LogDestruction(this);
4328 void HTMLMediaElement::StopSuspendingAfterFirstFrame() {
4329 mAllowSuspendAfterFirstFrame = false;
4330 if (!mSuspendedAfterFirstFrame) return;
4331 mSuspendedAfterFirstFrame = false;
4332 if (mDecoder) {
4333 mDecoder->Resume();
4337 void HTMLMediaElement::SetPlayedOrSeeked(bool aValue) {
4338 if (aValue == mHasPlayedOrSeeked) {
4339 return;
4342 mHasPlayedOrSeeked = aValue;
4344 // Force a reflow so that the poster frame hides or shows immediately.
4345 nsIFrame* frame = GetPrimaryFrame();
4346 if (!frame) {
4347 return;
4349 frame->PresShell()->FrameNeedsReflow(frame, IntrinsicDirty::TreeChange,
4350 NS_FRAME_IS_DIRTY);
4353 void HTMLMediaElement::NotifyXPCOMShutdown() { ShutdownDecoder(); }
4355 already_AddRefed<Promise> HTMLMediaElement::Play(ErrorResult& aRv) {
4356 LOG(LogLevel::Debug,
4357 ("%p Play() called by JS readyState=%d", this, mReadyState.Ref()));
4359 // 4.8.12.8
4360 // When the play() method on a media element is invoked, the user agent must
4361 // run the following steps.
4363 RefPtr<PlayPromise> promise = CreatePlayPromise(aRv);
4364 if (NS_WARN_IF(aRv.Failed())) {
4365 return nullptr;
4368 // 4.8.12.8 - Step 1:
4369 // If the media element is not allowed to play, return a promise rejected
4370 // with a "NotAllowedError" DOMException and abort these steps.
4371 // NOTE: we may require requesting permission from the user, so we do the
4372 // "not allowed" check below.
4374 // 4.8.12.8 - Step 2:
4375 // If the media element's error attribute is not null and its code
4376 // attribute has the value MEDIA_ERR_SRC_NOT_SUPPORTED, return a promise
4377 // rejected with a "NotSupportedError" DOMException and abort these steps.
4378 if (GetError() && GetError()->Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) {
4379 LOG(LogLevel::Debug,
4380 ("%p Play() promise rejected because source not supported.", this));
4381 promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR);
4382 return promise.forget();
4385 // 4.8.12.8 - Step 3:
4386 // Let promise be a new promise and append promise to the list of pending
4387 // play promises.
4388 // Note: Promise appended to list of pending promises as needed below.
4390 if (ShouldBeSuspendedByInactiveDocShell()) {
4391 LOG(LogLevel::Debug, ("%p no allow to play by the docShell for now", this));
4392 mPendingPlayPromises.AppendElement(promise);
4393 return promise.forget();
4396 // We may delay starting playback of a media resource for an unvisited tab
4397 // until it's going to foreground or being resumed by the play tab icon.
4398 if (MediaPlaybackDelayPolicy::ShouldDelayPlayback(this)) {
4399 CreateResumeDelayedMediaPlaybackAgentIfNeeded();
4400 LOG(LogLevel::Debug, ("%p delay Play() call", this));
4401 MaybeDoLoad();
4402 // When play is delayed, save a reference to the promise, and return it.
4403 // The promise will be resolved when we resume play by either the tab is
4404 // brought to the foreground, or the audio tab indicator is clicked.
4405 mPendingPlayPromises.AppendElement(promise);
4406 return promise.forget();
4409 const bool handlingUserInput = UserActivation::IsHandlingUserInput();
4410 mPendingPlayPromises.AppendElement(promise);
4412 if (AllowedToPlay()) {
4413 AUTOPLAY_LOG("allow MediaElement %p to play", this);
4414 mAllowedToPlayPromise.ResolveIfExists(true, __func__);
4415 PlayInternal(handlingUserInput);
4416 UpdateCustomPolicyAfterPlayed();
4417 } else {
4418 AUTOPLAY_LOG("reject MediaElement %p to play", this);
4419 AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
4421 return promise.forget();
4424 void HTMLMediaElement::DispatchEventsWhenPlayWasNotAllowed() {
4425 if (StaticPrefs::media_autoplay_block_event_enabled()) {
4426 DispatchAsyncEvent(u"blocked"_ns);
4428 DispatchBlockEventForVideoControl();
4429 if (!mHasEverBeenBlockedForAutoplay) {
4430 MaybeNotifyAutoplayBlocked();
4431 ReportToConsole(nsIScriptError::warningFlag, "BlockAutoplayError");
4432 mHasEverBeenBlockedForAutoplay = true;
4436 void HTMLMediaElement::MaybeNotifyAutoplayBlocked() {
4437 // This event is used to notify front-end side that we've blocked autoplay,
4438 // so front-end side should show blocking icon as well.
4439 RefPtr<AsyncEventDispatcher> asyncDispatcher =
4440 new AsyncEventDispatcher(OwnerDoc(), u"GloballyAutoplayBlocked"_ns,
4441 CanBubble::eYes, ChromeOnlyDispatch::eYes);
4442 asyncDispatcher->PostDOMEvent();
4445 void HTMLMediaElement::DispatchBlockEventForVideoControl() {
4446 #if defined(MOZ_WIDGET_ANDROID)
4447 nsVideoFrame* videoFrame = do_QueryFrame(GetPrimaryFrame());
4448 if (!videoFrame || !videoFrame->GetVideoControls()) {
4449 return;
4452 RefPtr<AsyncEventDispatcher> asyncDispatcher = new AsyncEventDispatcher(
4453 videoFrame->GetVideoControls(), u"MozNoControlsBlockedVideo"_ns,
4454 CanBubble::eYes);
4455 asyncDispatcher->PostDOMEvent();
4456 #endif
4459 void HTMLMediaElement::PlayInternal(bool aHandlingUserInput) {
4460 if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE) {
4461 // The media load algorithm will be initiated by a user interaction.
4462 // We want to boost the channel priority for better responsiveness.
4463 // Note this must be done before UpdatePreloadAction() which will
4464 // update |mPreloadAction|.
4465 mUseUrgentStartForChannel = true;
4468 StopSuspendingAfterFirstFrame();
4469 SetPlayedOrSeeked(true);
4471 // 4.8.12.8 - Step 4:
4472 // If the media element's networkState attribute has the value NETWORK_EMPTY,
4473 // invoke the media element's resource selection algorithm.
4474 MaybeDoLoad();
4475 if (mSuspendedForPreloadNone) {
4476 ResumeLoad(PRELOAD_ENOUGH);
4479 // 4.8.12.8 - Step 5:
4480 // If the playback has ended and the direction of playback is forwards,
4481 // seek to the earliest possible position of the media resource.
4483 // Even if we just did Load() or ResumeLoad(), we could already have a decoder
4484 // here if we managed to clone an existing decoder.
4485 if (mDecoder) {
4486 if (mDecoder->IsEnded()) {
4487 SetCurrentTime(0);
4489 if (!mSuspendedByInactiveDocOrDocshell) {
4490 mDecoder->Play();
4494 if (mCurrentPlayRangeStart == -1.0) {
4495 mCurrentPlayRangeStart = CurrentTime();
4498 const bool oldPaused = mPaused;
4499 mPaused = false;
4500 mAutoplaying = false;
4502 // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
4503 // and our preload status.
4504 AddRemoveSelfReference();
4505 UpdatePreloadAction();
4506 UpdateSrcMediaStreamPlaying();
4507 StartMediaControlKeyListenerIfNeeded();
4509 // Once play() has been called in a user generated event handler,
4510 // it is allowed to autoplay. Note: we can reach here when not in
4511 // a user generated event handler if our readyState has not yet
4512 // reached HAVE_METADATA.
4513 mIsBlessed |= aHandlingUserInput;
4515 // TODO: If the playback has ended, then the user agent must set
4516 // seek to the effective start.
4518 // 4.8.12.8 - Step 6:
4519 // If the media element's paused attribute is true, run the following steps:
4520 if (oldPaused) {
4521 // 6.1. Change the value of paused to false. (Already done.)
4522 // This step is uplifted because the "block-media-playback" feature needs
4523 // the mPaused to be false before UpdateAudioChannelPlayingState() being
4524 // called.
4526 // 6.2. If the show poster flag is true, set the element's show poster flag
4527 // to false and run the time marches on steps.
4528 if (mShowPoster) {
4529 mShowPoster = false;
4530 if (mTextTrackManager) {
4531 mTextTrackManager->TimeMarchesOn();
4535 // 6.3. Queue a task to fire a simple event named play at the element.
4536 DispatchAsyncEvent(u"play"_ns);
4538 // 6.4. If the media element's readyState attribute has the value
4539 // HAVE_NOTHING, HAVE_METADATA, or HAVE_CURRENT_DATA, queue a task to
4540 // fire a simple event named waiting at the element.
4541 // Otherwise, the media element's readyState attribute has the value
4542 // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA: notify about playing for the
4543 // element.
4544 switch (mReadyState) {
4545 case HAVE_NOTHING:
4546 DispatchAsyncEvent(u"waiting"_ns);
4547 break;
4548 case HAVE_METADATA:
4549 case HAVE_CURRENT_DATA:
4550 DispatchAsyncEvent(u"waiting"_ns);
4551 break;
4552 case HAVE_FUTURE_DATA:
4553 case HAVE_ENOUGH_DATA:
4554 NotifyAboutPlaying();
4555 break;
4557 } else if (mReadyState >= HAVE_FUTURE_DATA) {
4558 // 7. Otherwise, if the media element's readyState attribute has the value
4559 // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, take pending play promises and
4560 // queue a task to resolve pending play promises with the result.
4561 AsyncResolvePendingPlayPromises();
4564 // 8. Set the media element's autoplaying flag to false. (Already done.)
4566 // 9. Return promise.
4567 // (Done in caller.)
4570 void HTMLMediaElement::MaybeDoLoad() {
4571 if (mNetworkState == NETWORK_EMPTY) {
4572 DoLoad();
4576 void HTMLMediaElement::UpdateWakeLock() {
4577 MOZ_ASSERT(NS_IsMainThread());
4578 // Ensure we have a wake lock if we're playing audibly. This ensures the
4579 // device doesn't sleep while playing.
4580 bool playing = !mPaused;
4581 bool isAudible = Volume() > 0.0 && !mMuted && mIsAudioTrackAudible;
4582 // WakeLock when playing audible media.
4583 if (playing && isAudible) {
4584 CreateAudioWakeLockIfNeeded();
4585 } else {
4586 ReleaseAudioWakeLockIfExists();
4590 void HTMLMediaElement::CreateAudioWakeLockIfNeeded() {
4591 if (!mWakeLock) {
4592 RefPtr<power::PowerManagerService> pmService =
4593 power::PowerManagerService::GetInstance();
4594 NS_ENSURE_TRUE_VOID(pmService);
4596 ErrorResult rv;
4597 mWakeLock = pmService->NewWakeLock(u"audio-playing"_ns,
4598 OwnerDoc()->GetInnerWindow(), rv);
4602 void HTMLMediaElement::ReleaseAudioWakeLockIfExists() {
4603 if (mWakeLock) {
4604 ErrorResult rv;
4605 mWakeLock->Unlock(rv);
4606 rv.SuppressException();
4607 mWakeLock = nullptr;
4611 void HTMLMediaElement::WakeLockRelease() { ReleaseAudioWakeLockIfExists(); }
4613 void HTMLMediaElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
4614 if (!this->Controls() || !aVisitor.mEvent->mFlags.mIsTrusted) {
4615 nsGenericHTMLElement::GetEventTargetParent(aVisitor);
4616 return;
4619 // We will need to trap pointer, touch, and mouse events within the media
4620 // element, allowing media control exclusive consumption on these events,
4621 // and preventing the content from handling them.
4622 switch (aVisitor.mEvent->mMessage) {
4623 case ePointerDown:
4624 case ePointerUp:
4625 case eTouchEnd:
4626 // Always prevent touchmove captured in video element from being handled by
4627 // content, since we always do that for touchstart.
4628 case eTouchMove:
4629 case eTouchStart:
4630 case eMouseClick:
4631 case eMouseDoubleClick:
4632 case eMouseDown:
4633 case eMouseUp:
4634 aVisitor.mCanHandle = false;
4635 return;
4637 // The *move events however are only comsumed when the range input is being
4638 // dragged.
4639 case ePointerMove:
4640 case eMouseMove: {
4641 nsINode* node =
4642 nsINode::FromEventTargetOrNull(aVisitor.mEvent->mOriginalTarget);
4643 if (MOZ_UNLIKELY(!node)) {
4644 return;
4646 HTMLInputElement* el = nullptr;
4647 if (node->IsInNativeAnonymousSubtree() || node->IsInUAWidget()) {
4648 if (node->IsHTMLElement(nsGkAtoms::input)) {
4649 // The node is a <input type="range">
4650 el = static_cast<HTMLInputElement*>(node);
4651 } else if (node->GetParentNode() &&
4652 node->GetParentNode()->IsHTMLElement(nsGkAtoms::input)) {
4653 // The node is a child of <input type="range">
4654 el = static_cast<HTMLInputElement*>(node->GetParentNode());
4657 if (el && el->IsDraggingRange()) {
4658 aVisitor.mCanHandle = false;
4659 return;
4661 nsGenericHTMLElement::GetEventTargetParent(aVisitor);
4662 return;
4664 default:
4665 nsGenericHTMLElement::GetEventTargetParent(aVisitor);
4666 return;
4670 bool HTMLMediaElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
4671 const nsAString& aValue,
4672 nsIPrincipal* aMaybeScriptedPrincipal,
4673 nsAttrValue& aResult) {
4674 // Mappings from 'preload' attribute strings to an enumeration.
4675 static const nsAttrValue::EnumTable kPreloadTable[] = {
4676 {"", HTMLMediaElement::PRELOAD_ATTR_EMPTY},
4677 {"none", HTMLMediaElement::PRELOAD_ATTR_NONE},
4678 {"metadata", HTMLMediaElement::PRELOAD_ATTR_METADATA},
4679 {"auto", HTMLMediaElement::PRELOAD_ATTR_AUTO},
4680 {nullptr, 0}};
4682 if (aNamespaceID == kNameSpaceID_None) {
4683 if (aAttribute == nsGkAtoms::crossorigin) {
4684 ParseCORSValue(aValue, aResult);
4685 return true;
4687 if (aAttribute == nsGkAtoms::preload) {
4688 return aResult.ParseEnumValue(aValue, kPreloadTable, false);
4692 return nsGenericHTMLElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
4693 aMaybeScriptedPrincipal, aResult);
4696 void HTMLMediaElement::DoneCreatingElement() {
4697 if (HasAttr(kNameSpaceID_None, nsGkAtoms::muted)) {
4698 mMuted |= MUTED_BY_CONTENT;
4702 bool HTMLMediaElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable,
4703 int32_t* aTabIndex) {
4704 if (nsGenericHTMLElement::IsHTMLFocusable(aWithMouse, aIsFocusable,
4705 aTabIndex)) {
4706 return true;
4709 *aIsFocusable = true;
4710 return false;
4713 int32_t HTMLMediaElement::TabIndexDefault() { return 0; }
4715 nsresult HTMLMediaElement::AfterSetAttr(int32_t aNameSpaceID, nsAtom* aName,
4716 const nsAttrValue* aValue,
4717 const nsAttrValue* aOldValue,
4718 nsIPrincipal* aMaybeScriptedPrincipal,
4719 bool aNotify) {
4720 if (aNameSpaceID == kNameSpaceID_None) {
4721 if (aName == nsGkAtoms::src) {
4722 mSrcMediaSource = nullptr;
4723 mSrcAttrTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal(
4724 this, aValue ? aValue->GetStringValue() : EmptyString(),
4725 aMaybeScriptedPrincipal);
4726 if (aValue) {
4727 nsString srcStr = aValue->GetStringValue();
4728 nsCOMPtr<nsIURI> uri;
4729 NewURIFromString(srcStr, getter_AddRefs(uri));
4730 if (uri && IsMediaSourceURI(uri)) {
4731 nsresult rv = NS_GetSourceForMediaSourceURI(
4732 uri, getter_AddRefs(mSrcMediaSource));
4733 if (NS_FAILED(rv)) {
4734 nsAutoString spec;
4735 GetCurrentSrc(spec);
4736 AutoTArray<nsString, 1> params = {spec};
4737 ReportLoadError("MediaLoadInvalidURI", params);
4741 } else if (aName == nsGkAtoms::autoplay) {
4742 if (aNotify) {
4743 if (aValue) {
4744 StopSuspendingAfterFirstFrame();
4745 CheckAutoplayDataReady();
4747 // This attribute can affect AddRemoveSelfReference
4748 AddRemoveSelfReference();
4749 UpdatePreloadAction();
4751 } else if (aName == nsGkAtoms::preload) {
4752 UpdatePreloadAction();
4753 } else if (aName == nsGkAtoms::loop) {
4754 if (mDecoder) {
4755 mDecoder->SetLooping(!!aValue);
4757 } else if (aName == nsGkAtoms::controls && IsInComposedDoc()) {
4758 NotifyUAWidgetSetupOrChange();
4762 // Since AfterMaybeChangeAttr may call DoLoad, make sure that it is called
4763 // *after* any possible changes to mSrcMediaSource.
4764 if (aValue) {
4765 AfterMaybeChangeAttr(aNameSpaceID, aName, aNotify);
4768 return nsGenericHTMLElement::AfterSetAttr(
4769 aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
4772 nsresult HTMLMediaElement::OnAttrSetButNotChanged(
4773 int32_t aNamespaceID, nsAtom* aName, const nsAttrValueOrString& aValue,
4774 bool aNotify) {
4775 AfterMaybeChangeAttr(aNamespaceID, aName, aNotify);
4777 return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID, aName,
4778 aValue, aNotify);
4781 void HTMLMediaElement::AfterMaybeChangeAttr(int32_t aNamespaceID, nsAtom* aName,
4782 bool aNotify) {
4783 if (aNamespaceID == kNameSpaceID_None) {
4784 if (aName == nsGkAtoms::src) {
4785 DoLoad();
4790 nsresult HTMLMediaElement::BindToTree(BindContext& aContext, nsINode& aParent) {
4791 nsresult rv = nsGenericHTMLElement::BindToTree(aContext, aParent);
4793 if (IsInComposedDoc()) {
4794 // Construct Shadow Root so web content can be hidden in the DOM.
4795 AttachAndSetUAShadowRoot();
4797 // The preload action depends on the value of the autoplay attribute.
4798 // It's value may have changed, so update it.
4799 UpdatePreloadAction();
4802 NotifyDecoderActivityChanges();
4803 mMediaControlKeyListener->UpdateOwnerBrowsingContextIfNeeded();
4804 return rv;
4807 void HTMLMediaElement::UnbindFromTree(bool aNullParent) {
4808 mVisibilityState = Visibility::Untracked;
4810 if (IsInComposedDoc()) {
4811 NotifyUAWidgetTeardown();
4814 nsGenericHTMLElement::UnbindFromTree(aNullParent);
4816 MOZ_ASSERT(IsActuallyInvisible());
4817 NotifyDecoderActivityChanges();
4819 // https://html.spec.whatwg.org/#playing-the-media-resource:remove-an-element-from-a-document
4821 // Dispatch a task to run once we're in a stable state which ensures we're
4822 // paused if we're no longer in a document. Note that we need to dispatch this
4823 // even if there are other tasks in flight for this because these can be
4824 // cancelled if there's a new load.
4826 // FIXME(emilio): Per that spec section, we should only do this if we used to
4827 // be connected, though other browsers match our current behavior...
4829 // Also, https://github.com/whatwg/html/issues/4928
4830 nsCOMPtr<nsIRunnable> task =
4831 NS_NewRunnableFunction("dom::HTMLMediaElement::UnbindFromTree",
4832 [self = RefPtr<HTMLMediaElement>(this)]() {
4833 if (!self->IsInComposedDoc()) {
4834 self->PauseInternal();
4835 self->mMediaControlKeyListener->StopIfNeeded();
4838 RunInStableState(task);
4841 /* static */
4842 CanPlayStatus HTMLMediaElement::GetCanPlay(
4843 const nsAString& aType, DecoderDoctorDiagnostics* aDiagnostics) {
4844 Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType);
4845 if (!containerType) {
4846 return CANPLAY_NO;
4848 CanPlayStatus status =
4849 DecoderTraits::CanHandleContainerType(*containerType, aDiagnostics);
4850 if (status == CANPLAY_YES &&
4851 (*containerType).ExtendedType().Codecs().IsEmpty()) {
4852 // Per spec: 'Generally, a user agent should never return "probably" for a
4853 // type that allows the `codecs` parameter if that parameter is not
4854 // present.' As all our currently-supported types allow for `codecs`, we can
4855 // do this check here.
4856 // TODO: Instead, missing `codecs` should be checked in each decoder's
4857 // `IsSupportedType` call from `CanHandleCodecsType()`.
4858 // See bug 1399023.
4859 return CANPLAY_MAYBE;
4861 return status;
4864 void HTMLMediaElement::CanPlayType(const nsAString& aType, nsAString& aResult) {
4865 DecoderDoctorDiagnostics diagnostics;
4866 CanPlayStatus canPlay = GetCanPlay(aType, &diagnostics);
4867 diagnostics.StoreFormatDiagnostics(OwnerDoc(), aType, canPlay != CANPLAY_NO,
4868 __func__);
4869 switch (canPlay) {
4870 case CANPLAY_NO:
4871 aResult.Truncate();
4872 break;
4873 case CANPLAY_YES:
4874 aResult.AssignLiteral("probably");
4875 break;
4876 case CANPLAY_MAYBE:
4877 aResult.AssignLiteral("maybe");
4878 break;
4879 default:
4880 MOZ_ASSERT_UNREACHABLE("Unexpected case.");
4881 break;
4884 LOG(LogLevel::Debug,
4885 ("%p CanPlayType(%s) = \"%s\"", this, NS_ConvertUTF16toUTF8(aType).get(),
4886 NS_ConvertUTF16toUTF8(aResult).get()));
4889 void HTMLMediaElement::AssertReadyStateIsNothing() {
4890 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4891 if (mReadyState != HAVE_NOTHING) {
4892 char buf[1024];
4893 SprintfLiteral(buf,
4894 "readyState=%d networkState=%d mLoadWaitStatus=%d "
4895 "mSourceLoadCandidate=%d "
4896 "mIsLoadingFromSourceChildren=%d mPreloadAction=%d "
4897 "mSuspendedForPreloadNone=%d error=%d",
4898 int(mReadyState), int(mNetworkState), int(mLoadWaitStatus),
4899 !!mSourceLoadCandidate, mIsLoadingFromSourceChildren,
4900 int(mPreloadAction), mSuspendedForPreloadNone,
4901 GetError() ? GetError()->Code() : 0);
4902 MOZ_CRASH_UNSAFE_PRINTF("ReadyState should be HAVE_NOTHING! %s", buf);
4904 #endif
4907 nsresult HTMLMediaElement::InitializeDecoderAsClone(
4908 ChannelMediaDecoder* aOriginal) {
4909 NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
4910 NS_ASSERTION(mDecoder == nullptr, "Shouldn't have a decoder");
4911 AssertReadyStateIsNothing();
4913 MediaDecoderInit decoderInit(
4914 this, this, mMuted ? 0.0 : mVolume, mPreservesPitch,
4915 ClampPlaybackRate(mPlaybackRate),
4916 mPreloadAction == HTMLMediaElement::PRELOAD_METADATA, mHasSuspendTaint,
4917 HasAttr(kNameSpaceID_None, nsGkAtoms::loop), aOriginal->ContainerType());
4919 RefPtr<ChannelMediaDecoder> decoder = aOriginal->Clone(decoderInit);
4920 if (!decoder) return NS_ERROR_FAILURE;
4922 LOG(LogLevel::Debug,
4923 ("%p Cloned decoder %p from %p", this, decoder.get(), aOriginal));
4925 return FinishDecoderSetup(decoder);
4928 template <typename DecoderType, typename... LoadArgs>
4929 nsresult HTMLMediaElement::SetupDecoder(DecoderType* aDecoder,
4930 LoadArgs&&... aArgs) {
4931 LOG(LogLevel::Debug, ("%p Created decoder %p for type %s", this, aDecoder,
4932 aDecoder->ContainerType().OriginalString().Data()));
4934 nsresult rv = aDecoder->Load(std::forward<LoadArgs>(aArgs)...);
4935 if (NS_FAILED(rv)) {
4936 aDecoder->Shutdown();
4937 LOG(LogLevel::Debug, ("%p Failed to load for decoder %p", this, aDecoder));
4938 return rv;
4941 rv = FinishDecoderSetup(aDecoder);
4942 // Only ChannelMediaDecoder supports resource cloning.
4943 if (std::is_same_v<DecoderType, ChannelMediaDecoder> && NS_SUCCEEDED(rv)) {
4944 AddMediaElementToURITable();
4945 NS_ASSERTION(
4946 MediaElementTableCount(this, mLoadingSrc) == 1,
4947 "Media element should have single table entry if decode initialized");
4950 return rv;
4953 nsresult HTMLMediaElement::InitializeDecoderForChannel(
4954 nsIChannel* aChannel, nsIStreamListener** aListener) {
4955 NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
4956 AssertReadyStateIsNothing();
4958 DecoderDoctorDiagnostics diagnostics;
4960 nsAutoCString mimeType;
4961 aChannel->GetContentType(mimeType);
4962 NS_ASSERTION(!mimeType.IsEmpty(), "We should have the Content-Type.");
4963 NS_ConvertUTF8toUTF16 mimeUTF16(mimeType);
4965 RefPtr<HTMLMediaElement> self = this;
4966 auto reportCanPlay = [&, self](bool aCanPlay) {
4967 diagnostics.StoreFormatDiagnostics(self->OwnerDoc(), mimeUTF16, aCanPlay,
4968 __func__);
4969 if (!aCanPlay) {
4970 nsAutoString src;
4971 self->GetCurrentSrc(src);
4972 AutoTArray<nsString, 2> params = {mimeUTF16, src};
4973 self->ReportLoadError("MediaLoadUnsupportedMimeType", params);
4977 auto onExit = MakeScopeExit([self] {
4978 if (self->mChannelLoader) {
4979 self->mChannelLoader->Done();
4980 self->mChannelLoader = nullptr;
4984 Maybe<MediaContainerType> containerType = MakeMediaContainerType(mimeType);
4985 if (!containerType) {
4986 reportCanPlay(false);
4987 return NS_ERROR_FAILURE;
4990 MediaDecoderInit decoderInit(
4991 this, this, mMuted ? 0.0 : mVolume, mPreservesPitch,
4992 ClampPlaybackRate(mPlaybackRate),
4993 mPreloadAction == HTMLMediaElement::PRELOAD_METADATA, mHasSuspendTaint,
4994 HasAttr(kNameSpaceID_None, nsGkAtoms::loop), *containerType);
4996 #ifdef MOZ_ANDROID_HLS_SUPPORT
4997 if (HLSDecoder::IsSupportedType(*containerType)) {
4998 RefPtr<HLSDecoder> decoder = HLSDecoder::Create(decoderInit);
4999 if (!decoder) {
5000 reportCanPlay(false);
5001 return NS_ERROR_OUT_OF_MEMORY;
5003 reportCanPlay(true);
5004 return SetupDecoder(decoder.get(), aChannel);
5006 #endif
5008 RefPtr<ChannelMediaDecoder> decoder =
5009 ChannelMediaDecoder::Create(decoderInit, &diagnostics);
5010 if (!decoder) {
5011 reportCanPlay(false);
5012 return NS_ERROR_FAILURE;
5015 reportCanPlay(true);
5016 bool isPrivateBrowsing = NodePrincipal()->GetPrivateBrowsingId() > 0;
5017 return SetupDecoder(decoder.get(), aChannel, isPrivateBrowsing, aListener);
5020 nsresult HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder) {
5021 ChangeNetworkState(NETWORK_LOADING);
5023 // Set mDecoder now so if methods like GetCurrentSrc get called between
5024 // here and Load(), they work.
5025 SetDecoder(aDecoder);
5027 // Notify the decoder of the initial activity status.
5028 NotifyDecoderActivityChanges();
5030 // Update decoder principal before we start decoding, since it
5031 // can affect how we feed data to MediaStreams
5032 NotifyDecoderPrincipalChanged();
5034 // Set sink device if we have one. Otherwise the default is used.
5035 if (mSink.second) {
5036 mDecoder
5037 ->SetSink(mSink.second)
5038 #ifdef DEBUG
5039 ->Then(mAbstractMainThread, __func__,
5040 [](const GenericPromise::ResolveOrRejectValue& aValue) {
5041 MOZ_ASSERT(aValue.IsResolve() && !aValue.ResolveValue());
5043 #else
5045 #endif
5048 if (mMediaKeys) {
5049 if (mMediaKeys->GetCDMProxy()) {
5050 mDecoder->SetCDMProxy(mMediaKeys->GetCDMProxy());
5051 } else {
5052 // CDM must have crashed.
5053 ShutdownDecoder();
5054 return NS_ERROR_FAILURE;
5058 if (mChannelLoader) {
5059 mChannelLoader->Done();
5060 mChannelLoader = nullptr;
5063 // We may want to suspend the new stream now.
5064 // This will also do an AddRemoveSelfReference.
5065 NotifyOwnerDocumentActivityChanged();
5067 if (!mDecoder) {
5068 // NotifyOwnerDocumentActivityChanged may shutdown the decoder if the
5069 // owning document is inactive and we're in the EME case. We could try and
5070 // handle this, but at the time of writing it's a pretty niche case, so just
5071 // bail.
5072 return NS_ERROR_FAILURE;
5075 if (mSuspendedByInactiveDocOrDocshell) {
5076 mDecoder->Suspend();
5079 if (!mPaused) {
5080 SetPlayedOrSeeked(true);
5081 if (!mSuspendedByInactiveDocOrDocshell) {
5082 mDecoder->Play();
5086 MaybeBeginCloningVisually();
5088 return NS_OK;
5091 void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags) {
5092 if (!mSrcStream) {
5093 return;
5096 bool shouldPlay = !(aFlags & REMOVING_SRC_STREAM) && !mPaused &&
5097 !mSuspendedByInactiveDocOrDocshell;
5098 if (shouldPlay == mSrcStreamIsPlaying) {
5099 return;
5101 mSrcStreamIsPlaying = shouldPlay;
5103 LOG(LogLevel::Debug,
5104 ("MediaElement %p %s playback of DOMMediaStream %p", this,
5105 shouldPlay ? "Setting up" : "Removing", mSrcStream.get()));
5107 if (shouldPlay) {
5108 mSrcStreamPlaybackEnded = false;
5109 mSrcStreamReportPlaybackEnded = false;
5111 if (mMediaStreamRenderer) {
5112 mMediaStreamRenderer->Start();
5114 if (mSecondaryMediaStreamRenderer) {
5115 mSecondaryMediaStreamRenderer->Start();
5118 SetCapturedOutputStreamsEnabled(true); // Unmute
5119 // If the input is a media stream, we don't check its data and always regard
5120 // it as audible when it's playing.
5121 SetAudibleState(true);
5122 } else {
5123 if (mMediaStreamRenderer) {
5124 mMediaStreamRenderer->Stop();
5126 if (mSecondaryMediaStreamRenderer) {
5127 mSecondaryMediaStreamRenderer->Stop();
5129 SetCapturedOutputStreamsEnabled(false); // Mute
5133 void HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying() {
5134 if (!mMediaStreamRenderer) {
5135 // Notifications are async, the renderer could have been cleared.
5136 return;
5139 mMediaStreamRenderer->SetProgressingCurrentTime(IsPotentiallyPlaying());
5142 void HTMLMediaElement::UpdateSrcStreamTime() {
5143 MOZ_ASSERT(NS_IsMainThread());
5145 if (mSrcStreamPlaybackEnded) {
5146 // We do a separate FireTimeUpdate() when this is set.
5147 return;
5150 FireTimeUpdate(TimeupdateType::ePeriodic);
5153 void HTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream* aStream) {
5154 NS_ASSERTION(!mSrcStream, "Should have been ended already");
5156 mLoadingSrc = nullptr;
5157 mSrcStream = aStream;
5159 VideoFrameContainer* container = GetVideoFrameContainer();
5160 RefPtr<FirstFrameVideoOutput> firstFrameOutput =
5161 container
5162 ? MakeAndAddRef<FirstFrameVideoOutput>(container, mAbstractMainThread)
5163 : nullptr;
5164 mMediaStreamRenderer = MakeAndAddRef<MediaStreamRenderer>(
5165 mAbstractMainThread, container, firstFrameOutput, this);
5166 mWatchManager.Watch(mPaused,
5167 &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
5168 mWatchManager.Watch(mReadyState,
5169 &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
5170 mWatchManager.Watch(mSrcStreamPlaybackEnded,
5171 &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
5172 mWatchManager.Watch(mSrcStreamPlaybackEnded,
5173 &HTMLMediaElement::UpdateSrcStreamReportPlaybackEnded);
5174 mWatchManager.Watch(mMediaStreamRenderer->CurrentGraphTime(),
5175 &HTMLMediaElement::UpdateSrcStreamTime);
5176 SetVolumeInternal();
5177 if (mSink.second) {
5178 mMediaStreamRenderer->SetAudioOutputDevice(mSink.second);
5181 UpdateSrcMediaStreamPlaying();
5182 UpdateSrcStreamPotentiallyPlaying();
5183 mSrcStreamVideoPrincipal = NodePrincipal();
5185 // If we pause this media element, track changes in the underlying stream
5186 // will continue to fire events at this element and alter its track list.
5187 // That's simpler than delaying the events, but probably confusing...
5188 nsTArray<RefPtr<MediaStreamTrack>> tracks;
5189 mSrcStream->GetTracks(tracks);
5190 for (const RefPtr<MediaStreamTrack>& track : tracks) {
5191 NotifyMediaStreamTrackAdded(track);
5194 mMediaStreamTrackListener = MakeUnique<MediaStreamTrackListener>(this);
5195 mSrcStream->RegisterTrackListener(mMediaStreamTrackListener.get());
5197 ChangeNetworkState(NETWORK_IDLE);
5198 ChangeDelayLoadStatus(false);
5200 // FirstFrameLoaded() will be called when the stream has tracks.
5203 void HTMLMediaElement::EndSrcMediaStreamPlayback() {
5204 MOZ_ASSERT(mSrcStream);
5206 UpdateSrcMediaStreamPlaying(REMOVING_SRC_STREAM);
5208 if (mSelectedVideoStreamTrack) {
5209 mSelectedVideoStreamTrack->RemovePrincipalChangeObserver(this);
5211 mSelectedVideoStreamTrack = nullptr;
5213 MOZ_ASSERT_IF(mSecondaryMediaStreamRenderer,
5214 !mMediaStreamRenderer == !mSecondaryMediaStreamRenderer);
5215 if (mMediaStreamRenderer) {
5216 mWatchManager.Unwatch(mPaused,
5217 &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
5218 mWatchManager.Unwatch(mReadyState,
5219 &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
5220 mWatchManager.Unwatch(mSrcStreamPlaybackEnded,
5221 &HTMLMediaElement::UpdateSrcStreamPotentiallyPlaying);
5222 mWatchManager.Unwatch(
5223 mSrcStreamPlaybackEnded,
5224 &HTMLMediaElement::UpdateSrcStreamReportPlaybackEnded);
5225 mWatchManager.Unwatch(mMediaStreamRenderer->CurrentGraphTime(),
5226 &HTMLMediaElement::UpdateSrcStreamTime);
5227 mMediaStreamRenderer->Shutdown();
5228 mMediaStreamRenderer = nullptr;
5230 if (mSecondaryMediaStreamRenderer) {
5231 mSecondaryMediaStreamRenderer->Shutdown();
5232 mSecondaryMediaStreamRenderer = nullptr;
5235 mSrcStream->UnregisterTrackListener(mMediaStreamTrackListener.get());
5236 mMediaStreamTrackListener = nullptr;
5237 mSrcStreamPlaybackEnded = false;
5238 mSrcStreamReportPlaybackEnded = false;
5239 mSrcStreamVideoPrincipal = nullptr;
5241 mSrcStream = nullptr;
5244 static already_AddRefed<AudioTrack> CreateAudioTrack(
5245 AudioStreamTrack* aStreamTrack, nsIGlobalObject* aOwnerGlobal) {
5246 nsAutoString id;
5247 nsAutoString label;
5248 aStreamTrack->GetId(id);
5249 aStreamTrack->GetLabel(label, CallerType::System);
5251 return MediaTrackList::CreateAudioTrack(aOwnerGlobal, id, u"main"_ns, label,
5252 u""_ns, true, aStreamTrack);
5255 static already_AddRefed<VideoTrack> CreateVideoTrack(
5256 VideoStreamTrack* aStreamTrack, nsIGlobalObject* aOwnerGlobal) {
5257 nsAutoString id;
5258 nsAutoString label;
5259 aStreamTrack->GetId(id);
5260 aStreamTrack->GetLabel(label, CallerType::System);
5262 return MediaTrackList::CreateVideoTrack(aOwnerGlobal, id, u"main"_ns, label,
5263 u""_ns, aStreamTrack);
5266 void HTMLMediaElement::NotifyMediaStreamTrackAdded(
5267 const RefPtr<MediaStreamTrack>& aTrack) {
5268 MOZ_ASSERT(aTrack);
5270 if (aTrack->Ended()) {
5271 return;
5274 #ifdef DEBUG
5275 nsAutoString id;
5276 aTrack->GetId(id);
5278 LOG(LogLevel::Debug, ("%p, Adding %sTrack with id %s", this,
5279 aTrack->AsAudioStreamTrack() ? "Audio" : "Video",
5280 NS_ConvertUTF16toUTF8(id).get()));
5281 #endif
5283 if (AudioStreamTrack* t = aTrack->AsAudioStreamTrack()) {
5284 MOZ_DIAGNOSTIC_ASSERT(AudioTracks(), "Element can't have been unlinked");
5285 RefPtr<AudioTrack> audioTrack =
5286 CreateAudioTrack(t, AudioTracks()->GetOwnerGlobal());
5287 AudioTracks()->AddTrack(audioTrack);
5288 } else if (VideoStreamTrack* t = aTrack->AsVideoStreamTrack()) {
5289 // TODO: Fix this per the spec on bug 1273443.
5290 if (!IsVideo()) {
5291 return;
5293 MOZ_DIAGNOSTIC_ASSERT(VideoTracks(), "Element can't have been unlinked");
5294 RefPtr<VideoTrack> videoTrack =
5295 CreateVideoTrack(t, VideoTracks()->GetOwnerGlobal());
5296 VideoTracks()->AddTrack(videoTrack);
5297 // New MediaStreamTrack added, set the new added video track as selected
5298 // video track when there is no selected track.
5299 if (VideoTracks()->SelectedIndex() == -1) {
5300 MOZ_ASSERT(!mSelectedVideoStreamTrack);
5301 videoTrack->SetEnabledInternal(true, dom::MediaTrack::FIRE_NO_EVENTS);
5305 // The set of enabled AudioTracks and selected video track might have changed.
5306 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
5307 AbstractThread::DispatchDirectTask(
5308 NewRunnableMethod("HTMLMediaElement::FirstFrameLoaded", this,
5309 &HTMLMediaElement::FirstFrameLoaded));
5312 void HTMLMediaElement::NotifyMediaStreamTrackRemoved(
5313 const RefPtr<MediaStreamTrack>& aTrack) {
5314 MOZ_ASSERT(aTrack);
5316 nsAutoString id;
5317 aTrack->GetId(id);
5319 LOG(LogLevel::Debug, ("%p, Removing %sTrack with id %s", this,
5320 aTrack->AsAudioStreamTrack() ? "Audio" : "Video",
5321 NS_ConvertUTF16toUTF8(id).get()));
5323 MOZ_DIAGNOSTIC_ASSERT(AudioTracks() && VideoTracks(),
5324 "Element can't have been unlinked");
5325 if (dom::MediaTrack* t = AudioTracks()->GetTrackById(id)) {
5326 AudioTracks()->RemoveTrack(t);
5327 } else if (dom::MediaTrack* t = VideoTracks()->GetTrackById(id)) {
5328 VideoTracks()->RemoveTrack(t);
5329 } else {
5330 NS_ASSERTION(aTrack->AsVideoStreamTrack() && !IsVideo(),
5331 "MediaStreamTrack ended but did not exist in track lists. "
5332 "This is only allowed if a video element ends and we are an "
5333 "audio element.");
5334 return;
5338 void HTMLMediaElement::ProcessMediaFragmentURI() {
5339 if (!mLoadingSrc) {
5340 mFragmentStart = mFragmentEnd = -1.0;
5341 return;
5343 nsMediaFragmentURIParser parser(mLoadingSrc);
5345 if (mDecoder && parser.HasEndTime()) {
5346 mFragmentEnd = parser.GetEndTime();
5349 if (parser.HasStartTime()) {
5350 SetCurrentTime(parser.GetStartTime());
5351 mFragmentStart = parser.GetStartTime();
5355 void HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
5356 UniquePtr<const MetadataTags> aTags) {
5357 MOZ_ASSERT(NS_IsMainThread());
5359 if (mDecoder) {
5360 ConstructMediaTracks(aInfo);
5363 SetMediaInfo(*aInfo);
5365 mIsEncrypted =
5366 aInfo->IsEncrypted() || mPendingEncryptedInitData.IsEncrypted();
5367 mTags = std::move(aTags);
5368 mLoadedDataFired = false;
5369 ChangeReadyState(HAVE_METADATA);
5371 // Add output tracks synchronously now to be sure they're available in
5372 // "loadedmetadata" event handlers.
5373 UpdateOutputTrackSources();
5375 DispatchAsyncEvent(u"durationchange"_ns);
5376 if (IsVideo() && HasVideo()) {
5377 DispatchAsyncEvent(u"resize"_ns);
5379 NS_ASSERTION(!HasVideo() || (mMediaInfo.mVideo.mDisplay.width > 0 &&
5380 mMediaInfo.mVideo.mDisplay.height > 0),
5381 "Video resolution must be known on 'loadedmetadata'");
5382 DispatchAsyncEvent(u"loadedmetadata"_ns);
5384 if (mDecoder && mDecoder->IsTransportSeekable() &&
5385 mDecoder->IsMediaSeekable()) {
5386 ProcessMediaFragmentURI();
5387 mDecoder->SetFragmentEndTime(mFragmentEnd);
5389 if (mIsEncrypted) {
5390 // We only support playback of encrypted content via MSE by default.
5391 if (!mMediaSource && Preferences::GetBool("media.eme.mse-only", true)) {
5392 DecodeError(
5393 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
5394 "Encrypted content not supported outside of MSE"));
5395 return;
5398 // Dispatch a distinct 'encrypted' event for each initData we have.
5399 for (const auto& initData : mPendingEncryptedInitData.mInitDatas) {
5400 DispatchEncrypted(initData.mInitData, initData.mType);
5402 mPendingEncryptedInitData.Reset();
5405 if (IsVideo() && aInfo->HasVideo()) {
5406 // We are a video element playing video so update the screen wakelock
5407 NotifyOwnerDocumentActivityChanged();
5410 if (mDefaultPlaybackStartPosition != 0.0) {
5411 SetCurrentTime(mDefaultPlaybackStartPosition);
5412 mDefaultPlaybackStartPosition = 0.0;
5415 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
5418 void HTMLMediaElement::FirstFrameLoaded() {
5419 LOG(LogLevel::Debug,
5420 ("%p, FirstFrameLoaded() mFirstFrameLoaded=%d mWaitingForKey=%d", this,
5421 mFirstFrameLoaded.Ref(), mWaitingForKey));
5423 NS_ASSERTION(!mSuspendedAfterFirstFrame, "Should not have already suspended");
5425 if (!mFirstFrameLoaded) {
5426 mFirstFrameLoaded = true;
5429 ChangeDelayLoadStatus(false);
5431 if (mDecoder && mAllowSuspendAfterFirstFrame && mPaused &&
5432 !HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
5433 mPreloadAction == HTMLMediaElement::PRELOAD_METADATA) {
5434 mSuspendedAfterFirstFrame = true;
5435 mDecoder->Suspend();
5439 void HTMLMediaElement::NetworkError(const MediaResult& aError) {
5440 if (mReadyState == HAVE_NOTHING) {
5441 NoSupportedMediaSourceError(aError.Description());
5442 } else {
5443 Error(MEDIA_ERR_NETWORK);
5447 void HTMLMediaElement::DecodeError(const MediaResult& aError) {
5448 nsAutoString src;
5449 GetCurrentSrc(src);
5450 AutoTArray<nsString, 1> params = {src};
5451 ReportLoadError("MediaLoadDecodeError", params);
5453 DecoderDoctorDiagnostics diagnostics;
5454 diagnostics.StoreDecodeError(OwnerDoc(), aError, src, __func__);
5456 if (mIsLoadingFromSourceChildren) {
5457 mErrorSink->ResetError();
5458 if (mSourceLoadCandidate) {
5459 DispatchAsyncSourceError(mSourceLoadCandidate);
5460 QueueLoadFromSourceTask();
5461 } else {
5462 NS_WARNING("Should know the source we were loading from!");
5464 } else if (mReadyState == HAVE_NOTHING) {
5465 NoSupportedMediaSourceError(aError.Description());
5466 } else if (IsCORSSameOrigin()) {
5467 Error(MEDIA_ERR_DECODE, aError.Description());
5468 } else {
5469 Error(MEDIA_ERR_DECODE, "Failed to decode media"_ns);
5473 void HTMLMediaElement::DecodeWarning(const MediaResult& aError) {
5474 nsAutoString src;
5475 GetCurrentSrc(src);
5476 DecoderDoctorDiagnostics diagnostics;
5477 diagnostics.StoreDecodeWarning(OwnerDoc(), aError, src, __func__);
5480 bool HTMLMediaElement::HasError() const { return GetError(); }
5482 void HTMLMediaElement::LoadAborted() { Error(MEDIA_ERR_ABORTED); }
5484 void HTMLMediaElement::Error(uint16_t aErrorCode,
5485 const nsACString& aErrorDetails) {
5486 mErrorSink->SetError(aErrorCode, aErrorDetails);
5487 ChangeDelayLoadStatus(false);
5488 UpdateAudioChannelPlayingState();
5491 void HTMLMediaElement::PlaybackEnded() {
5492 // We changed state which can affect AddRemoveSelfReference
5493 AddRemoveSelfReference();
5495 NS_ASSERTION(!mDecoder || mDecoder->IsEnded(),
5496 "Decoder fired ended, but not in ended state");
5498 // IsPlaybackEnded() became true.
5499 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources);
5501 if (mSrcStream) {
5502 LOG(LogLevel::Debug,
5503 ("%p, got duration by reaching the end of the resource", this));
5504 mSrcStreamPlaybackEnded = true;
5505 DispatchAsyncEvent(u"durationchange"_ns);
5506 } else {
5507 // mediacapture-main:
5508 // Setting the loop attribute has no effect since a MediaStream has no
5509 // defined end and therefore cannot be looped.
5510 if (HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) {
5511 SetCurrentTime(0);
5512 return;
5516 FireTimeUpdate(TimeupdateType::eMandatory);
5518 if (!mPaused) {
5519 Pause();
5522 if (mSrcStream) {
5523 // A MediaStream that goes from inactive to active shall be eligible for
5524 // autoplay again according to the mediacapture-main spec.
5525 mAutoplaying = true;
5528 if (StaticPrefs::media_mediacontrol_stopcontrol_aftermediaends()) {
5529 mMediaControlKeyListener->StopIfNeeded();
5531 DispatchAsyncEvent(u"ended"_ns);
5534 void HTMLMediaElement::UpdateSrcStreamReportPlaybackEnded() {
5535 mSrcStreamReportPlaybackEnded = mSrcStreamPlaybackEnded;
5538 void HTMLMediaElement::SeekStarted() { DispatchAsyncEvent(u"seeking"_ns); }
5540 void HTMLMediaElement::SeekCompleted() {
5541 mPlayingBeforeSeek = false;
5542 SetPlayedOrSeeked(true);
5543 if (mTextTrackManager) {
5544 mTextTrackManager->DidSeek();
5546 // https://html.spec.whatwg.org/multipage/media.html#seeking:dom-media-seek
5547 // (Step 16)
5548 // TODO (bug 1688131): run these steps in a stable state.
5549 FireTimeUpdate(TimeupdateType::eMandatory);
5550 DispatchAsyncEvent(u"seeked"_ns);
5551 // We changed whether we're seeking so we need to AddRemoveSelfReference
5552 AddRemoveSelfReference();
5553 if (mCurrentPlayRangeStart == -1.0) {
5554 mCurrentPlayRangeStart = CurrentTime();
5557 if (mSeekDOMPromise) {
5558 mAbstractMainThread->Dispatch(NS_NewRunnableFunction(
5559 __func__, [promise = std::move(mSeekDOMPromise)] {
5560 promise->MaybeResolveWithUndefined();
5561 }));
5563 MOZ_ASSERT(!mSeekDOMPromise);
5566 void HTMLMediaElement::SeekAborted() {
5567 if (mSeekDOMPromise) {
5568 mAbstractMainThread->Dispatch(NS_NewRunnableFunction(
5569 __func__, [promise = std::move(mSeekDOMPromise)] {
5570 promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
5571 }));
5573 MOZ_ASSERT(!mSeekDOMPromise);
5576 void HTMLMediaElement::NotifySuspendedByCache(bool aSuspendedByCache) {
5577 LOG(LogLevel::Debug,
5578 ("%p, mDownloadSuspendedByCache=%d", this, aSuspendedByCache));
5579 mDownloadSuspendedByCache = aSuspendedByCache;
5582 void HTMLMediaElement::DownloadSuspended() {
5583 if (mNetworkState == NETWORK_LOADING) {
5584 DispatchAsyncEvent(u"progress"_ns);
5586 ChangeNetworkState(NETWORK_IDLE);
5589 void HTMLMediaElement::DownloadResumed() {
5590 ChangeNetworkState(NETWORK_LOADING);
5593 void HTMLMediaElement::CheckProgress(bool aHaveNewProgress) {
5594 MOZ_ASSERT(NS_IsMainThread());
5595 MOZ_ASSERT(mNetworkState == NETWORK_LOADING);
5597 TimeStamp now = TimeStamp::NowLoRes();
5599 if (aHaveNewProgress) {
5600 mDataTime = now;
5603 // If this is the first progress, or PROGRESS_MS has passed since the last
5604 // progress event fired and more data has arrived since then, fire a
5605 // progress event.
5606 NS_ASSERTION(
5607 (mProgressTime.IsNull() && !aHaveNewProgress) || !mDataTime.IsNull(),
5608 "null TimeStamp mDataTime should not be used in comparison");
5609 if (mProgressTime.IsNull()
5610 ? aHaveNewProgress
5611 : (now - mProgressTime >=
5612 TimeDuration::FromMilliseconds(PROGRESS_MS) &&
5613 mDataTime > mProgressTime)) {
5614 DispatchAsyncEvent(u"progress"_ns);
5615 // Resolution() ensures that future data will have now > mProgressTime,
5616 // and so will trigger another event. mDataTime is not reset because it
5617 // is still required to detect stalled; it is similarly offset by
5618 // resolution to indicate the new data has not yet arrived.
5619 mProgressTime = now - TimeDuration::Resolution();
5620 if (mDataTime > mProgressTime) {
5621 mDataTime = mProgressTime;
5623 if (!mProgressTimer) {
5624 NS_ASSERTION(aHaveNewProgress,
5625 "timer dispatched when there was no timer");
5626 // Were stalled. Restart timer.
5627 StartProgressTimer();
5628 if (!mLoadedDataFired) {
5629 ChangeDelayLoadStatus(true);
5632 // Download statistics may have been updated, force a recheck of the
5633 // readyState.
5634 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
5637 if (now - mDataTime >= TimeDuration::FromMilliseconds(STALL_MS)) {
5638 if (!mMediaSource) {
5639 DispatchAsyncEvent(u"stalled"_ns);
5640 } else {
5641 ChangeDelayLoadStatus(false);
5644 NS_ASSERTION(mProgressTimer, "detected stalled without timer");
5645 // Stop timer events, which prevents repeated stalled events until there
5646 // is more progress.
5647 StopProgress();
5650 AddRemoveSelfReference();
5653 /* static */
5654 void HTMLMediaElement::ProgressTimerCallback(nsITimer* aTimer, void* aClosure) {
5655 auto* decoder = static_cast<HTMLMediaElement*>(aClosure);
5656 decoder->CheckProgress(false);
5659 void HTMLMediaElement::StartProgressTimer() {
5660 MOZ_ASSERT(NS_IsMainThread());
5661 MOZ_ASSERT(mNetworkState == NETWORK_LOADING);
5662 NS_ASSERTION(!mProgressTimer, "Already started progress timer.");
5664 NS_NewTimerWithFuncCallback(
5665 getter_AddRefs(mProgressTimer), ProgressTimerCallback, this, PROGRESS_MS,
5666 nsITimer::TYPE_REPEATING_SLACK, "HTMLMediaElement::ProgressTimerCallback",
5667 mMainThreadEventTarget);
5670 void HTMLMediaElement::StartProgress() {
5671 // Record the time now for detecting stalled.
5672 mDataTime = TimeStamp::NowLoRes();
5673 // Reset mProgressTime so that mDataTime is not indicating bytes received
5674 // after the last progress event.
5675 mProgressTime = TimeStamp();
5676 StartProgressTimer();
5679 void HTMLMediaElement::StopProgress() {
5680 MOZ_ASSERT(NS_IsMainThread());
5681 if (!mProgressTimer) {
5682 return;
5685 mProgressTimer->Cancel();
5686 mProgressTimer = nullptr;
5689 void HTMLMediaElement::DownloadProgressed() {
5690 if (mNetworkState != NETWORK_LOADING) {
5691 return;
5693 CheckProgress(true);
5696 bool HTMLMediaElement::ShouldCheckAllowOrigin() {
5697 return mCORSMode != CORS_NONE;
5700 bool HTMLMediaElement::IsCORSSameOrigin() {
5701 bool subsumes;
5702 RefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
5703 return (NS_SUCCEEDED(NodePrincipal()->Subsumes(principal, &subsumes)) &&
5704 subsumes) ||
5705 ShouldCheckAllowOrigin();
5708 void HTMLMediaElement::UpdateReadyStateInternal() {
5709 if (!mDecoder && !mSrcStream) {
5710 // Not initialized - bail out.
5711 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
5712 "Not initialized",
5713 this));
5714 return;
5717 if (mDecoder && mReadyState < HAVE_METADATA) {
5718 // aNextFrame might have a next frame because the decoder can advance
5719 // on its own thread before MetadataLoaded gets a chance to run.
5720 // The arrival of more data can't change us out of this readyState.
5721 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
5722 "Decoder ready state < HAVE_METADATA",
5723 this));
5724 return;
5727 if (mDecoder) {
5728 // IsPlaybackEnded() might have become false.
5729 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateOutputTrackSources);
5732 if (mSrcStream && mReadyState < HAVE_METADATA) {
5733 bool hasAudioTracks = AudioTracks() && !AudioTracks()->IsEmpty();
5734 bool hasVideoTracks = VideoTracks() && !VideoTracks()->IsEmpty();
5735 if (!hasAudioTracks && !hasVideoTracks) {
5736 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
5737 "Stream with no tracks",
5738 this));
5739 // Give it one last chance to remove the self reference if needed.
5740 AddRemoveSelfReference();
5741 return;
5744 if (IsVideo() && hasVideoTracks && !HasVideo()) {
5745 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
5746 "Stream waiting for video",
5747 this));
5748 return;
5751 LOG(LogLevel::Debug,
5752 ("MediaElement %p UpdateReadyStateInternal() Stream has "
5753 "metadata; audioTracks=%d, videoTracks=%d, "
5754 "hasVideoFrame=%d",
5755 this, AudioTracks()->Length(), VideoTracks()->Length(), HasVideo()));
5757 // We are playing a stream that has video and a video frame is now set.
5758 // This means we have all metadata needed to change ready state.
5759 MediaInfo mediaInfo = mMediaInfo;
5760 if (hasAudioTracks) {
5761 mediaInfo.EnableAudio();
5763 if (hasVideoTracks) {
5764 mediaInfo.EnableVideo();
5765 if (mSelectedVideoStreamTrack) {
5766 mediaInfo.mVideo.SetAlpha(mSelectedVideoStreamTrack->HasAlpha());
5769 MetadataLoaded(&mediaInfo, nullptr);
5772 if (mMediaSource) {
5773 // readyState has changed, assuming it's following the pending mediasource
5774 // operations. Notify the Mediasource that the operations have completed.
5775 mMediaSource->CompletePendingTransactions();
5778 enum NextFrameStatus nextFrameStatus = NextFrameStatus();
5779 if (mWaitingForKey == NOT_WAITING_FOR_KEY) {
5780 if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE && mDecoder &&
5781 !mDecoder->IsEnded()) {
5782 nextFrameStatus = mDecoder->NextFrameBufferedStatus();
5784 } else if (mWaitingForKey == WAITING_FOR_KEY) {
5785 if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE ||
5786 nextFrameStatus == NEXT_FRAME_UNAVAILABLE_BUFFERING) {
5787 // http://w3c.github.io/encrypted-media/#wait-for-key
5788 // Continuing 7.3.4 Queue a "waitingforkey" Event
5789 // 4. Queue a task to fire a simple event named waitingforkey
5790 // at the media element.
5791 // 5. Set the readyState of media element to HAVE_METADATA.
5792 // NOTE: We'll change to HAVE_CURRENT_DATA or HAVE_METADATA
5793 // depending on whether we've loaded the first frame or not
5794 // below.
5795 // 6. Suspend playback.
5796 // Note: Playback will already be stalled, as the next frame is
5797 // unavailable.
5798 mWaitingForKey = WAITING_FOR_KEY_DISPATCHED;
5799 DispatchAsyncEvent(u"waitingforkey"_ns);
5801 } else {
5802 MOZ_ASSERT(mWaitingForKey == WAITING_FOR_KEY_DISPATCHED);
5803 if (nextFrameStatus == NEXT_FRAME_AVAILABLE) {
5804 // We have new frames after dispatching "waitingforkey".
5805 // This means we've got the key and can reset mWaitingForKey now.
5806 mWaitingForKey = NOT_WAITING_FOR_KEY;
5810 if (nextFrameStatus == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING) {
5811 LOG(LogLevel::Debug,
5812 ("MediaElement %p UpdateReadyStateInternal() "
5813 "NEXT_FRAME_UNAVAILABLE_SEEKING; Forcing HAVE_METADATA",
5814 this));
5815 ChangeReadyState(HAVE_METADATA);
5816 return;
5819 if (IsVideo() && VideoTracks() && !VideoTracks()->IsEmpty() &&
5820 !IsPlaybackEnded() && GetImageContainer() &&
5821 !GetImageContainer()->HasCurrentImage()) {
5822 // Don't advance if we are playing video, but don't have a video frame.
5823 // Also, if video became available after advancing to HAVE_CURRENT_DATA
5824 // while we are still playing, we need to revert to HAVE_METADATA until
5825 // a video frame is available.
5826 LOG(LogLevel::Debug,
5827 ("MediaElement %p UpdateReadyStateInternal() "
5828 "Playing video but no video frame; Forcing HAVE_METADATA",
5829 this));
5830 ChangeReadyState(HAVE_METADATA);
5831 return;
5834 if (!mFirstFrameLoaded) {
5835 // We haven't yet loaded the first frame, making us unable to determine
5836 // if we have enough valid data at the present stage.
5837 return;
5840 if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE_BUFFERING) {
5841 // Force HAVE_CURRENT_DATA when buffering.
5842 ChangeReadyState(HAVE_CURRENT_DATA);
5843 return;
5846 // TextTracks must be loaded for the HAVE_ENOUGH_DATA and
5847 // HAVE_FUTURE_DATA.
5848 // So force HAVE_CURRENT_DATA if text tracks not loaded.
5849 if (mTextTrackManager && !mTextTrackManager->IsLoaded()) {
5850 ChangeReadyState(HAVE_CURRENT_DATA);
5851 return;
5854 if (mDownloadSuspendedByCache && mDecoder && !mDecoder->IsEnded()) {
5855 // The decoder has signaled that the download has been suspended by the
5856 // media cache. So move readyState into HAVE_ENOUGH_DATA, in case there's
5857 // script waiting for a "canplaythrough" event; without this forced
5858 // transition, we will never fire the "canplaythrough" event if the
5859 // media cache is too small, and scripts are bound to fail. Don't force
5860 // this transition if the decoder is in ended state; the readyState
5861 // should remain at HAVE_CURRENT_DATA in this case.
5862 // Note that this state transition includes the case where we finished
5863 // downloaded the whole data stream.
5864 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
5865 "Decoder download suspended by cache",
5866 this));
5867 ChangeReadyState(HAVE_ENOUGH_DATA);
5868 return;
5871 if (nextFrameStatus != MediaDecoderOwner::NEXT_FRAME_AVAILABLE) {
5872 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
5873 "Next frame not available",
5874 this));
5875 ChangeReadyState(HAVE_CURRENT_DATA);
5876 return;
5879 if (mSrcStream) {
5880 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
5881 "Stream HAVE_ENOUGH_DATA",
5882 this));
5883 ChangeReadyState(HAVE_ENOUGH_DATA);
5884 return;
5887 // Now see if we should set HAVE_ENOUGH_DATA.
5888 // If it's something we don't know the size of, then we can't
5889 // make a real estimate, so we go straight to HAVE_ENOUGH_DATA once
5890 // we've downloaded enough data that our download rate is considered
5891 // reliable. We have to move to HAVE_ENOUGH_DATA at some point or
5892 // autoplay elements for live streams will never play. Otherwise we
5893 // move to HAVE_ENOUGH_DATA if we can play through the entire media
5894 // without stopping to buffer.
5895 if (mDecoder->CanPlayThrough()) {
5896 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
5897 "Decoder can play through",
5898 this));
5899 ChangeReadyState(HAVE_ENOUGH_DATA);
5900 return;
5902 LOG(LogLevel::Debug, ("MediaElement %p UpdateReadyStateInternal() "
5903 "Default; Decoder has future data",
5904 this));
5905 ChangeReadyState(HAVE_FUTURE_DATA);
5908 static const char* const gReadyStateToString[] = {
5909 "HAVE_NOTHING", "HAVE_METADATA", "HAVE_CURRENT_DATA", "HAVE_FUTURE_DATA",
5910 "HAVE_ENOUGH_DATA"};
5912 void HTMLMediaElement::ChangeReadyState(nsMediaReadyState aState) {
5913 if (mReadyState == aState) {
5914 return;
5917 nsMediaReadyState oldState = mReadyState;
5918 mReadyState = aState;
5919 LOG(LogLevel::Debug,
5920 ("%p Ready state changed to %s", this, gReadyStateToString[aState]));
5922 DDLOG(DDLogCategory::Property, "ready_state", gReadyStateToString[aState]);
5924 // https://html.spec.whatwg.org/multipage/media.html#text-track-cue-active-flag
5925 // The user agent must synchronously unset cues' active flag whenever the
5926 // media element's readyState is changed back to HAVE_NOTHING.
5927 if (mReadyState == HAVE_NOTHING && mTextTrackManager) {
5928 mTextTrackManager->NotifyReset();
5931 if (mNetworkState == NETWORK_EMPTY) {
5932 return;
5935 UpdateAudioChannelPlayingState();
5937 // Handle raising of "waiting" event during seek (see 4.8.10.9)
5938 // or
5939 // 4.8.12.7 Ready states:
5940 // "If the previous ready state was HAVE_FUTURE_DATA or more, and the new
5941 // ready state is HAVE_CURRENT_DATA or less
5942 // If the media element was potentially playing before its readyState
5943 // attribute changed to a value lower than HAVE_FUTURE_DATA, and the element
5944 // has not ended playback, and playback has not stopped due to errors,
5945 // paused for user interaction, or paused for in-band content, the user agent
5946 // must queue a task to fire a simple event named timeupdate at the element,
5947 // and queue a task to fire a simple event named waiting at the element."
5948 if (mPlayingBeforeSeek && mReadyState < HAVE_FUTURE_DATA) {
5949 DispatchAsyncEvent(u"waiting"_ns);
5950 } else if (oldState >= HAVE_FUTURE_DATA && mReadyState < HAVE_FUTURE_DATA &&
5951 !Paused() && !Ended() && !mErrorSink->mError) {
5952 FireTimeUpdate(TimeupdateType::eMandatory);
5953 DispatchAsyncEvent(u"waiting"_ns);
5956 if (oldState < HAVE_CURRENT_DATA && mReadyState >= HAVE_CURRENT_DATA &&
5957 !mLoadedDataFired) {
5958 DispatchAsyncEvent(u"loadeddata"_ns);
5959 mLoadedDataFired = true;
5962 if (oldState < HAVE_FUTURE_DATA && mReadyState >= HAVE_FUTURE_DATA) {
5963 DispatchAsyncEvent(u"canplay"_ns);
5964 if (!mPaused) {
5965 if (mDecoder && !mSuspendedByInactiveDocOrDocshell) {
5966 MOZ_ASSERT(AllowedToPlay());
5967 mDecoder->Play();
5969 NotifyAboutPlaying();
5973 CheckAutoplayDataReady();
5975 if (oldState < HAVE_ENOUGH_DATA && mReadyState >= HAVE_ENOUGH_DATA) {
5976 DispatchAsyncEvent(u"canplaythrough"_ns);
5980 static const char* const gNetworkStateToString[] = {"EMPTY", "IDLE", "LOADING",
5981 "NO_SOURCE"};
5983 void HTMLMediaElement::ChangeNetworkState(nsMediaNetworkState aState) {
5984 if (mNetworkState == aState) {
5985 return;
5988 nsMediaNetworkState oldState = mNetworkState;
5989 mNetworkState = aState;
5990 LOG(LogLevel::Debug,
5991 ("%p Network state changed to %s", this, gNetworkStateToString[aState]));
5992 DDLOG(DDLogCategory::Property, "network_state",
5993 gNetworkStateToString[aState]);
5995 if (oldState == NETWORK_LOADING) {
5996 // Stop progress notification when exiting NETWORK_LOADING.
5997 StopProgress();
6000 if (mNetworkState == NETWORK_LOADING) {
6001 // Start progress notification when entering NETWORK_LOADING.
6002 StartProgress();
6003 } else if (mNetworkState == NETWORK_IDLE && !mErrorSink->mError) {
6004 // Fire 'suspend' event when entering NETWORK_IDLE and no error presented.
6005 DispatchAsyncEvent(u"suspend"_ns);
6008 // According to the resource selection (step2, step9-18), dedicated media
6009 // source failure step (step4) and aborting existing load (step4), set show
6010 // poster flag to true. https://html.spec.whatwg.org/multipage/media.html
6011 if (mNetworkState == NETWORK_NO_SOURCE || mNetworkState == NETWORK_EMPTY) {
6012 mShowPoster = true;
6015 // Changing mNetworkState affects AddRemoveSelfReference().
6016 AddRemoveSelfReference();
6019 bool HTMLMediaElement::CanActivateAutoplay() {
6020 // We also activate autoplay when playing a media source since the data
6021 // download is controlled by the script and there is no way to evaluate
6022 // MediaDecoder::CanPlayThrough().
6024 if (!HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) {
6025 return false;
6028 if (!mAutoplaying) {
6029 return false;
6032 if (IsEditable()) {
6033 return false;
6036 if (!mPaused) {
6037 return false;
6040 if (mSuspendedByInactiveDocOrDocshell) {
6041 return false;
6044 // Static document is used for print preview and printing, should not be
6045 // autoplay
6046 if (OwnerDoc()->IsStaticDocument()) {
6047 return false;
6050 if (ShouldBeSuspendedByInactiveDocShell()) {
6051 LOG(LogLevel::Debug, ("%p prohibiting autoplay by the docShell", this));
6052 return false;
6055 if (MediaPlaybackDelayPolicy::ShouldDelayPlayback(this)) {
6056 CreateResumeDelayedMediaPlaybackAgentIfNeeded();
6057 LOG(LogLevel::Debug, ("%p delay playing from autoplay", this));
6058 return false;
6061 return mReadyState >= HAVE_ENOUGH_DATA;
6064 void HTMLMediaElement::CheckAutoplayDataReady() {
6065 if (!CanActivateAutoplay()) {
6066 return;
6069 if (!AllowedToPlay()) {
6070 DispatchEventsWhenPlayWasNotAllowed();
6071 return;
6074 mAllowedToPlayPromise.ResolveIfExists(true, __func__);
6075 mPaused = false;
6076 // We changed mPaused which can affect AddRemoveSelfReference
6077 AddRemoveSelfReference();
6078 UpdateSrcMediaStreamPlaying();
6079 UpdateAudioChannelPlayingState();
6080 StartMediaControlKeyListenerIfNeeded();
6082 if (mDecoder) {
6083 SetPlayedOrSeeked(true);
6084 if (mCurrentPlayRangeStart == -1.0) {
6085 mCurrentPlayRangeStart = CurrentTime();
6087 MOZ_ASSERT(!mSuspendedByInactiveDocOrDocshell);
6088 mDecoder->Play();
6089 } else if (mSrcStream) {
6090 SetPlayedOrSeeked(true);
6093 // https://html.spec.whatwg.org/multipage/media.html#ready-states:show-poster-flag
6094 if (mShowPoster) {
6095 mShowPoster = false;
6096 if (mTextTrackManager) {
6097 mTextTrackManager->TimeMarchesOn();
6101 // For blocked media, the event would be pending until it is resumed.
6102 DispatchAsyncEvent(u"play"_ns);
6104 DispatchAsyncEvent(u"playing"_ns);
6107 bool HTMLMediaElement::IsActuallyInvisible() const {
6108 // That means an element is not connected. It probably hasn't connected to a
6109 // document tree, or connects to a disconnected DOM tree.
6110 if (!IsInComposedDoc()) {
6111 return true;
6114 // An element is not in user's view port, which means it's either existing in
6115 // somewhere in the page where user hasn't seen yet, or is being set
6116 // `display:none`.
6117 if (!IsInViewPort()) {
6118 return true;
6121 // Element being used in picture-in-picture mode would be always visible.
6122 if (IsBeingUsedInPictureInPictureMode()) {
6123 return false;
6126 // That check is the page is in the background.
6127 return OwnerDoc()->Hidden();
6130 bool HTMLMediaElement::IsInViewPort() const {
6131 return mVisibilityState == Visibility::ApproximatelyVisible;
6134 VideoFrameContainer* HTMLMediaElement::GetVideoFrameContainer() {
6135 if (mShuttingDown) {
6136 return nullptr;
6139 if (mVideoFrameContainer) return mVideoFrameContainer;
6141 // Only video frames need an image container.
6142 if (!IsVideo()) {
6143 return nullptr;
6146 mVideoFrameContainer = new VideoFrameContainer(
6147 this, MakeAndAddRef<ImageContainer>(ImageContainer::ASYNCHRONOUS));
6149 return mVideoFrameContainer;
6152 void HTMLMediaElement::PrincipalChanged(MediaStreamTrack* aTrack) {
6153 if (aTrack != mSelectedVideoStreamTrack) {
6154 return;
6157 nsContentUtils::CombineResourcePrincipals(&mSrcStreamVideoPrincipal,
6158 aTrack->GetPrincipal());
6160 LOG(LogLevel::Debug,
6161 ("HTMLMediaElement %p video track principal changed to %p (combined "
6162 "into %p). Waiting for it to reach VideoFrameContainer before setting.",
6163 this, aTrack->GetPrincipal(), mSrcStreamVideoPrincipal.get()));
6165 if (mVideoFrameContainer) {
6166 UpdateSrcStreamVideoPrincipal(
6167 mVideoFrameContainer->GetLastPrincipalHandle());
6171 void HTMLMediaElement::UpdateSrcStreamVideoPrincipal(
6172 const PrincipalHandle& aPrincipalHandle) {
6173 nsTArray<RefPtr<VideoStreamTrack>> videoTracks;
6174 mSrcStream->GetVideoTracks(videoTracks);
6176 for (const RefPtr<VideoStreamTrack>& track : videoTracks) {
6177 if (PrincipalHandleMatches(aPrincipalHandle, track->GetPrincipal()) &&
6178 !track->Ended()) {
6179 // When the PrincipalHandle for the VideoFrameContainer changes to that of
6180 // a live track in mSrcStream we know that a removed track was displayed
6181 // but is no longer so.
6182 LOG(LogLevel::Debug, ("HTMLMediaElement %p VideoFrameContainer's "
6183 "PrincipalHandle matches track %p. That's all we "
6184 "need.",
6185 this, track.get()));
6186 mSrcStreamVideoPrincipal = track->GetPrincipal();
6187 break;
6192 void HTMLMediaElement::PrincipalHandleChangedForVideoFrameContainer(
6193 VideoFrameContainer* aContainer,
6194 const PrincipalHandle& aNewPrincipalHandle) {
6195 MOZ_ASSERT(NS_IsMainThread());
6197 if (!mSrcStream) {
6198 return;
6201 LOG(LogLevel::Debug, ("HTMLMediaElement %p PrincipalHandle changed in "
6202 "VideoFrameContainer.",
6203 this));
6205 UpdateSrcStreamVideoPrincipal(aNewPrincipalHandle);
6208 already_AddRefed<nsMediaEventRunner> HTMLMediaElement::GetEventRunner(
6209 const nsAString& aName, EventFlag aFlag) {
6210 RefPtr<nsMediaEventRunner> runner;
6211 if (aName.EqualsLiteral("playing")) {
6212 runner = new nsNotifyAboutPlayingRunner(this, TakePendingPlayPromises());
6213 } else if (aName.EqualsLiteral("timeupdate")) {
6214 runner = new nsTimeupdateRunner(this, aFlag == EventFlag::eMandatory);
6215 } else {
6216 runner = new nsAsyncEventRunner(aName, this);
6218 return runner.forget();
6221 nsresult HTMLMediaElement::DispatchEvent(const nsAString& aName) {
6222 LOG_EVENT(LogLevel::Debug, ("%p Dispatching event %s", this,
6223 NS_ConvertUTF16toUTF8(aName).get()));
6225 if (mEventBlocker->ShouldBlockEventDelivery()) {
6226 RefPtr<nsMediaEventRunner> runner = GetEventRunner(aName);
6227 mEventBlocker->PostponeEvent(runner);
6228 return NS_OK;
6231 return nsContentUtils::DispatchTrustedEvent(
6232 OwnerDoc(), static_cast<nsIContent*>(this), aName, CanBubble::eNo,
6233 Cancelable::eNo);
6236 void HTMLMediaElement::DispatchAsyncEvent(const nsAString& aName) {
6237 RefPtr<nsMediaEventRunner> runner = GetEventRunner(aName);
6238 DispatchAsyncEvent(std::move(runner));
6241 void HTMLMediaElement::DispatchAsyncEvent(RefPtr<nsMediaEventRunner> aRunner) {
6242 NS_ConvertUTF16toUTF8 eventName(aRunner->EventName());
6243 LOG_EVENT(LogLevel::Debug, ("%p Queuing event %s", this, eventName.get()));
6244 DDLOG(DDLogCategory::Event, "HTMLMediaElement", nsCString(eventName.get()));
6245 if (mEventBlocker->ShouldBlockEventDelivery()) {
6246 mEventBlocker->PostponeEvent(aRunner);
6247 return;
6249 mMainThreadEventTarget->Dispatch(aRunner.forget());
6252 bool HTMLMediaElement::IsPotentiallyPlaying() const {
6253 // TODO:
6254 // playback has not stopped due to errors,
6255 // and the element has not paused for user interaction
6256 return !mPaused &&
6257 (mReadyState == HAVE_ENOUGH_DATA || mReadyState == HAVE_FUTURE_DATA) &&
6258 !IsPlaybackEnded();
6261 bool HTMLMediaElement::IsPlaybackEnded() const {
6262 // TODO:
6263 // the current playback position is equal to the effective end of the media
6264 // resource. See bug 449157.
6265 if (mDecoder) {
6266 return mReadyState >= HAVE_METADATA && mDecoder->IsEnded();
6268 if (mSrcStream) {
6269 return mReadyState >= HAVE_METADATA && mSrcStreamPlaybackEnded;
6271 return false;
6274 already_AddRefed<nsIPrincipal> HTMLMediaElement::GetCurrentPrincipal() {
6275 if (mDecoder) {
6276 return mDecoder->GetCurrentPrincipal();
6278 if (mSrcStream) {
6279 nsTArray<RefPtr<MediaStreamTrack>> tracks;
6280 mSrcStream->GetTracks(tracks);
6281 nsCOMPtr<nsIPrincipal> principal = mSrcStream->GetPrincipal();
6282 return principal.forget();
6284 return nullptr;
6287 bool HTMLMediaElement::HadCrossOriginRedirects() {
6288 if (mDecoder) {
6289 return mDecoder->HadCrossOriginRedirects();
6291 return false;
6294 already_AddRefed<nsIPrincipal> HTMLMediaElement::GetCurrentVideoPrincipal() {
6295 if (mDecoder) {
6296 return mDecoder->GetCurrentPrincipal();
6298 if (mSrcStream) {
6299 nsCOMPtr<nsIPrincipal> principal = mSrcStreamVideoPrincipal;
6300 return principal.forget();
6302 return nullptr;
6305 void HTMLMediaElement::NotifyDecoderPrincipalChanged() {
6306 RefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
6307 bool isSameOrigin = !principal || IsCORSSameOrigin();
6308 mDecoder->UpdateSameOriginStatus(isSameOrigin);
6310 if (isSameOrigin) {
6311 principal = NodePrincipal();
6313 for (const auto& entry : mOutputTrackSources.Values()) {
6314 entry->SetPrincipal(principal);
6316 mDecoder->SetOutputTracksPrincipal(principal);
6319 void HTMLMediaElement::Invalidate(bool aImageSizeChanged,
6320 Maybe<nsIntSize>& aNewIntrinsicSize,
6321 bool aForceInvalidate) {
6322 nsIFrame* frame = GetPrimaryFrame();
6323 if (aNewIntrinsicSize) {
6324 UpdateMediaSize(aNewIntrinsicSize.value());
6325 if (frame) {
6326 nsPresContext* presContext = frame->PresContext();
6327 PresShell* presShell = presContext->PresShell();
6328 presShell->FrameNeedsReflow(frame, IntrinsicDirty::StyleChange,
6329 NS_FRAME_IS_DIRTY);
6333 RefPtr<ImageContainer> imageContainer = GetImageContainer();
6334 bool asyncInvalidate =
6335 imageContainer && imageContainer->IsAsync() && !aForceInvalidate;
6336 if (frame) {
6337 if (aImageSizeChanged) {
6338 frame->InvalidateFrame();
6339 } else {
6340 frame->InvalidateLayer(DisplayItemType::TYPE_VIDEO, nullptr, nullptr,
6341 asyncInvalidate ? nsIFrame::UPDATE_IS_ASYNC : 0);
6345 SVGObserverUtils::InvalidateDirectRenderingObservers(this);
6348 void HTMLMediaElement::UpdateMediaSize(const nsIntSize& aSize) {
6349 MOZ_ASSERT(NS_IsMainThread());
6351 if (IsVideo() && mReadyState != HAVE_NOTHING &&
6352 mMediaInfo.mVideo.mDisplay != aSize) {
6353 DispatchAsyncEvent(u"resize"_ns);
6356 mMediaInfo.mVideo.mDisplay = aSize;
6357 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
6360 void HTMLMediaElement::SuspendOrResumeElement(bool aSuspendElement) {
6361 LOG(LogLevel::Debug, ("%p SuspendOrResumeElement(suspend=%d) docHidden=%d",
6362 this, aSuspendElement, OwnerDoc()->Hidden()));
6364 if (aSuspendElement == mSuspendedByInactiveDocOrDocshell) {
6365 return;
6368 mSuspendedByInactiveDocOrDocshell = aSuspendElement;
6369 UpdateSrcMediaStreamPlaying();
6370 UpdateAudioChannelPlayingState();
6372 if (aSuspendElement) {
6373 if (mDecoder) {
6374 mDecoder->Pause();
6375 mDecoder->Suspend();
6376 mDecoder->SetDelaySeekMode(true);
6378 mEventBlocker->SetBlockEventDelivery(true);
6379 // We won't want to resume media element from the bfcache.
6380 ClearResumeDelayedMediaPlaybackAgentIfNeeded();
6381 mMediaControlKeyListener->StopIfNeeded();
6382 } else {
6383 if (mDecoder) {
6384 mDecoder->Resume();
6385 if (!mPaused && !mDecoder->IsEnded()) {
6386 mDecoder->Play();
6388 mDecoder->SetDelaySeekMode(false);
6390 mEventBlocker->SetBlockEventDelivery(false);
6391 // If the media element has been blocked and isn't still allowed to play
6392 // when it comes back from the bfcache, we would notify front end to show
6393 // the blocking icon in order to inform user that the site is still being
6394 // blocked.
6395 if (mHasEverBeenBlockedForAutoplay && !AllowedToPlay()) {
6396 MaybeNotifyAutoplayBlocked();
6398 StartMediaControlKeyListenerIfNeeded();
6400 if (StaticPrefs::media_testing_only_events()) {
6401 auto dispatcher = MakeRefPtr<AsyncEventDispatcher>(
6402 this, u"MozMediaSuspendChanged"_ns, CanBubble::eYes,
6403 ChromeOnlyDispatch::eYes);
6404 dispatcher->PostDOMEvent();
6408 bool HTMLMediaElement::IsBeingDestroyed() {
6409 nsIDocShell* docShell = OwnerDoc()->GetDocShell();
6410 bool isBeingDestroyed = false;
6411 if (docShell) {
6412 docShell->IsBeingDestroyed(&isBeingDestroyed);
6414 return isBeingDestroyed;
6417 bool HTMLMediaElement::ShouldBeSuspendedByInactiveDocShell() const {
6418 BrowsingContext* bc = OwnerDoc()->GetBrowsingContext();
6419 return bc && !bc->IsActive() && bc->Top()->GetSuspendMediaWhenInactive();
6422 void HTMLMediaElement::NotifyOwnerDocumentActivityChanged() {
6423 if (mDecoder && !IsBeingDestroyed()) {
6424 NotifyDecoderActivityChanges();
6427 // We would suspend media when the document is inactive, or its docshell has
6428 // been set to hidden and explicitly wants to suspend media. In those cases,
6429 // the media would be not visible and we don't want them to continue playing.
6430 bool shouldSuspend =
6431 !OwnerDoc()->IsActive() || ShouldBeSuspendedByInactiveDocShell();
6432 SuspendOrResumeElement(shouldSuspend);
6434 // If the owning document has become inactive we should shutdown the CDM.
6435 if (!OwnerDoc()->IsCurrentActiveDocument() && mMediaKeys) {
6436 // We don't shutdown MediaKeys here because it also listens for document
6437 // activity and will take care of shutting down itself.
6438 DDUNLINKCHILD(mMediaKeys.get());
6439 mMediaKeys = nullptr;
6440 if (mDecoder) {
6441 ShutdownDecoder();
6445 AddRemoveSelfReference();
6448 void HTMLMediaElement::NotifyFullScreenChanged() {
6449 const bool isInFullScreen = IsInFullScreen();
6450 if (isInFullScreen) {
6451 StartMediaControlKeyListenerIfNeeded();
6452 if (!mMediaControlKeyListener->IsStarted()) {
6453 MEDIACONTROL_LOG("Failed to start the listener when entering fullscreen");
6456 // Updating controller fullscreen state no matter the listener starts or not.
6457 BrowsingContext* bc = OwnerDoc()->GetBrowsingContext();
6458 if (RefPtr<IMediaInfoUpdater> updater = ContentMediaAgent::Get(bc)) {
6459 updater->NotifyMediaFullScreenState(bc->Id(), isInFullScreen);
6463 void HTMLMediaElement::AddRemoveSelfReference() {
6464 // XXX we could release earlier here in many situations if we examined
6465 // which event listeners are attached. Right now we assume there is a
6466 // potential listener for every event. We would also have to keep the
6467 // element alive if it was playing and producing audio output --- right now
6468 // that's covered by the !mPaused check.
6469 Document* ownerDoc = OwnerDoc();
6471 // See the comment at the top of this file for the explanation of this
6472 // boolean expression.
6473 bool needSelfReference =
6474 !mShuttingDown && ownerDoc->IsActive() &&
6475 (mDelayingLoadEvent || (!mPaused && !Ended()) ||
6476 (mDecoder && mDecoder->IsSeeking()) || CanActivateAutoplay() ||
6477 (mMediaSource ? mProgressTimer : mNetworkState == NETWORK_LOADING));
6479 if (needSelfReference != mHasSelfReference) {
6480 mHasSelfReference = needSelfReference;
6481 RefPtr<HTMLMediaElement> self = this;
6482 if (needSelfReference) {
6483 // The shutdown observer will hold a strong reference to us. This
6484 // will do to keep us alive. We need to know about shutdown so that
6485 // we can release our self-reference.
6486 mMainThreadEventTarget->Dispatch(NS_NewRunnableFunction(
6487 "dom::HTMLMediaElement::AddSelfReference",
6488 [self]() { self->mShutdownObserver->AddRefMediaElement(); }));
6489 } else {
6490 // Dispatch Release asynchronously so that we don't destroy this object
6491 // inside a call stack of method calls on this object
6492 mMainThreadEventTarget->Dispatch(NS_NewRunnableFunction(
6493 "dom::HTMLMediaElement::AddSelfReference",
6494 [self]() { self->mShutdownObserver->ReleaseMediaElement(); }));
6499 void HTMLMediaElement::NotifyShutdownEvent() {
6500 mShuttingDown = true;
6501 ResetState();
6502 AddRemoveSelfReference();
6505 void HTMLMediaElement::DispatchAsyncSourceError(nsIContent* aSourceElement) {
6506 LOG_EVENT(LogLevel::Debug, ("%p Queuing simple source error event", this));
6508 nsCOMPtr<nsIRunnable> event =
6509 new nsSourceErrorEventRunner(this, aSourceElement);
6510 mMainThreadEventTarget->Dispatch(event.forget());
6513 void HTMLMediaElement::NotifyAddedSource() {
6514 // If a source element is inserted as a child of a media element
6515 // that has no src attribute and whose networkState has the value
6516 // NETWORK_EMPTY, the user agent must invoke the media element's
6517 // resource selection algorithm.
6518 if (!HasAttr(kNameSpaceID_None, nsGkAtoms::src) &&
6519 mNetworkState == NETWORK_EMPTY) {
6520 AssertReadyStateIsNothing();
6521 QueueSelectResourceTask();
6524 // A load was paused in the resource selection algorithm, waiting for
6525 // a new source child to be added, resume the resource selection algorithm.
6526 if (mLoadWaitStatus == WAITING_FOR_SOURCE) {
6527 // Rest the flag so we don't queue multiple LoadFromSourceTask() when
6528 // multiple <source> are attached in an event loop.
6529 mLoadWaitStatus = NOT_WAITING;
6530 QueueLoadFromSourceTask();
6534 Element* HTMLMediaElement::GetNextSource() {
6535 mSourceLoadCandidate = nullptr;
6537 while (true) {
6538 if (mSourcePointer == nsINode::GetLastChild()) {
6539 return nullptr; // no more children
6542 if (!mSourcePointer) {
6543 mSourcePointer = nsINode::GetFirstChild();
6544 } else {
6545 mSourcePointer = mSourcePointer->GetNextSibling();
6547 nsIContent* child = mSourcePointer;
6549 // If child is a <source> element, it is the next candidate.
6550 if (child && child->IsHTMLElement(nsGkAtoms::source)) {
6551 mSourceLoadCandidate = child;
6552 return child->AsElement();
6555 MOZ_ASSERT_UNREACHABLE("Execution should not reach here!");
6556 return nullptr;
6559 void HTMLMediaElement::ChangeDelayLoadStatus(bool aDelay) {
6560 if (mDelayingLoadEvent == aDelay) return;
6562 mDelayingLoadEvent = aDelay;
6564 LOG(LogLevel::Debug, ("%p ChangeDelayLoadStatus(%d) doc=0x%p", this, aDelay,
6565 mLoadBlockedDoc.get()));
6566 if (mDecoder) {
6567 mDecoder->SetLoadInBackground(!aDelay);
6569 if (aDelay) {
6570 mLoadBlockedDoc = OwnerDoc();
6571 mLoadBlockedDoc->BlockOnload();
6572 } else {
6573 // mLoadBlockedDoc might be null due to GC unlinking
6574 if (mLoadBlockedDoc) {
6575 mLoadBlockedDoc->UnblockOnload(false);
6576 mLoadBlockedDoc = nullptr;
6580 // We changed mDelayingLoadEvent which can affect AddRemoveSelfReference
6581 AddRemoveSelfReference();
6584 already_AddRefed<nsILoadGroup> HTMLMediaElement::GetDocumentLoadGroup() {
6585 if (!OwnerDoc()->IsActive()) {
6586 NS_WARNING("Load group requested for media element in inactive document.");
6588 return OwnerDoc()->GetDocumentLoadGroup();
6591 nsresult HTMLMediaElement::CopyInnerTo(Element* aDest) {
6592 nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest);
6593 NS_ENSURE_SUCCESS(rv, rv);
6594 if (aDest->OwnerDoc()->IsStaticDocument()) {
6595 HTMLMediaElement* dest = static_cast<HTMLMediaElement*>(aDest);
6596 dest->SetMediaInfo(mMediaInfo);
6598 return rv;
6601 already_AddRefed<TimeRanges> HTMLMediaElement::Buffered() const {
6602 media::TimeIntervals buffered =
6603 mDecoder ? mDecoder->GetBuffered() : media::TimeIntervals();
6604 RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()), buffered);
6605 return ranges.forget();
6608 void HTMLMediaElement::SetRequestHeaders(nsIHttpChannel* aChannel) {
6609 // Send Accept header for video and audio types only (Bug 489071)
6610 SetAcceptHeader(aChannel);
6612 // Apache doesn't send Content-Length when gzip transfer encoding is used,
6613 // which prevents us from estimating the video length (if explicit
6614 // Content-Duration and a length spec in the container are not present either)
6615 // and from seeking. So, disable the standard "Accept-Encoding: gzip,deflate"
6616 // that we usually send. See bug 614760.
6617 DebugOnly<nsresult> rv =
6618 aChannel->SetRequestHeader("Accept-Encoding"_ns, ""_ns, false);
6619 MOZ_ASSERT(NS_SUCCEEDED(rv));
6621 // Set the Referrer header
6623 // FIXME: Shouldn't this use the Element constructor? Though I guess it
6624 // doesn't matter as no HTMLMediaElement supports the referrerinfo attribute.
6625 auto referrerInfo = MakeRefPtr<ReferrerInfo>(*OwnerDoc());
6626 rv = aChannel->SetReferrerInfoWithoutClone(referrerInfo);
6627 MOZ_ASSERT(NS_SUCCEEDED(rv));
6630 const TimeStamp& HTMLMediaElement::LastTimeupdateDispatchTime() const {
6631 MOZ_ASSERT(NS_IsMainThread());
6632 return mLastTimeUpdateDispatchTime;
6635 void HTMLMediaElement::UpdateLastTimeupdateDispatchTime() {
6636 MOZ_ASSERT(NS_IsMainThread());
6637 mLastTimeUpdateDispatchTime = TimeStamp::Now();
6640 bool HTMLMediaElement::ShouldQueueTimeupdateAsyncTask(
6641 TimeupdateType aType) const {
6642 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
6643 // That means dispatching `timeupdate` is mandatorily required in the spec.
6644 if (aType == TimeupdateType::eMandatory) {
6645 return true;
6648 // The timeupdate only occurs when the current playback position changes.
6649 // https://html.spec.whatwg.org/multipage/media.html#event-media-timeupdate
6650 if (mLastCurrentTime == CurrentTime()) {
6651 return false;
6654 // Number of milliseconds between timeupdate events as defined by spec.
6655 if (!mQueueTimeUpdateRunnerTime.IsNull() &&
6656 TimeStamp::Now() - mQueueTimeUpdateRunnerTime <
6657 TimeDuration::FromMilliseconds(TIMEUPDATE_MS)) {
6658 return false;
6660 return true;
6663 void HTMLMediaElement::FireTimeUpdate(TimeupdateType aType) {
6664 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
6666 if (ShouldQueueTimeupdateAsyncTask(aType)) {
6667 RefPtr<nsMediaEventRunner> runner =
6668 GetEventRunner(u"timeupdate"_ns, aType == TimeupdateType::eMandatory
6669 ? EventFlag::eMandatory
6670 : EventFlag::eNone);
6671 DispatchAsyncEvent(std::move(runner));
6672 mQueueTimeUpdateRunnerTime = TimeStamp::Now();
6673 mLastCurrentTime = CurrentTime();
6675 if (mFragmentEnd >= 0.0 && CurrentTime() >= mFragmentEnd) {
6676 Pause();
6677 mFragmentEnd = -1.0;
6678 mFragmentStart = -1.0;
6679 mDecoder->SetFragmentEndTime(mFragmentEnd);
6682 // Update the cues displaying on the video.
6683 // Here mTextTrackManager can be null if the cycle collector has unlinked
6684 // us before our parent. In that case UnbindFromTree will call us
6685 // when our parent is unlinked.
6686 if (mTextTrackManager) {
6687 mTextTrackManager->TimeMarchesOn();
6691 MediaError* HTMLMediaElement::GetError() const { return mErrorSink->mError; }
6693 void HTMLMediaElement::GetCurrentSpec(nsCString& aString) {
6694 // If playing a regular URL, an ObjectURL of a Blob/File, return that.
6695 if (mLoadingSrc) {
6696 mLoadingSrc->GetSpec(aString);
6697 } else if (mSrcMediaSource) {
6698 // If playing an ObjectURL, and it's a MediaSource, return the value of the
6699 // `src` attribute.
6700 nsAutoString src;
6701 GetSrc(src);
6702 CopyUTF16toUTF8(src, aString);
6703 } else {
6704 // Playing e.g. a MediaStream via an object URL - return an empty string
6705 aString.Truncate();
6709 double HTMLMediaElement::MozFragmentEnd() {
6710 double duration = Duration();
6712 // If there is no end fragment, or the fragment end is greater than the
6713 // duration, return the duration.
6714 return (mFragmentEnd < 0.0 || mFragmentEnd > duration) ? duration
6715 : mFragmentEnd;
6718 void HTMLMediaElement::SetDefaultPlaybackRate(double aDefaultPlaybackRate,
6719 ErrorResult& aRv) {
6720 if (mSrcAttrStream) {
6721 return;
6724 if (aDefaultPlaybackRate < 0) {
6725 aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
6726 return;
6729 double defaultPlaybackRate = ClampPlaybackRate(aDefaultPlaybackRate);
6731 if (mDefaultPlaybackRate == defaultPlaybackRate) {
6732 return;
6735 mDefaultPlaybackRate = defaultPlaybackRate;
6736 DispatchAsyncEvent(u"ratechange"_ns);
6739 void HTMLMediaElement::SetPlaybackRate(double aPlaybackRate, ErrorResult& aRv) {
6740 if (mSrcAttrStream) {
6741 return;
6744 // Changing the playback rate of a media that has more than two channels is
6745 // not supported.
6746 if (aPlaybackRate < 0) {
6747 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
6748 return;
6751 if (mPlaybackRate == aPlaybackRate) {
6752 return;
6755 mPlaybackRate = aPlaybackRate;
6756 // Playback rate threshold above which audio is muted.
6757 uint32_t threshold = StaticPrefs::media_audio_playbackrate_muting_threshold();
6758 if (mPlaybackRate != 0.0 &&
6759 (mPlaybackRate > threshold || mPlaybackRate < 1. / threshold)) {
6760 SetMutedInternal(mMuted | MUTED_BY_INVALID_PLAYBACK_RATE);
6761 } else {
6762 SetMutedInternal(mMuted & ~MUTED_BY_INVALID_PLAYBACK_RATE);
6765 if (mDecoder) {
6766 mDecoder->SetPlaybackRate(ClampPlaybackRate(mPlaybackRate));
6768 DispatchAsyncEvent(u"ratechange"_ns);
6771 void HTMLMediaElement::SetPreservesPitch(bool aPreservesPitch) {
6772 mPreservesPitch = aPreservesPitch;
6773 if (mDecoder) {
6774 mDecoder->SetPreservesPitch(mPreservesPitch);
6778 ImageContainer* HTMLMediaElement::GetImageContainer() {
6779 VideoFrameContainer* container = GetVideoFrameContainer();
6780 return container ? container->GetImageContainer() : nullptr;
6783 void HTMLMediaElement::UpdateAudioChannelPlayingState() {
6784 if (mAudioChannelWrapper) {
6785 mAudioChannelWrapper->UpdateAudioChannelPlayingState();
6789 static const char* VisibilityString(Visibility aVisibility) {
6790 switch (aVisibility) {
6791 case Visibility::Untracked: {
6792 return "Untracked";
6794 case Visibility::ApproximatelyNonVisible: {
6795 return "ApproximatelyNonVisible";
6797 case Visibility::ApproximatelyVisible: {
6798 return "ApproximatelyVisible";
6802 return "NAN";
6805 void HTMLMediaElement::OnVisibilityChange(Visibility aNewVisibility) {
6806 LOG(LogLevel::Debug,
6807 ("OnVisibilityChange(): %s\n", VisibilityString(aNewVisibility)));
6809 mVisibilityState = aNewVisibility;
6810 if (StaticPrefs::media_test_video_suspend()) {
6811 DispatchAsyncEvent(u"visibilitychanged"_ns);
6814 if (!mDecoder) {
6815 return;
6817 NotifyDecoderActivityChanges();
6820 MediaKeys* HTMLMediaElement::GetMediaKeys() const { return mMediaKeys; }
6822 bool HTMLMediaElement::ContainsRestrictedContent() const {
6823 return GetMediaKeys() != nullptr;
6826 void HTMLMediaElement::SetCDMProxyFailure(const MediaResult& aResult) {
6827 LOG(LogLevel::Debug, ("%s", __func__));
6828 MOZ_ASSERT(mSetMediaKeysDOMPromise);
6830 ResetSetMediaKeysTempVariables();
6832 mSetMediaKeysDOMPromise->MaybeReject(aResult.Code(), aResult.Message());
6835 void HTMLMediaElement::RemoveMediaKeys() {
6836 LOG(LogLevel::Debug, ("%s", __func__));
6837 // 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
6838 // to decrypt media data and remove the association with the media element.
6839 if (mMediaKeys) {
6840 mMediaKeys->Unbind();
6842 mMediaKeys = nullptr;
6845 bool HTMLMediaElement::TryRemoveMediaKeysAssociation() {
6846 MOZ_ASSERT(mMediaKeys);
6847 LOG(LogLevel::Debug, ("%s", __func__));
6848 // 5.2.1 If the user agent or CDM do not support removing the association,
6849 // let this object's attaching media keys value be false and reject promise
6850 // with a new DOMException whose name is NotSupportedError.
6851 // 5.2.2 If the association cannot currently be removed, let this object's
6852 // attaching media keys value be false and reject promise with a new
6853 // DOMException whose name is InvalidStateError.
6854 if (mDecoder) {
6855 RefPtr<HTMLMediaElement> self = this;
6856 mDecoder->SetCDMProxy(nullptr)
6857 ->Then(
6858 mAbstractMainThread, __func__,
6859 [self]() {
6860 self->mSetCDMRequest.Complete();
6862 self->RemoveMediaKeys();
6863 if (self->AttachNewMediaKeys()) {
6864 // No incoming MediaKeys object or MediaDecoder is not
6865 // created yet.
6866 self->MakeAssociationWithCDMResolved();
6869 [self](const MediaResult& aResult) {
6870 self->mSetCDMRequest.Complete();
6871 // 5.2.4 If the preceding step failed, let this object's
6872 // attaching media keys value be false and reject promise with
6873 // a new DOMException whose name is the appropriate error name.
6874 self->SetCDMProxyFailure(aResult);
6876 ->Track(mSetCDMRequest);
6877 return false;
6880 RemoveMediaKeys();
6881 return true;
6884 bool HTMLMediaElement::DetachExistingMediaKeys() {
6885 LOG(LogLevel::Debug, ("%s", __func__));
6886 MOZ_ASSERT(mSetMediaKeysDOMPromise);
6887 // 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
6888 // already in use by another media element, and the user agent is unable
6889 // to use it with this element, let this object's attaching media keys
6890 // value be false and reject promise with a new DOMException whose name
6891 // is QuotaExceededError.
6892 if (mIncomingMediaKeys && mIncomingMediaKeys->IsBoundToMediaElement()) {
6893 SetCDMProxyFailure(MediaResult(
6894 NS_ERROR_DOM_QUOTA_EXCEEDED_ERR,
6895 "MediaKeys object is already bound to another HTMLMediaElement"));
6896 return false;
6899 // 5.2 If the mediaKeys attribute is not null, run the following steps:
6900 if (mMediaKeys) {
6901 return TryRemoveMediaKeysAssociation();
6903 return true;
6906 void HTMLMediaElement::MakeAssociationWithCDMResolved() {
6907 LOG(LogLevel::Debug, ("%s", __func__));
6908 MOZ_ASSERT(mSetMediaKeysDOMPromise);
6910 // 5.4 Set the mediaKeys attribute to mediaKeys.
6911 mMediaKeys = mIncomingMediaKeys;
6912 // 5.5 Let this object's attaching media keys value be false.
6913 ResetSetMediaKeysTempVariables();
6914 // 5.6 Resolve promise.
6915 mSetMediaKeysDOMPromise->MaybeResolveWithUndefined();
6916 mSetMediaKeysDOMPromise = nullptr;
6919 bool HTMLMediaElement::TryMakeAssociationWithCDM(CDMProxy* aProxy) {
6920 LOG(LogLevel::Debug, ("%s", __func__));
6921 MOZ_ASSERT(aProxy);
6923 // 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
6924 // algorithm on the media element.
6925 // Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
6926 if (mDecoder) {
6927 // CDMProxy is set asynchronously in MediaFormatReader, once it's done,
6928 // HTMLMediaElement should resolve or reject the DOM promise.
6929 RefPtr<HTMLMediaElement> self = this;
6930 mDecoder->SetCDMProxy(aProxy)
6931 ->Then(
6932 mAbstractMainThread, __func__,
6933 [self]() {
6934 self->mSetCDMRequest.Complete();
6935 self->MakeAssociationWithCDMResolved();
6937 [self](const MediaResult& aResult) {
6938 self->mSetCDMRequest.Complete();
6939 self->SetCDMProxyFailure(aResult);
6941 ->Track(mSetCDMRequest);
6942 return false;
6944 return true;
6947 bool HTMLMediaElement::AttachNewMediaKeys() {
6948 LOG(LogLevel::Debug,
6949 ("%s incoming MediaKeys(%p)", __func__, mIncomingMediaKeys.get()));
6950 MOZ_ASSERT(mSetMediaKeysDOMPromise);
6952 // 5.3. If mediaKeys is not null, run the following steps:
6953 if (mIncomingMediaKeys) {
6954 auto* cdmProxy = mIncomingMediaKeys->GetCDMProxy();
6955 if (!cdmProxy) {
6956 SetCDMProxyFailure(MediaResult(
6957 NS_ERROR_DOM_INVALID_STATE_ERR,
6958 "CDM crashed before binding MediaKeys object to HTMLMediaElement"));
6959 return false;
6962 // 5.3.1 Associate the CDM instance represented by mediaKeys with the
6963 // media element for decrypting media data.
6964 if (NS_FAILED(mIncomingMediaKeys->Bind(this))) {
6965 // 5.3.2 If the preceding step failed, run the following steps:
6967 // 5.3.2.1 Set the mediaKeys attribute to null.
6968 mMediaKeys = nullptr;
6969 // 5.3.2.2 Let this object's attaching media keys value be false.
6970 // 5.3.2.3 Reject promise with a new DOMException whose name is
6971 // the appropriate error name.
6972 SetCDMProxyFailure(
6973 MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
6974 "Failed to bind MediaKeys object to HTMLMediaElement"));
6975 return false;
6977 return TryMakeAssociationWithCDM(cdmProxy);
6979 return true;
6982 void HTMLMediaElement::ResetSetMediaKeysTempVariables() {
6983 mAttachingMediaKey = false;
6984 mIncomingMediaKeys = nullptr;
6987 already_AddRefed<Promise> HTMLMediaElement::SetMediaKeys(
6988 mozilla::dom::MediaKeys* aMediaKeys, ErrorResult& aRv) {
6989 LOG(LogLevel::Debug, ("%p SetMediaKeys(%p) mMediaKeys=%p mDecoder=%p", this,
6990 aMediaKeys, mMediaKeys.get(), mDecoder.get()));
6992 if (MozAudioCaptured()) {
6993 aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
6994 return nullptr;
6997 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
6998 if (!win) {
6999 aRv.Throw(NS_ERROR_UNEXPECTED);
7000 return nullptr;
7002 RefPtr<DetailedPromise> promise = DetailedPromise::Create(
7003 win->AsGlobal(), aRv, "HTMLMediaElement.setMediaKeys"_ns);
7004 if (aRv.Failed()) {
7005 return nullptr;
7008 // 1. If mediaKeys and the mediaKeys attribute are the same object,
7009 // return a resolved promise.
7010 if (mMediaKeys == aMediaKeys) {
7011 promise->MaybeResolveWithUndefined();
7012 return promise.forget();
7015 // 2. If this object's attaching media keys value is true, return a
7016 // promise rejected with a new DOMException whose name is InvalidStateError.
7017 if (mAttachingMediaKey) {
7018 promise->MaybeRejectWithInvalidStateError(
7019 "A MediaKeys object is in attaching operation.");
7020 return promise.forget();
7023 // 3. Let this object's attaching media keys value be true.
7024 mAttachingMediaKey = true;
7025 mIncomingMediaKeys = aMediaKeys;
7027 // 4. Let promise be a new promise.
7028 mSetMediaKeysDOMPromise = promise;
7030 // 5. Run the following steps in parallel:
7032 // 5.1 & 5.2 & 5.3
7033 if (!DetachExistingMediaKeys() || !AttachNewMediaKeys()) {
7034 return promise.forget();
7037 // 5.4, 5.5, 5.6
7038 MakeAssociationWithCDMResolved();
7040 // 6. Return promise.
7041 return promise.forget();
7044 EventHandlerNonNull* HTMLMediaElement::GetOnencrypted() {
7045 return EventTarget::GetEventHandler(nsGkAtoms::onencrypted);
7048 void HTMLMediaElement::SetOnencrypted(EventHandlerNonNull* aCallback) {
7049 EventTarget::SetEventHandler(nsGkAtoms::onencrypted, aCallback);
7052 EventHandlerNonNull* HTMLMediaElement::GetOnwaitingforkey() {
7053 return EventTarget::GetEventHandler(nsGkAtoms::onwaitingforkey);
7056 void HTMLMediaElement::SetOnwaitingforkey(EventHandlerNonNull* aCallback) {
7057 EventTarget::SetEventHandler(nsGkAtoms::onwaitingforkey, aCallback);
7060 void HTMLMediaElement::DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
7061 const nsAString& aInitDataType) {
7062 LOG(LogLevel::Debug, ("%p DispatchEncrypted initDataType='%s'", this,
7063 NS_ConvertUTF16toUTF8(aInitDataType).get()));
7065 if (mReadyState == HAVE_NOTHING) {
7066 // Ready state not HAVE_METADATA (yet), don't dispatch encrypted now.
7067 // Queueing for later dispatch in MetadataLoaded.
7068 mPendingEncryptedInitData.AddInitData(aInitDataType, aInitData);
7069 return;
7072 RefPtr<MediaEncryptedEvent> event;
7073 if (IsCORSSameOrigin()) {
7074 event = MediaEncryptedEvent::Constructor(this, aInitDataType, aInitData);
7075 } else {
7076 event = MediaEncryptedEvent::Constructor(this);
7079 RefPtr<AsyncEventDispatcher> asyncDispatcher =
7080 new AsyncEventDispatcher(this, event);
7081 asyncDispatcher->PostDOMEvent();
7084 bool HTMLMediaElement::IsEventAttributeNameInternal(nsAtom* aName) {
7085 return aName == nsGkAtoms::onencrypted ||
7086 nsGenericHTMLElement::IsEventAttributeNameInternal(aName);
7089 void HTMLMediaElement::NotifyWaitingForKey() {
7090 LOG(LogLevel::Debug, ("%p, NotifyWaitingForKey()", this));
7092 // http://w3c.github.io/encrypted-media/#wait-for-key
7093 // 7.3.4 Queue a "waitingforkey" Event
7094 // 1. Let the media element be the specified HTMLMediaElement object.
7095 // 2. If the media element's waiting for key value is true, abort these steps.
7096 if (mWaitingForKey == NOT_WAITING_FOR_KEY) {
7097 // 3. Set the media element's waiting for key value to true.
7098 // Note: algorithm continues in UpdateReadyStateInternal() when all decoded
7099 // data enqueued in the MDSM is consumed.
7100 mWaitingForKey = WAITING_FOR_KEY;
7101 // mWaitingForKey changed outside of UpdateReadyStateInternal. This may
7102 // affect mReadyState.
7103 mWatchManager.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal);
7107 AudioTrackList* HTMLMediaElement::AudioTracks() { return mAudioTrackList; }
7109 VideoTrackList* HTMLMediaElement::VideoTracks() { return mVideoTrackList; }
7111 TextTrackList* HTMLMediaElement::GetTextTracks() {
7112 return GetOrCreateTextTrackManager()->GetTextTracks();
7115 already_AddRefed<TextTrack> HTMLMediaElement::AddTextTrack(
7116 TextTrackKind aKind, const nsAString& aLabel, const nsAString& aLanguage) {
7117 return GetOrCreateTextTrackManager()->AddTextTrack(
7118 aKind, aLabel, aLanguage, TextTrackMode::Hidden,
7119 TextTrackReadyState::Loaded, TextTrackSource::AddTextTrack);
7122 void HTMLMediaElement::PopulatePendingTextTrackList() {
7123 if (mTextTrackManager) {
7124 mTextTrackManager->PopulatePendingList();
7128 TextTrackManager* HTMLMediaElement::GetOrCreateTextTrackManager() {
7129 if (!mTextTrackManager) {
7130 mTextTrackManager = new TextTrackManager(this);
7131 mTextTrackManager->AddListeners();
7133 return mTextTrackManager;
7136 MediaDecoderOwner::NextFrameStatus HTMLMediaElement::NextFrameStatus() {
7137 if (mDecoder) {
7138 return mDecoder->NextFrameStatus();
7140 if (mSrcStream) {
7141 AutoTArray<RefPtr<MediaTrack>, 4> tracks;
7142 GetAllEnabledMediaTracks(tracks);
7143 if (!tracks.IsEmpty() && !mSrcStreamPlaybackEnded) {
7144 return NEXT_FRAME_AVAILABLE;
7146 return NEXT_FRAME_UNAVAILABLE;
7148 return NEXT_FRAME_UNINITIALIZED;
7151 void HTMLMediaElement::SetDecoder(MediaDecoder* aDecoder) {
7152 MOZ_ASSERT(aDecoder); // Use ShutdownDecoder() to clear.
7153 if (mDecoder) {
7154 ShutdownDecoder();
7156 mDecoder = aDecoder;
7157 DDLINKCHILD("decoder", mDecoder.get());
7158 if (mDecoder && mForcedHidden) {
7159 mDecoder->SetForcedHidden(mForcedHidden);
7163 float HTMLMediaElement::ComputedVolume() const {
7164 return mMuted ? 0.0f
7165 : mAudioChannelWrapper ? mAudioChannelWrapper->GetEffectiveVolume()
7166 : static_cast<float>(mVolume);
7169 bool HTMLMediaElement::ComputedMuted() const {
7170 return (mMuted & MUTED_BY_AUDIO_CHANNEL);
7173 bool HTMLMediaElement::IsSuspendedByInactiveDocOrDocShell() const {
7174 return mSuspendedByInactiveDocOrDocshell;
7177 bool HTMLMediaElement::IsCurrentlyPlaying() const {
7178 // We have playable data, but we still need to check whether data is "real"
7179 // current data.
7180 return mReadyState >= HAVE_CURRENT_DATA && !IsPlaybackEnded();
7183 void HTMLMediaElement::SetAudibleState(bool aAudible) {
7184 if (mIsAudioTrackAudible != aAudible) {
7185 mIsAudioTrackAudible = aAudible;
7186 NotifyAudioPlaybackChanged(
7187 AudioChannelService::AudibleChangedReasons::eDataAudibleChanged);
7191 void HTMLMediaElement::NotifyAudioPlaybackChanged(
7192 AudibleChangedReasons aReason) {
7193 if (mAudioChannelWrapper) {
7194 mAudioChannelWrapper->NotifyAudioPlaybackChanged(aReason);
7196 // We would start the listener after media becomes audible.
7197 const bool isAudible = IsAudible();
7198 if (isAudible && !mMediaControlKeyListener->IsStarted()) {
7199 StartMediaControlKeyListenerIfNeeded();
7201 mMediaControlKeyListener->UpdateMediaAudibleState(isAudible);
7202 // only request wake lock for audible media.
7203 UpdateWakeLock();
7206 void HTMLMediaElement::SetMediaInfo(const MediaInfo& aInfo) {
7207 const bool oldHasAudio = mMediaInfo.HasAudio();
7208 mMediaInfo = aInfo;
7209 if ((aInfo.HasAudio() != oldHasAudio) && mResumeDelayedPlaybackAgent) {
7210 mResumeDelayedPlaybackAgent->UpdateAudibleState(this, IsAudible());
7212 nsILoadContext* loadContext = OwnerDoc()->GetLoadContext();
7213 if (HasAudio() && loadContext && !loadContext->UsePrivateBrowsing()) {
7214 mTitleChangeObserver->Subscribe();
7215 UpdateStreamName();
7216 } else {
7217 mTitleChangeObserver->Unsubscribe();
7219 if (mAudioChannelWrapper) {
7220 mAudioChannelWrapper->AudioCaptureTrackChangeIfNeeded();
7222 UpdateWakeLock();
7225 MediaInfo HTMLMediaElement::GetMediaInfo() const { return mMediaInfo; }
7227 FrameStatistics* HTMLMediaElement::GetFrameStatistics() const {
7228 return mDecoder ? &(mDecoder->GetFrameStatistics()) : nullptr;
7231 void HTMLMediaElement::DispatchAsyncTestingEvent(const nsAString& aName) {
7232 if (!StaticPrefs::media_testing_only_events()) {
7233 return;
7235 DispatchAsyncEvent(aName);
7238 void HTMLMediaElement::AudioCaptureTrackChange(bool aCapture) {
7239 // No need to capture a silent media element.
7240 if (!HasAudio()) {
7241 return;
7244 if (aCapture && !mStreamWindowCapturer) {
7245 nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
7246 if (!window) {
7247 return;
7250 MediaTrackGraph* mtg = MediaTrackGraph::GetInstance(
7251 MediaTrackGraph::AUDIO_THREAD_DRIVER, window,
7252 MediaTrackGraph::REQUEST_DEFAULT_SAMPLE_RATE,
7253 MediaTrackGraph::DEFAULT_OUTPUT_DEVICE);
7254 RefPtr<DOMMediaStream> stream =
7255 CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED,
7256 StreamCaptureType::CAPTURE_AUDIO, mtg);
7257 mStreamWindowCapturer =
7258 MakeUnique<MediaStreamWindowCapturer>(stream, window->WindowID());
7259 } else if (!aCapture && mStreamWindowCapturer) {
7260 for (size_t i = 0; i < mOutputStreams.Length(); i++) {
7261 if (mOutputStreams[i].mStream == mStreamWindowCapturer->mStream) {
7262 // We own this MediaStream, it is not exposed to JS.
7263 AutoTArray<RefPtr<MediaStreamTrack>, 2> tracks;
7264 mStreamWindowCapturer->mStream->GetTracks(tracks);
7265 for (auto& track : tracks) {
7266 track->Stop();
7268 mOutputStreams.RemoveElementAt(i);
7269 break;
7272 mStreamWindowCapturer = nullptr;
7273 if (mOutputStreams.IsEmpty()) {
7274 mTracksCaptured = nullptr;
7279 void HTMLMediaElement::NotifyCueDisplayStatesChanged() {
7280 if (!mTextTrackManager) {
7281 return;
7284 mTextTrackManager->DispatchUpdateCueDisplay();
7287 void HTMLMediaElement::LogVisibility(CallerAPI aAPI) {
7288 const bool isVisible = mVisibilityState == Visibility::ApproximatelyVisible;
7290 LOG(LogLevel::Debug, ("%p visibility = %u, API: '%d' and 'All'", this,
7291 isVisible, static_cast<int>(aAPI)));
7293 if (!isVisible) {
7294 LOG(LogLevel::Debug, ("%p inTree = %u, API: '%d' and 'All'", this,
7295 IsInComposedDoc(), static_cast<int>(aAPI)));
7299 void HTMLMediaElement::UpdateCustomPolicyAfterPlayed() {
7300 if (mAudioChannelWrapper) {
7301 mAudioChannelWrapper->NotifyPlayStateChanged();
7305 AbstractThread* HTMLMediaElement::AbstractMainThread() const {
7306 MOZ_ASSERT(mAbstractMainThread);
7308 return mAbstractMainThread;
7311 nsTArray<RefPtr<PlayPromise>> HTMLMediaElement::TakePendingPlayPromises() {
7312 return std::move(mPendingPlayPromises);
7315 void HTMLMediaElement::NotifyAboutPlaying() {
7316 // Stick to the DispatchAsyncEvent() call path for now because we want to
7317 // trigger some telemetry-related codes in the DispatchAsyncEvent() method.
7318 DispatchAsyncEvent(u"playing"_ns);
7321 already_AddRefed<PlayPromise> HTMLMediaElement::CreatePlayPromise(
7322 ErrorResult& aRv) const {
7323 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
7325 if (!win) {
7326 aRv.Throw(NS_ERROR_UNEXPECTED);
7327 return nullptr;
7330 RefPtr<PlayPromise> promise = PlayPromise::Create(win->AsGlobal(), aRv);
7331 LOG(LogLevel::Debug, ("%p created PlayPromise %p", this, promise.get()));
7333 return promise.forget();
7336 already_AddRefed<Promise> HTMLMediaElement::CreateDOMPromise(
7337 ErrorResult& aRv) const {
7338 nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
7340 if (!win) {
7341 aRv.Throw(NS_ERROR_UNEXPECTED);
7342 return nullptr;
7345 return Promise::Create(win->AsGlobal(), aRv);
7348 void HTMLMediaElement::AsyncResolvePendingPlayPromises() {
7349 if (mShuttingDown) {
7350 return;
7353 nsCOMPtr<nsIRunnable> event = new nsResolveOrRejectPendingPlayPromisesRunner(
7354 this, TakePendingPlayPromises());
7356 mMainThreadEventTarget->Dispatch(event.forget());
7359 void HTMLMediaElement::AsyncRejectPendingPlayPromises(nsresult aError) {
7360 if (!mPaused) {
7361 mPaused = true;
7362 DispatchAsyncEvent(u"pause"_ns);
7365 if (mShuttingDown) {
7366 return;
7369 if (aError == NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR) {
7370 DispatchEventsWhenPlayWasNotAllowed();
7373 nsCOMPtr<nsIRunnable> event = new nsResolveOrRejectPendingPlayPromisesRunner(
7374 this, TakePendingPlayPromises(), aError);
7376 mMainThreadEventTarget->Dispatch(event.forget());
7379 void HTMLMediaElement::GetEMEInfo(dom::EMEDebugInfo& aInfo) {
7380 MOZ_ASSERT(NS_IsMainThread(),
7381 "MediaKeys expects to be interacted with on main thread!");
7382 if (!mMediaKeys) {
7383 return;
7385 mMediaKeys->GetKeySystem(aInfo.mKeySystem);
7386 mMediaKeys->GetSessionsInfo(aInfo.mSessionsInfo);
7389 void HTMLMediaElement::NotifyDecoderActivityChanges() const {
7390 if (mDecoder) {
7391 mDecoder->NotifyOwnerActivityChanged(IsActuallyInvisible(),
7392 IsInComposedDoc());
7396 Document* HTMLMediaElement::GetDocument() const { return OwnerDoc(); }
7398 bool HTMLMediaElement::IsAudible() const {
7399 // No audio track.
7400 if (!HasAudio()) {
7401 return false;
7404 // Muted or the volume should not be ~0
7405 if (mMuted || (std::fabs(Volume()) <= 1e-7)) {
7406 return false;
7409 return mIsAudioTrackAudible;
7412 Maybe<nsAutoString> HTMLMediaElement::GetKeySystem() const {
7413 if (!mMediaKeys) {
7414 return Nothing();
7416 nsAutoString keySystem;
7417 mMediaKeys->GetKeySystem(keySystem);
7418 return Some(keySystem);
7421 void HTMLMediaElement::ConstructMediaTracks(const MediaInfo* aInfo) {
7422 if (!aInfo) {
7423 return;
7426 AudioTrackList* audioList = AudioTracks();
7427 if (audioList && aInfo->HasAudio()) {
7428 const TrackInfo& info = aInfo->mAudio;
7429 RefPtr<AudioTrack> track = MediaTrackList::CreateAudioTrack(
7430 audioList->GetOwnerGlobal(), info.mId, info.mKind, info.mLabel,
7431 info.mLanguage, info.mEnabled);
7433 audioList->AddTrack(track);
7436 VideoTrackList* videoList = VideoTracks();
7437 if (videoList && aInfo->HasVideo()) {
7438 const TrackInfo& info = aInfo->mVideo;
7439 RefPtr<VideoTrack> track = MediaTrackList::CreateVideoTrack(
7440 videoList->GetOwnerGlobal(), info.mId, info.mKind, info.mLabel,
7441 info.mLanguage);
7443 videoList->AddTrack(track);
7444 track->SetEnabledInternal(info.mEnabled, MediaTrack::FIRE_NO_EVENTS);
7448 void HTMLMediaElement::RemoveMediaTracks() {
7449 if (mAudioTrackList) {
7450 mAudioTrackList->RemoveTracks();
7452 if (mVideoTrackList) {
7453 mVideoTrackList->RemoveTracks();
7457 class MediaElementGMPCrashHelper : public GMPCrashHelper {
7458 public:
7459 explicit MediaElementGMPCrashHelper(HTMLMediaElement* aElement)
7460 : mElement(aElement) {
7461 MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
7463 already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() override {
7464 MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
7465 if (!mElement) {
7466 return nullptr;
7468 return do_AddRef(mElement->OwnerDoc()->GetInnerWindow());
7471 private:
7472 WeakPtr<HTMLMediaElement> mElement;
7475 already_AddRefed<GMPCrashHelper> HTMLMediaElement::CreateGMPCrashHelper() {
7476 return MakeAndAddRef<MediaElementGMPCrashHelper>(this);
7479 void HTMLMediaElement::MarkAsTainted() {
7480 mHasSuspendTaint = true;
7482 if (mDecoder) {
7483 mDecoder->SetSuspendTaint(true);
7487 bool HasDebuggerOrTabsPrivilege(JSContext* aCx, JSObject* aObj) {
7488 return nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::debugger) ||
7489 nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::tabs);
7492 already_AddRefed<Promise> HTMLMediaElement::SetSinkId(const nsAString& aSinkId,
7493 ErrorResult& aRv) {
7494 nsCOMPtr<nsPIDOMWindowInner> win = OwnerDoc()->GetInnerWindow();
7495 if (!win) {
7496 aRv.Throw(NS_ERROR_UNEXPECTED);
7497 return nullptr;
7500 RefPtr<Promise> promise = Promise::Create(win->AsGlobal(), aRv);
7501 if (aRv.Failed()) {
7502 return nullptr;
7505 if (!FeaturePolicyUtils::IsFeatureAllowed(win->GetExtantDoc(),
7506 u"speaker-selection"_ns)) {
7507 promise->MaybeRejectWithNotAllowedError(
7508 "Document's Permissions Policy does not allow setSinkId()");
7511 if (mSink.first.Equals(aSinkId)) {
7512 promise->MaybeResolveWithUndefined();
7513 return promise.forget();
7516 RefPtr<MediaDevices> mediaDevices = win->Navigator()->GetMediaDevices(aRv);
7517 if (aRv.Failed()) {
7518 return nullptr;
7521 nsString sinkId(aSinkId);
7522 mediaDevices->GetSinkDevice(sinkId)
7523 ->Then(
7524 mAbstractMainThread, __func__,
7525 [self = RefPtr<HTMLMediaElement>(this),
7526 this](RefPtr<AudioDeviceInfo>&& aInfo) {
7527 // Sink found switch output device.
7528 MOZ_ASSERT(aInfo);
7529 if (mDecoder) {
7530 RefPtr<SinkInfoPromise> p = mDecoder->SetSink(aInfo)->Then(
7531 mAbstractMainThread, __func__,
7532 [aInfo](const GenericPromise::ResolveOrRejectValue& aValue) {
7533 if (aValue.IsResolve()) {
7534 return SinkInfoPromise::CreateAndResolve(aInfo, __func__);
7536 return SinkInfoPromise::CreateAndReject(
7537 aValue.RejectValue(), __func__);
7539 return p;
7541 if (mSrcStream) {
7542 MOZ_ASSERT(mMediaStreamRenderer);
7543 RefPtr<SinkInfoPromise> p =
7544 mMediaStreamRenderer->SetAudioOutputDevice(aInfo)->Then(
7545 mAbstractMainThread, __func__,
7546 [aInfo](const GenericPromise::AllPromiseType::
7547 ResolveOrRejectValue& aValue) {
7548 if (aValue.IsResolve()) {
7549 return SinkInfoPromise::CreateAndResolve(aInfo,
7550 __func__);
7552 return SinkInfoPromise::CreateAndReject(
7553 aValue.RejectValue(), __func__);
7555 return p;
7557 // No media attached to the element save it for later.
7558 return SinkInfoPromise::CreateAndResolve(aInfo, __func__);
7560 [](nsresult res) {
7561 // Promise is rejected, sink not found.
7562 return SinkInfoPromise::CreateAndReject(res, __func__);
7564 ->Then(mAbstractMainThread, __func__,
7565 [promise, self = RefPtr<HTMLMediaElement>(this), this,
7566 sinkId](const SinkInfoPromise::ResolveOrRejectValue& aValue) {
7567 if (aValue.IsResolve()) {
7568 mSink = std::pair(sinkId, aValue.ResolveValue());
7569 promise->MaybeResolveWithUndefined();
7570 } else {
7571 switch (aValue.RejectValue()) {
7572 case NS_ERROR_ABORT:
7573 promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
7574 break;
7575 case NS_ERROR_NOT_AVAILABLE: {
7576 promise->MaybeRejectWithNotFoundError(
7577 "The object can not be found here.");
7578 break;
7580 default:
7581 MOZ_ASSERT_UNREACHABLE("Invalid error.");
7586 aRv = NS_OK;
7587 return promise.forget();
7590 void HTMLMediaElement::NotifyTextTrackModeChanged() {
7591 if (mPendingTextTrackChanged) {
7592 return;
7594 mPendingTextTrackChanged = true;
7595 mAbstractMainThread->Dispatch(
7596 NS_NewRunnableFunction("HTMLMediaElement::NotifyTextTrackModeChanged",
7597 [this, self = RefPtr<HTMLMediaElement>(this)]() {
7598 mPendingTextTrackChanged = false;
7599 if (!mTextTrackManager) {
7600 return;
7602 GetTextTracks()->CreateAndDispatchChangeEvent();
7603 // https://html.spec.whatwg.org/multipage/media.html#text-track-model:show-poster-flag
7604 if (!mShowPoster) {
7605 mTextTrackManager->TimeMarchesOn();
7607 }));
7610 void HTMLMediaElement::CreateResumeDelayedMediaPlaybackAgentIfNeeded() {
7611 if (mResumeDelayedPlaybackAgent) {
7612 return;
7614 mResumeDelayedPlaybackAgent =
7615 MediaPlaybackDelayPolicy::CreateResumeDelayedPlaybackAgent(this,
7616 IsAudible());
7617 if (!mResumeDelayedPlaybackAgent) {
7618 LOG(LogLevel::Debug,
7619 ("%p Failed to create a delayed playback agant", this));
7620 return;
7622 mResumeDelayedPlaybackAgent->GetResumePromise()
7623 ->Then(
7624 mAbstractMainThread, __func__,
7625 [self = RefPtr<HTMLMediaElement>(this)]() {
7626 LOG(LogLevel::Debug, ("%p Resume delayed Play() call", self.get()));
7627 self->mResumePlaybackRequest.Complete();
7628 self->mResumeDelayedPlaybackAgent = nullptr;
7629 IgnoredErrorResult dummy;
7630 RefPtr<Promise> toBeIgnored = self->Play(dummy);
7632 [self = RefPtr<HTMLMediaElement>(this)]() {
7633 LOG(LogLevel::Debug,
7634 ("%p Can not resume delayed Play() call", self.get()));
7635 self->mResumePlaybackRequest.Complete();
7636 self->mResumeDelayedPlaybackAgent = nullptr;
7638 ->Track(mResumePlaybackRequest);
7641 void HTMLMediaElement::ClearResumeDelayedMediaPlaybackAgentIfNeeded() {
7642 if (mResumeDelayedPlaybackAgent) {
7643 mResumePlaybackRequest.DisconnectIfExists();
7644 mResumeDelayedPlaybackAgent = nullptr;
7648 void HTMLMediaElement::NotifyMediaControlPlaybackStateChanged() {
7649 if (!mMediaControlKeyListener->IsStarted()) {
7650 return;
7652 if (mPaused) {
7653 mMediaControlKeyListener->NotifyMediaStoppedPlaying();
7654 } else {
7655 mMediaControlKeyListener->NotifyMediaStartedPlaying();
7659 bool HTMLMediaElement::IsInFullScreen() const {
7660 return State().HasState(ElementState::FULLSCREEN);
7663 bool HTMLMediaElement::IsPlayable() const {
7664 return (mDecoder || mSrcStream) && !HasError();
7667 bool HTMLMediaElement::ShouldStartMediaControlKeyListener() const {
7668 if (!IsPlayable()) {
7669 MEDIACONTROL_LOG("Not start listener because media is not playable");
7670 return false;
7673 if (mSrcStream) {
7674 MEDIACONTROL_LOG("Not listening because media is real-time");
7675 return false;
7678 if (IsBeingUsedInPictureInPictureMode()) {
7679 MEDIACONTROL_LOG("Start listener because of being used in PiP mode");
7680 return true;
7683 if (IsInFullScreen()) {
7684 MEDIACONTROL_LOG("Start listener because of being used in fullscreen");
7685 return true;
7688 // In order to filter out notification-ish sound, we use this pref to set the
7689 // eligible media duration to prevent showing media control for those short
7690 // sound.
7691 if (Duration() <
7692 StaticPrefs::media_mediacontrol_eligible_media_duration_s()) {
7693 MEDIACONTROL_LOG("Not listening because media's duration %f is too short.",
7694 Duration());
7695 return false;
7698 // This includes cases such like `video is muted`, `video has zero volume`,
7699 // `video's audio track is still inaudible` and `tab is muted by audio channel
7700 // (tab sound indicator)`, all these cases would make media inaudible.
7701 // `ComputedVolume()` would return the final volume applied the affection made
7702 // by audio channel, which is used to detect if the tab is muted by audio
7703 // channel.
7704 if (!IsAudible() || ComputedVolume() == 0.0f) {
7705 MEDIACONTROL_LOG("Not listening because media is inaudible");
7706 return false;
7708 return true;
7711 void HTMLMediaElement::StartMediaControlKeyListenerIfNeeded() {
7712 if (!ShouldStartMediaControlKeyListener()) {
7713 return;
7715 mMediaControlKeyListener->Start();
7718 void HTMLMediaElement::UpdateStreamName() {
7719 MOZ_ASSERT(NS_IsMainThread());
7721 nsAutoString aTitle;
7722 OwnerDoc()->GetTitle(aTitle);
7724 if (mDecoder) {
7725 mDecoder->SetStreamName(aTitle);
7729 void HTMLMediaElement::SetSecondaryMediaStreamRenderer(
7730 VideoFrameContainer* aContainer,
7731 FirstFrameVideoOutput* aFirstFrameOutput /* = nullptr */) {
7732 MOZ_ASSERT(mSrcStream);
7733 MOZ_ASSERT(mMediaStreamRenderer);
7734 if (mSecondaryMediaStreamRenderer) {
7735 mSecondaryMediaStreamRenderer->Shutdown();
7736 mSecondaryMediaStreamRenderer = nullptr;
7738 if (aContainer) {
7739 mSecondaryMediaStreamRenderer = MakeAndAddRef<MediaStreamRenderer>(
7740 mAbstractMainThread, aContainer, aFirstFrameOutput, this);
7741 if (mSrcStreamIsPlaying) {
7742 mSecondaryMediaStreamRenderer->Start();
7744 if (mSelectedVideoStreamTrack) {
7745 mSecondaryMediaStreamRenderer->AddTrack(mSelectedVideoStreamTrack);
7750 void HTMLMediaElement::UpdateMediaControlAfterPictureInPictureModeChanged() {
7751 if (IsBeingUsedInPictureInPictureMode()) {
7752 // When media enters PIP mode, we should ensure that the listener has been
7753 // started because we always want to control PIP video.
7754 StartMediaControlKeyListenerIfNeeded();
7755 if (!mMediaControlKeyListener->IsStarted()) {
7756 MEDIACONTROL_LOG("Failed to start listener when entering PIP mode");
7758 // Updating controller PIP state no matter the listener starts or not.
7759 mMediaControlKeyListener->SetPictureInPictureModeEnabled(true);
7760 } else {
7761 mMediaControlKeyListener->SetPictureInPictureModeEnabled(false);
7765 bool HTMLMediaElement::IsBeingUsedInPictureInPictureMode() const {
7766 if (!IsVideo()) {
7767 return false;
7769 return static_cast<const HTMLVideoElement*>(this)->IsCloningElementVisually();
7772 void HTMLMediaElement::NodeInfoChanged(Document* aOldDoc) {
7773 if (mMediaSource) {
7774 OwnerDoc()->AddMediaElementWithMSE();
7775 aOldDoc->RemoveMediaElementWithMSE();
7778 nsGenericHTMLElement::NodeInfoChanged(aOldDoc);
7781 } // namespace mozilla::dom
7783 #undef LOG
7784 #undef LOG_EVENT