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/. */
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"
30 #include "HTMLMediaElement.h"
31 #include "ImageContainer.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"
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"
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"
129 #include "nsThreadUtils.h"
130 #include "nsURIHashKey.h"
131 #include "nsVideoFrame.h"
132 #include "ReferrerInfo.h"
133 #include "xpcpublic.h"
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
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
214 class HTMLMediaElement::EventBlocker final
: public nsISupports
{
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
) {
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.
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
);
251 MOZ_ASSERT(NS_IsMainThread());
252 for (auto& runner
: mPendingEventRunners
) {
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());
266 for (const auto& runner
: mPendingEventRunners
) {
267 total
+= aMallocSizeOf(runner
);
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
)
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
{
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.
328 MOZ_ASSERT(NS_IsMainThread());
330 // We have already been started, do not notify start twice.
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!");
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
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());
364 // We have already been stopped, do not notify stop twice.
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
) {
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
) {
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
) {
457 } else if (aKey
== MediaControlKey::Pause
) {
460 MOZ_ASSERT(aKey
== MediaControlKey::Stop
,
461 "Not supported key for media element!");
467 void UpdateOwnerBrowsingContextIfNeeded() {
468 // Has not notified any information about the owner context yet.
473 BrowsingContext
* currentBC
= GetCurrentBrowsingContext();
474 MOZ_ASSERT(currentBC
);
475 // Still in the same browsing context, no need to update.
476 if (currentBC
->Id() == mOwnerBrowsingContextId
) {
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
;
492 if (wasInPlayingState
) {
493 NotifyMediaStartedPlaying();
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.
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
) {
519 MOZ_ASSERT(currentBC
);
520 mOwnerBrowsingContextId
= currentBC
->Id();
521 MEDIACONTROL_LOG("Init agent in browsing context %" PRIu64
,
522 mOwnerBrowsingContextId
);
523 mControlAgent
->AddReceiver(this);
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!");
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
{
562 explicit MediaStreamTrackListener(HTMLMediaElement
* aElement
)
563 : mElement(aElement
) {}
565 void NotifyTrackAdded(const RefPtr
<MediaStreamTrack
>& aTrack
) override
{
569 mElement
->NotifyMediaStreamTrackAdded(aTrack
);
572 void NotifyTrackRemoved(const RefPtr
<MediaStreamTrack
>& aTrack
) override
{
576 mElement
->NotifyMediaStreamTrackRemoved(aTrack
);
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.
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()) {
604 if (!mElement
->Autoplay()) {
607 LOG(LogLevel::Info
, ("%p, mSrcStream %p became active on autoplaying, "
608 "ended element. Reloading.",
609 mElement
.get(), mElement
->mSrcStream
.get()));
613 void NotifyActive() override
{
618 if (!mElement
->IsVideo()) {
619 // Audio elements use NotifyAudible().
626 void NotifyAudible() override
{
631 if (mElement
->IsVideo()) {
632 // Video elements use NotifyActive().
640 MOZ_ASSERT(mElement
);
642 if (mElement
->IsPlaybackEnded()) {
645 LOG(LogLevel::Debug
, ("%p, mSrcStream %p became inactive", mElement
.get(),
646 mElement
->mSrcStream
.get()));
648 mElement
->PlaybackEnded();
651 void NotifyInactive() override
{
656 if (!mElement
->IsVideo()) {
657 // Audio elements use NotifyInaudible().
664 void NotifyInaudible() override
{
669 if (mElement
->IsVideo()) {
670 // Video elements use NotifyInactive().
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
686 class HTMLMediaElement::MediaStreamRenderer
687 : public DOMMediaStream::TrackListener
{
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
);
706 for (const auto& t
: mAudioTracks
.Clone()) {
708 RemoveTrack(t
->AsAudioStreamTrack());
712 RemoveTrack(mVideoTrack
->AsVideoStreamTrack());
714 mWatchManager
.Shutdown();
715 mFirstFrameVideoOutput
= nullptr;
718 void UpdateGraphTime() {
720 mGraphTimeDummy
->mTrack
->Graph()->CurrentTime() - *mGraphTimeOffset
;
723 void SetFirstFrameRendered() {
724 if (!mFirstFrameVideoOutput
) {
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
) {
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
);
749 mWatchManager
.Unwatch(graph
->CurrentTime(),
750 &MediaStreamRenderer::UpdateGraphTime
);
761 if (!mGraphTimeDummy
) {
765 for (const auto& t
: mAudioTracks
) {
767 t
->AsAudioStreamTrack()->AddAudioOutput(mAudioOutputKey
,
769 t
->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey
,
775 mVideoTrack
->AsVideoStreamTrack()->AddVideoOutput(mVideoContainer
);
786 if (!mGraphTimeDummy
) {
790 for (const auto& t
: mAudioTracks
) {
792 t
->AsAudioStreamTrack()->RemoveAudioOutput(mAudioOutputKey
);
797 mVideoTrack
->AsVideoStreamTrack()->RemoveVideoOutput(mVideoContainer
);
801 void SetAudioOutputVolume(float aVolume
) {
802 if (mAudioOutputVolume
== aVolume
) {
805 mAudioOutputVolume
= aVolume
;
809 for (const auto& t
: mAudioTracks
) {
811 t
->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey
,
817 RefPtr
<GenericPromise::AllPromiseType
> SetAudioOutputDevice(
818 AudioDeviceInfo
* aSink
) {
820 MOZ_ASSERT(mAudioOutputSink
!= aSink
);
822 mAudioOutputSink
= aSink
;
825 return GenericPromise::AllPromiseType::CreateAndResolve(nsTArray
<bool>(),
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
,
837 if (!promises
.Length()) {
838 // Not active track, save it for later
839 return GenericPromise::AllPromiseType::CreateAndResolve(nsTArray
<bool>(),
843 return GenericPromise::All(GetCurrentSerialEventTarget(), promises
);
846 void AddTrack(AudioStreamTrack
* aTrack
) {
847 MOZ_DIAGNOSTIC_ASSERT(!mAudioTracks
.Contains(aTrack
));
848 mAudioTracks
.AppendElement(aTrack
);
849 EnsureGraphTimeDummy();
851 aTrack
->AddAudioOutput(mAudioOutputKey
, mAudioOutputSink
);
852 aTrack
->SetAudioOutputVolume(mAudioOutputKey
, mAudioOutputVolume
);
855 void AddTrack(VideoStreamTrack
* aTrack
) {
856 MOZ_DIAGNOSTIC_ASSERT(!mVideoTrack
);
857 if (!mVideoContainer
) {
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
);
869 aTrack
->AddVideoOutput(mVideoContainer
);
873 void RemoveTrack(AudioStreamTrack
* aTrack
) {
874 MOZ_DIAGNOSTIC_ASSERT(mAudioTracks
.Contains(aTrack
));
876 aTrack
->RemoveAudioOutput(mAudioOutputKey
);
878 mAudioTracks
.RemoveElement(aTrack
);
880 void RemoveTrack(VideoStreamTrack
* aTrack
) {
881 MOZ_DIAGNOSTIC_ASSERT(mVideoTrack
== aTrack
);
882 if (!mVideoContainer
) {
885 if (mFirstFrameVideoOutput
) {
886 aTrack
->RemoveVideoOutput(mFirstFrameVideoOutput
);
889 aTrack
->RemoveVideoOutput(mVideoContainer
);
891 mVideoTrack
= nullptr;
894 double CurrentTime() const {
895 if (!mGraphTimeDummy
) {
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
;
911 ~MediaStreamRenderer() { Shutdown(); }
913 void EnsureGraphTimeDummy() {
914 if (mGraphTimeDummy
) {
918 MediaTrackGraph
* graph
= nullptr;
919 for (const auto& t
: mAudioTracks
) {
920 if (t
&& !t
->Ended()) {
926 if (!graph
&& mVideoTrack
&& !mVideoTrack
->Ended()) {
927 graph
= mVideoTrack
->Graph();
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
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
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
{
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
),
997 mIntendedElementMuteState(aMuteState
),
998 mElementMuteState(aMuteState
),
999 mMediaDecoderHasAlpha(Some(aHasAlpha
)) {
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(),
1011 mMainThreadEventTarget(aMainThreadEventTarget
),
1012 mCapturedTrack(aCapturedTrack
),
1013 mCapturedTrackSource(aCapturedTrackSource
),
1016 mIntendedElementMuteState(aMuteState
),
1017 mElementMuteState(aMuteState
) {
1019 MOZ_ASSERT(mCapturedTrack
);
1020 MOZ_ASSERT(mCapturedTrackSource
);
1023 mCapturedTrack
->AddConsumer(this);
1024 mCapturedTrackSource
->RegisterSink(this);
1027 void SetEnabled(bool aEnabled
) {
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
) {
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());
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()) {
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.
1102 SetPrincipal(mCapturedTrackSource
->GetPrincipal());
1105 void MutedChanged(bool aNewState
) override
{
1106 MediaStreamTrackSource::MutedChanged(Muted());
1109 void OverrideEnded() override
{
1111 MediaStreamTrackSource::OverrideEnded();
1114 void NotifyEnabledChanged(MediaStreamTrack
* aTrack
, bool aEnabled
) override
{
1115 MediaStreamTrackSource::MutedChanged(Muted());
1118 bool Muted() const {
1119 return mElementMuteState
== OutputMuteState::Muted
||
1121 (mCapturedTrack
->Muted() || !mCapturedTrack
->Enabled()));
1124 bool HasAlpha() const override
{
1125 if (mCapturedTrack
) {
1126 return mCapturedTrack
->AsVideoStreamTrack()
1127 ? mCapturedTrack
->AsVideoStreamTrack()->HasAlpha()
1130 return mMediaDecoderHasAlpha
.valueOr(false);
1133 ProcessedMediaTrack
* Track() const { return mTrack
; }
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",
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
)
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
,
1213 public nsIThreadRetargetableStreamListener
{
1214 ~MediaLoadListener() = default;
1216 NS_DECL_THREADSAFE_ISUPPORTS
1217 NS_DECL_NSIREQUESTOBSERVER
1218 NS_DECL_NSISTREAMLISTENER
1219 NS_DECL_NSICHANNELEVENTSINK
1221 NS_DECL_NSIINTERFACEREQUESTOR
1222 NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
1225 explicit MediaLoadListener(HTMLMediaElement
* aElement
)
1226 : mElement(aElement
), mLoadID(aElement
->GetCurrentLoadID()) {
1227 MOZ_ASSERT(mElement
, "Must pass an element to call back");
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
)
1241 HTMLMediaElement::MediaLoadListener::Observe(nsISupports
* aSubject
,
1243 const char16_t
* aData
) {
1244 nsContentUtils::UnregisterShutdownObserver(this);
1246 // Clear mElement to break cycle so we don't leak on shutdown
1252 HTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest
* aRequest
) {
1253 nsContentUtils::UnregisterShutdownObserver(this);
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.
1274 nsresult rv
= aRequest
->GetStatus(&status
);
1275 NS_ENSURE_SUCCESS(rv
, rv
);
1276 if (NS_FAILED(status
)) {
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(
1284 element
->OwnerDoc()->AddBlockedNodeByClassifier(element
);
1286 element
->NotifyLoadError(
1287 nsPrintfCString("%u: %s", uint32_t(status
), "Request failed"));
1292 nsCOMPtr
<nsIHttpChannel
> hc
= do_QueryInterface(aRequest
);
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()));
1303 code
.AppendInt(responseStatus
);
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
);
1313 NS_SUCCEEDED(rv
= element
->InitializeDecoderForChannel(
1314 channel
, getter_AddRefs(mNextListener
))) &&
1316 rv
= mNextListener
->OnStartRequest(aRequest
);
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
;
1334 HTMLMediaElement::MediaLoadListener::OnStopRequest(nsIRequest
* aRequest
,
1336 if (mNextListener
) {
1337 return mNextListener
->OnStopRequest(aRequest
, aStatus
);
1343 HTMLMediaElement::MediaLoadListener::OnDataAvailable(nsIRequest
* aRequest
,
1344 nsIInputStream
* aStream
,
1347 if (!mNextListener
) {
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
);
1357 HTMLMediaElement::MediaLoadListener::AsyncOnChannelRedirect(
1358 nsIChannel
* aOldChannel
, nsIChannel
* aNewChannel
, uint32_t aFlags
,
1359 nsIAsyncVerifyRedirectCallback
* cb
) {
1360 // TODO is this really correct?? See bug #579329.
1362 mElement
->OnChannelRedirect(aOldChannel
, aNewChannel
, aFlags
);
1364 nsCOMPtr
<nsIChannelEventSink
> sink
= do_QueryInterface(mNextListener
);
1366 return sink
->AsyncOnChannelRedirect(aOldChannel
, aNewChannel
, aFlags
, cb
);
1368 cb
->OnRedirectVerifyCallback(NS_OK
);
1373 HTMLMediaElement::MediaLoadListener::CheckListenerChain() {
1374 MOZ_ASSERT(mNextListener
);
1375 nsCOMPtr
<nsIThreadRetargetableStreamListener
> retargetable
=
1376 do_QueryInterface(mNextListener
);
1378 return retargetable
->CheckListenerChain();
1380 return NS_ERROR_NO_INTERFACE
;
1384 HTMLMediaElement::MediaLoadListener::GetInterface(const nsIID
& aIID
,
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
,
1402 class HTMLMediaElement::AudioChannelAgentCallback final
1403 : public nsIAudioChannelAgentCallback
{
1405 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
1406 NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgentCallback
)
1408 explicit AudioChannelAgentCallback(HTMLMediaElement
* aOwner
)
1410 mAudioChannelVolume(1.0),
1411 mPlayingThroughTheAudioChannel(false),
1412 mIsOwnerAudible(IsOwnerAudible()),
1413 mIsShutDown(false) {
1415 MaybeCreateAudioChannelAgent();
1418 void UpdateAudioChannelPlayingState() {
1419 MOZ_ASSERT(!mIsShutDown
);
1420 bool playingThroughTheAudioChannel
= IsPlayingThroughTheAudioChannel();
1422 if (playingThroughTheAudioChannel
!= mPlayingThroughTheAudioChannel
) {
1423 if (!MaybeCreateAudioChannelAgent()) {
1427 mPlayingThroughTheAudioChannel
= playingThroughTheAudioChannel
;
1428 if (mPlayingThroughTheAudioChannel
) {
1429 StartAudioChannelAgent();
1431 StopAudioChanelAgent();
1436 void NotifyPlayStateChanged() {
1437 MOZ_ASSERT(!mIsShutDown
);
1438 UpdateAudioChannelPlayingState();
1441 NS_IMETHODIMP
WindowVolumeChanged(float aVolume
, bool aMuted
) override
{
1442 MOZ_ASSERT(mAudioChannelAgent
);
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
);
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`.
1471 NS_IMETHODIMP
WindowAudioCaptureChanged(bool aCapture
) override
{
1472 MOZ_ASSERT(mAudioChannelAgent
);
1473 AudioCaptureTrackChangeIfNeeded();
1477 void AudioCaptureTrackChangeIfNeeded() {
1478 MOZ_ASSERT(!mIsShutDown
);
1479 if (!IsPlayingStarted()) {
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
) {
1500 mIsOwnerAudible
= newAudibleState
;
1501 if (IsPlayingStarted()) {
1502 mAudioChannelAgent
->NotifyStartedAudible(mIsOwnerAudible
, aReason
);
1507 MOZ_ASSERT(!mIsShutDown
);
1508 if (mAudioChannelAgent
&& mAudioChannelAgent
->IsPlayingStarted()) {
1509 StopAudioChanelAgent();
1511 mAudioChannelAgent
= nullptr;
1515 float GetEffectiveVolume() const {
1516 MOZ_ASSERT(!mIsShutDown
);
1517 return static_cast<float>(mOwner
->Volume()) * mAudioChannelVolume
;
1521 ~AudioChannelAgentCallback() { MOZ_ASSERT(mIsShutDown
); };
1523 bool MaybeCreateAudioChannelAgent() {
1524 if (mAudioChannelAgent
) {
1528 mAudioChannelAgent
= new AudioChannelAgent();
1530 mAudioChannelAgent
->Init(mOwner
->OwnerDoc()->GetInnerWindow(), this);
1531 if (NS_WARN_IF(NS_FAILED(rv
))) {
1532 mAudioChannelAgent
= nullptr;
1534 AudioChannelService::GetAudioChannelLog(), LogLevel::Debug
,
1535 ("HTMLMediaElement::AudioChannelAgentCallback, Fail to initialize "
1536 "the audio channel agent, this = %p\n",
1544 void StartAudioChannelAgent() {
1545 MOZ_ASSERT(mAudioChannelAgent
);
1546 MOZ_ASSERT(!mAudioChannelAgent
->IsPlayingStarted());
1547 if (NS_WARN_IF(NS_FAILED(
1548 mAudioChannelAgent
->NotifyStartedPlaying(IsOwnerAudible())))) {
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();
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()) {
1585 // We should consider any bfcached page or inactive document as non-playing.
1586 if (!mOwner
->OwnerDoc()->IsActive()) {
1590 // Media is suspended by the docshell.
1591 if (mOwner
->ShouldBeSuspendedByInactiveDocShell()) {
1596 if (mOwner
->mPaused
) {
1601 if (!mOwner
->HasAudio()) {
1605 // A loop always is playing
1606 if (mOwner
->HasAttr(kNameSpaceID_None
, nsGkAtoms::loop
)) {
1610 // If we are actually playing...
1611 if (mOwner
->IsCurrentlyPlaying()) {
1615 // If we are playing an external stream.
1616 if (mOwner
->mSrcAttrStream
) {
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
;
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
{
1657 NS_INLINE_DECL_REFCOUNTING(ChannelLoader
);
1659 void LoadInternal(HTMLMediaElement
* aElement
) {
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
;
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
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
,
1700 nullptr, // aPerformanceStorage
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
);
1713 nsCOMPtr
<nsILoadInfo
> loadInfo
= channel
->LoadInfo();
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
));
1724 if (aElement
->mUseUrgentStartForChannel
) {
1725 cos
->AddClassFlags(nsIClassOfService::UrgentStart
);
1727 // Reset the flag to avoid loading again without initiated by user
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
);
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
);
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.
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
,
1787 mChannel
->CancelWithReason(NS_BINDING_ABORTED
,
1788 "HTMLMediaElement::ChannelLoader::Cancel"_ns
);
1794 MOZ_ASSERT(mChannel
);
1795 // Decoder successfully created, the decoder now owns the MediaResource
1796 // which owns the channel.
1800 nsresult
Redirect(nsIChannel
* aChannel
, nsIChannel
* aNewChannel
,
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
);
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
{
1839 explicit ErrorSink(HTMLMediaElement
* aOwner
) : mOwner(aOwner
) {
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.
1851 if (!IsValidErrorCode(aErrorCode
)) {
1852 NS_ASSERTION(false, "Undefined MediaError codes!");
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
);
1871 mOwner
->ChangeNetworkState(NETWORK_IDLE
);
1875 void ResetError() { mError
= nullptr; }
1877 RefPtr
<MediaError
> mError
;
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
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
) {
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()
2002 RefPtr
<MediaSource
> source
= mMediaSource
;
2003 return source
.forget();
2006 already_AddRefed
<Promise
> HTMLMediaElement::MozRequestDebugInfo(
2008 RefPtr
<Promise
> promise
= CreateDOMPromise(aRv
);
2009 if (NS_WARN_IF(aRv
.Failed())) {
2012 auto result
= MakeUnique
<dom::HTMLMediaElementDebugInfo
>();
2014 GetEMEInfo(result
->mEMEInfo
);
2016 if (mVideoFrameContainer
) {
2017 result
->mCompositorDroppedFrames
=
2018 mVideoFrameContainer
->GetDroppedImageCount();
2021 mDecoder
->RequestDebugInfo(result
->mDecoder
)
2023 mAbstractMainThread
, __func__
,
2024 [promise
, ptr
= std::move(result
)]() {
2025 promise
->MaybeResolve(ptr
.get());
2028 MOZ_ASSERT_UNREACHABLE("Unexpected RequestDebugInfo() rejection");
2031 promise
->MaybeResolve(result
.get());
2033 return promise
.forget();
2037 void HTMLMediaElement::MozEnableDebugLog(const GlobalObject
&) {
2038 DecoderDoctorLogger::EnableLogging();
2041 already_AddRefed
<Promise
> HTMLMediaElement::MozRequestDebugLog(
2043 RefPtr
<Promise
> promise
= CreateDOMPromise(aRv
);
2044 if (NS_WARN_IF(aRv
.Failed())) {
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
;
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*/,
2113 void HTMLMediaElement::SetDecodeError(const nsAString
& aError
,
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)
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__
);
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
},
2157 already_AddRefed
<layers::Image
> HTMLMediaElement::GetCurrentImage() {
2160 // TODO: In bug 1345404, handle case when video decoder is already suspended.
2161 ImageContainer
* container
= GetImageContainer();
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");
2192 mSrcAttrStream
= aValue
;
2193 UpdateAudioChannelPlayingState();
2197 bool HTMLMediaElement::Ended() {
2198 return (mDecoder
&& mDecoder
->IsEnded()) ||
2199 (mSrcStream
&& mSrcStreamReportPlaybackEnded
);
2202 void HTMLMediaElement::GetCurrentSrc(nsAString
& aCurrentSrc
) {
2204 GetCurrentSpec(src
);
2205 CopyUTF8toUTF16(src
, aCurrentSrc
);
2208 nsresult
HTMLMediaElement::OnChannelRedirect(nsIChannel
* aChannel
,
2209 nsIChannel
* aNewChannel
,
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();
2221 mMediaSource
->CompletePendingTransactions();
2223 mDecoder
->Shutdown();
2224 DDUNLINKCHILD(mDecoder
.get());
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.
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;
2252 fireTimeUpdate
= mDecoder
->GetCurrentTime() != 0.0;
2256 EndSrcMediaStreamPlayback();
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;
2291 SetAudibleState(false);
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.
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
) {
2355 bool isSameOriginLoad
= false;
2356 nsresult rv
= NS_ERROR_NOT_AVAILABLE
;
2357 if (mSrcAttrTriggeringPrincipal
&& mLoadingSrc
) {
2358 rv
= mSrcAttrTriggeringPrincipal
->IsSameOrigin(mLoadingSrc
,
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
);
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
) {
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
) {
2396 nsContentUtils::RunInStableState(task
.forget());
2399 void HTMLMediaElement::QueueLoadFromSourceTask() {
2400 if (!mIsLoadingFromSourceChildren
|| mShuttingDown
) {
2405 // Reset readyState to HAVE_NOTHING since we're going to load a new decoder.
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
)) {
2441 static nsCString
DocumentOrigin(Document
* aDoc
) {
2445 nsCOMPtr
<nsIPrincipal
> principal
= aDoc
->NodePrincipal();
2450 if (NS_FAILED(principal
->GetOrigin(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
) {
2472 mIsDoingExplicitLoad
= true;
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));
2484 if (mIsRunningLoadMethod
) {
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.
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();
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() {
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);
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
2558 UpdatePreloadAction();
2559 mIsRunningSelectResource
= true;
2561 // If we have a 'src' attribute, use that exclusively.
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()));
2572 !mIsLoadingFromSourceChildren
,
2573 "Should think we're not loading from source children by default");
2575 RemoveMediaElementFromURITable();
2576 if (!mSrcMediaSource
) {
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.
2598 rv
= LoadResource();
2599 if (NS_SUCCEEDED(rv
)) {
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()));
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();
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
) {
2651 LOG(LogLevel::Debug
, ("MediaElement %p %sTrack with id %s enabled", this,
2652 aTrack
->AsAudioTrack() ? "Audio" : "Video",
2653 NS_ConvertUTF16toUTF8(id
).get()));
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()) {
2666 mDisableVideo
= false;
2668 MOZ_ASSERT(false, "Unknown track type");
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
) {
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()) {
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()) {
2733 SetMutedInternal(mMuted
| MUTED_BY_AUDIO_TRACK
);
2736 } else if (aTrack
->AsVideoTrack()) {
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
) {
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();
2777 Element
* child
= GetNextSource();
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");
2788 // Must have src attribute.
2790 if (!child
->GetAttr(kNameSpaceID_None
, nsGkAtoms::src
, src
)) {
2791 ReportLoadError("MediaLoadSourceMissingSrc");
2792 DealWithFailedElement(child
);
2796 // If we have a type attribute, it must be a supported type.
2798 if (child
->GetAttr(kNameSpaceID_None
, nsGkAtoms::type
, type
) &&
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
};
2811 if (nextChild
&& nextChild
->IsHTMLElement(nsGkAtoms::source
)) {
2812 ReportLoadError("MediaLoadUnsupportedTypeAttributeLoadingNextChild",
2817 nextChild
= nextChild
->GetNextSibling();
2821 ReportLoadError("MediaLoadUnsupportedTypeAttribute", params
);
2824 DealWithFailedElement(child
);
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
));
2836 AutoTArray
<nsString
, 1> params
= {src
};
2837 ReportLoadError("MediaLoadInvalidURI", params
);
2838 DealWithFailedElement(child
);
2842 RemoveMediaElementFromURITable();
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.
2863 if (NS_SUCCEEDED(LoadResource())) {
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 "
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());
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 {
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
;
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
2939 uint32_t preloadDefault
= GetPreloadDefault();
2940 uint32_t preloadAuto
= GetPreloadDefaultAuto();
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
;
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
2974 ResumeLoad(PRELOAD_ENOUGH
);
2976 // Preload as much of the video as we can, i.e. don't suspend after
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
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
) {
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
;
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);
3077 SetPlaybackRate(mDefaultPlaybackRate
, IgnoreErrors());
3078 DispatchAsyncEvent(u
"loadstart"_ns
);
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();
3111 if (JSObject
* obj
= win
->AsGlobal()->GetGlobalJSObject()) {
3112 js::NotifyAnimationActivity(obj
);
3116 Seek(CurrentTime(), SeekTarget::NextFrame
, aRv
);
3121 mSeekDOMPromise
= CreateDOMPromise(aRv
);
3122 if (NS_WARN_IF(aRv
.Failed())) {
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
) {
3152 double end
= aRanges
.End(i
);
3153 if (aValue
<= end
) {
3158 aIntervalIndex
= length
;
3162 void HTMLMediaElement::Seek(double aTime
, SeekTarget::Type aSeekType
,
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()) {
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
);
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
);
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
);
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
);
3221 RefPtr
<TimeRanges
> seekable
=
3222 new TimeRanges(ToSupports(OwnerDoc()), seekableIntervals
);
3223 uint32_t length
= seekable
->Length();
3225 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
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
3235 bool isInRange
= IsInRanges(*seekable
, aTime
, range
);
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);
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 {
3279 if (mSrcStreamPlaybackEnded
) {
3280 return CurrentTime();
3282 return std::numeric_limits
<double>::infinity();
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;
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()"));
3332 void HTMLMediaElement::PauseInternal() {
3333 if (mDecoder
&& mNetworkState
!= NETWORK_EMPTY
) {
3336 bool oldPaused
= mPaused
;
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();
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
);
3364 if (aVolume
== mVolume
) return;
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
,
3381 if (mReadyState
< HAVE_METADATA
) {
3382 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
3386 JS::Rooted
<JSObject
*> tags(aCx
, JS_NewPlainObject(aCx
));
3388 aRv
.Throw(NS_ERROR_FAILURE
);
3392 for (const auto& entry
: *mTags
) {
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
);
3409 void HTMLMediaElement::SetMutedInternal(uint32_t aMuted
) {
3410 uint32_t oldMuted
= mMuted
;
3413 if (!!aMuted
== !!oldMuted
) {
3417 SetVolumeInternal();
3420 void HTMLMediaElement::PauseIfShouldNotBePlaying() {
3424 if (!AllowedToPlay()) {
3425 AUTOPLAY_LOG("pause because not allowed to play, element=%p", this);
3431 void HTMLMediaElement::SetVolumeInternal() {
3432 float effectiveVolume
= ComputedVolume();
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()) {
3451 SetMutedInternal(mMuted
| MUTED_BY_CONTENT
);
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
);
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()));
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());
3524 domTrack
= new VideoStreamTrack(
3525 aOutputStream
.mStream
->GetOwner(), aSource
->Track(), aSource
,
3526 MediaStreamTrackState::Live
, aSource
->Muted());
3529 aOutputStream
.mLiveTracks
.AppendElement(domTrack
);
3532 case AddTrackMode::ASYNC
:
3533 mMainThreadEventTarget
->Dispatch(
3534 NewRunnableMethod
<StoreRefPtrPassByPtr
<MediaStreamTrack
>>(
3535 "DOMMediaStream::AddTrackInternal", aOutputStream
.mStream
,
3536 &DOMMediaStream::AddTrackInternal
, domTrack
));
3538 case AddTrackMode::SYNC
:
3539 aOutputStream
.mStream
->AddTrackInternal(domTrack
);
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
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();
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
);
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
) {
3602 mOutputTrackSources
.GetWeak(track
->GetId());
3604 trackSourcesToRemove
.RemoveElement(track
->GetId());
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()));
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
) {
3634 DebugOnly
<size_t> length
= ms
.mLiveTracks
.Length();
3635 ms
.mLiveTracks
.RemoveElementsBy(
3636 [&](const RefPtr
<MediaStreamTrack
>& aTrack
) {
3637 if (&aTrack
->GetSource() != source
) {
3640 mMainThreadEventTarget
->Dispatch(
3641 NewRunnableMethod
<RefPtr
<MediaStreamTrack
>>(
3642 "DOMMediaStream::RemoveTrackInternal", ms
.mStream
,
3643 &DOMMediaStream::RemoveTrackInternal
, aTrack
));
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
) {
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()) {
3665 mOutputStreams
[i
].mFinishWhenEndedLoadingSrc
= mLoadingSrc
;
3666 } else if (mSrcAttrStream
) {
3667 mOutputStreams
[i
].mFinishWhenEndedAttrStream
= mSrcAttrStream
;
3668 } else if (mSrcMediaSource
) {
3669 mOutputStreams
[i
].mFinishWhenEndedMediaSource
= mSrcMediaSource
;
3675 // Discard finish-when-ended output streams with a loading src set as
3677 if (!IsPlaybackEnded() &&
3678 mLoadingSrc
== mOutputStreams
[i
].mFinishWhenEndedLoadingSrc
) {
3681 if (!IsPlaybackEnded() &&
3682 mSrcAttrStream
== mOutputStreams
[i
].mFinishWhenEndedAttrStream
) {
3685 if (!IsPlaybackEnded() &&
3686 mSrcMediaSource
== mOutputStreams
[i
].mFinishWhenEndedMediaSource
) {
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.
3701 // Finally add new MediaTracks.
3702 for (const auto& mediaTrack
: mediaTracksToAdd
) {
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
;
3712 MOZ_CRASH("Unknown track type");
3715 RefPtr
<ProcessedMediaTrack
> track
;
3716 RefPtr
<MediaElementTrackSource
> source
;
3718 track
= mTracksCaptured
.Ref()->mTrack
->Graph()->CreateForwardedInputTrack(
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()
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();
3737 MOZ_CRASH("Unknown track type");
3739 MOZ_ASSERT(inputTrack
);
3741 NS_ERROR("Input track not found in source stream");
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
);
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.
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();
3785 // Prevent capturing restricted video
3786 if (aCaptureType
== StreamCaptureType::CAPTURE_ALL_TRACKS
&&
3787 ContainsRestrictedContent()) {
3793 already_AddRefed
<DOMMediaStream
> HTMLMediaElement::CaptureStreamInternal(
3794 StreamCaptureBehavior aFinishBehavior
, StreamCaptureType aStreamCaptureType
,
3795 MediaTrackGraph
* aGraph
) {
3796 MOZ_ASSERT(CanBeCaptured(aStreamCaptureType
));
3798 LogVisibility(CallerAPI::CAPTURE_STREAM
);
3801 if (mTracksCaptured
.Ref() &&
3802 aGraph
!= mTracksCaptured
.Ref()->mTrack
->Graph()) {
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
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
) {
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
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
3865 if (out
->mCapturingAudioOnly
) {
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
);
3884 RefPtr
<DOMMediaStream
> stream
=
3885 CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED
,
3886 StreamCaptureType::CAPTURE_AUDIO
, aGraph
);
3888 aRv
.Throw(NS_ERROR_FAILURE
);
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(
3909 MediaTrackGraph::GraphDriverType graphDriverType
=
3910 HasAudio() ? MediaTrackGraph::AUDIO_THREAD_DRIVER
3911 : MediaTrackGraph::SYSTEM_THREAD_DRIVER
;
3913 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
3915 aRv
.Throw(NS_ERROR_FAILURE
);
3919 if (!CanBeCaptured(StreamCaptureType::CAPTURE_ALL_TRACKS
)) {
3920 aRv
.Throw(NS_ERROR_FAILURE
);
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
);
3932 aRv
.Throw(NS_ERROR_FAILURE
);
3936 return stream
.forget();
3939 already_AddRefed
<DOMMediaStream
> HTMLMediaElement::MozCaptureStreamUntilEnded(
3941 MediaTrackGraph::GraphDriverType graphDriverType
=
3942 HasAudio() ? MediaTrackGraph::AUDIO_THREAD_DRIVER
3943 : MediaTrackGraph::SYSTEM_THREAD_DRIVER
;
3945 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
3947 aRv
.Throw(NS_ERROR_FAILURE
);
3951 if (!CanBeCaptured(StreamCaptureType::CAPTURE_ALL_TRACKS
)) {
3952 aRv
.Throw(NS_ERROR_FAILURE
);
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
);
3964 aRv
.Throw(NS_ERROR_FAILURE
);
3968 return stream
.forget();
3971 class MediaElementSetForURI
: public nsURIHashKey
{
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
;
3988 static bool URISafeEquals(nsIURI
* a1
, nsIURI
* a2
) {
3990 // Consider two empty URIs *not* equal!
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
,
4001 if (!gElementTable
|| !aElement
) {
4004 uint32_t uriCount
= 0;
4005 uint32_t otherCount
= 0;
4006 for (const auto& entry
: *gElementTable
) {
4008 for (const auto& elem
: entry
.mElements
) {
4009 if (elem
== aElement
) {
4013 if (URISafeEquals(aURI
, entry
.GetKey())) {
4016 otherCount
+= count
;
4019 NS_ASSERTION(otherCount
== 0, "Should not have entries for unknown URIs");
4024 void HTMLMediaElement::AddMediaElementToURITable() {
4025 NS_ASSERTION(mDecoder
, "Call this only with decoder Load called");
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);
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
) {
4043 MediaElementSetForURI
* entry
= gElementTable
->GetEntry(mLoadingSrc
);
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
) {
4063 MediaElementSetForURI
* entry
= gElementTable
->GetEntry(aURI
);
4067 for (uint32_t i
= 0; i
< entry
->mElements
.Length(); ++i
) {
4068 HTMLMediaElement
* elem
= entry
->mElements
[i
];
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()) {
4086 class HTMLMediaElement::ShutdownObserver
: public nsIObserver
{
4087 enum class Phase
: int8_t { Init
, Subscribed
, Unsubscribed
};
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)|.
4099 MOZ_DIAGNOSTIC_ASSERT(mWeak
);
4100 if (strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
) == 0) {
4101 mWeak
->NotifyShutdownEvent();
4105 void Subscribe(HTMLMediaElement
* aPtr
) {
4106 MOZ_DIAGNOSTIC_ASSERT(mPhase
== Phase::Init
);
4107 MOZ_DIAGNOSTIC_ASSERT(!mWeak
);
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");
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");
4127 void ReleaseMediaElement() {
4128 MOZ_DIAGNOSTIC_ASSERT(mWeak
);
4129 MOZ_DIAGNOSTIC_ASSERT(mAddRefed
, "Should only release after AddRef");
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
{
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
{
4162 mElement
->UpdateStreamName();
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");
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
)),
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
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
);
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
4260 MediaShutdownManager::InitStatics();
4262 #if defined(MOZ_WIDGET_ANDROID)
4263 GVAutoplayPermissionRequestor::AskForPermissionIfNeeded(
4264 OwnerDoc()->GetInnerWindow());
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.");
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__
);
4296 if (mProgressTimer
) {
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;
4325 DecoderDoctorLogger::LogDestruction(this);
4328 void HTMLMediaElement::StopSuspendingAfterFirstFrame() {
4329 mAllowSuspendAfterFirstFrame
= false;
4330 if (!mSuspendedAfterFirstFrame
) return;
4331 mSuspendedAfterFirstFrame
= false;
4337 void HTMLMediaElement::SetPlayedOrSeeked(bool aValue
) {
4338 if (aValue
== mHasPlayedOrSeeked
) {
4342 mHasPlayedOrSeeked
= aValue
;
4344 // Force a reflow so that the poster frame hides or shows immediately.
4345 nsIFrame
* frame
= GetPrimaryFrame();
4349 frame
->PresShell()->FrameNeedsReflow(frame
, IntrinsicDirty::TreeChange
,
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()));
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())) {
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
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));
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();
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()) {
4452 RefPtr
<AsyncEventDispatcher
> asyncDispatcher
= new AsyncEventDispatcher(
4453 videoFrame
->GetVideoControls(), u
"MozNoControlsBlockedVideo"_ns
,
4455 asyncDispatcher
->PostDOMEvent();
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.
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.
4486 if (mDecoder
->IsEnded()) {
4489 if (!mSuspendedByInactiveDocOrDocshell
) {
4494 if (mCurrentPlayRangeStart
== -1.0) {
4495 mCurrentPlayRangeStart
= CurrentTime();
4498 const bool oldPaused
= mPaused
;
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:
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
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.
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
4544 switch (mReadyState
) {
4546 DispatchAsyncEvent(u
"waiting"_ns
);
4549 case HAVE_CURRENT_DATA
:
4550 DispatchAsyncEvent(u
"waiting"_ns
);
4552 case HAVE_FUTURE_DATA
:
4553 case HAVE_ENOUGH_DATA
:
4554 NotifyAboutPlaying();
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
) {
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();
4586 ReleaseAudioWakeLockIfExists();
4590 void HTMLMediaElement::CreateAudioWakeLockIfNeeded() {
4592 RefPtr
<power::PowerManagerService
> pmService
=
4593 power::PowerManagerService::GetInstance();
4594 NS_ENSURE_TRUE_VOID(pmService
);
4597 mWakeLock
= pmService
->NewWakeLock(u
"audio-playing"_ns
,
4598 OwnerDoc()->GetInnerWindow(), rv
);
4602 void HTMLMediaElement::ReleaseAudioWakeLockIfExists() {
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
);
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
) {
4626 // Always prevent touchmove captured in video element from being handled by
4627 // content, since we always do that for touchstart.
4631 case eMouseDoubleClick
:
4634 aVisitor
.mCanHandle
= false;
4637 // The *move events however are only comsumed when the range input is being
4642 nsINode::FromEventTargetOrNull(aVisitor
.mEvent
->mOriginalTarget
);
4643 if (MOZ_UNLIKELY(!node
)) {
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;
4661 nsGenericHTMLElement::GetEventTargetParent(aVisitor
);
4665 nsGenericHTMLElement::GetEventTargetParent(aVisitor
);
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
},
4682 if (aNamespaceID
== kNameSpaceID_None
) {
4683 if (aAttribute
== nsGkAtoms::crossorigin
) {
4684 ParseCORSValue(aValue
, aResult
);
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
,
4709 *aIsFocusable
= true;
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
,
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
);
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
)) {
4735 GetCurrentSrc(spec
);
4736 AutoTArray
<nsString
, 1> params
= {spec
};
4737 ReportLoadError("MediaLoadInvalidURI", params
);
4741 } else if (aName
== nsGkAtoms::autoplay
) {
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
) {
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.
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
,
4775 AfterMaybeChangeAttr(aNamespaceID
, aName
, aNotify
);
4777 return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID
, aName
,
4781 void HTMLMediaElement::AfterMaybeChangeAttr(int32_t aNamespaceID
, nsAtom
* aName
,
4783 if (aNamespaceID
== kNameSpaceID_None
) {
4784 if (aName
== nsGkAtoms::src
) {
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();
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
);
4842 CanPlayStatus
HTMLMediaElement::GetCanPlay(
4843 const nsAString
& aType
, DecoderDoctorDiagnostics
* aDiagnostics
) {
4844 Maybe
<MediaContainerType
> containerType
= MakeMediaContainerType(aType
);
4845 if (!containerType
) {
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()`.
4859 return CANPLAY_MAYBE
;
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
,
4874 aResult
.AssignLiteral("probably");
4877 aResult
.AssignLiteral("maybe");
4880 MOZ_ASSERT_UNREACHABLE("Unexpected case.");
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
) {
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
);
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
));
4941 rv
= FinishDecoderSetup(aDecoder
);
4942 // Only ChannelMediaDecoder supports resource cloning.
4943 if (std::is_same_v
<DecoderType
, ChannelMediaDecoder
> && NS_SUCCEEDED(rv
)) {
4944 AddMediaElementToURITable();
4946 MediaElementTableCount(this, mLoadingSrc
) == 1,
4947 "Media element should have single table entry if decode initialized");
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
,
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
);
5000 reportCanPlay(false);
5001 return NS_ERROR_OUT_OF_MEMORY
;
5003 reportCanPlay(true);
5004 return SetupDecoder(decoder
.get(), aChannel
);
5008 RefPtr
<ChannelMediaDecoder
> decoder
=
5009 ChannelMediaDecoder::Create(decoderInit
, &diagnostics
);
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.
5037 ->SetSink(mSink
.second
)
5039 ->Then(mAbstractMainThread
, __func__
,
5040 [](const GenericPromise::ResolveOrRejectValue
& aValue
) {
5041 MOZ_ASSERT(aValue
.IsResolve() && !aValue
.ResolveValue());
5049 if (mMediaKeys
->GetCDMProxy()) {
5050 mDecoder
->SetCDMProxy(mMediaKeys
->GetCDMProxy());
5052 // CDM must have crashed.
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();
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
5072 return NS_ERROR_FAILURE
;
5075 if (mSuspendedByInactiveDocOrDocshell
) {
5076 mDecoder
->Suspend();
5080 SetPlayedOrSeeked(true);
5081 if (!mSuspendedByInactiveDocOrDocshell
) {
5086 MaybeBeginCloningVisually();
5091 void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags
) {
5096 bool shouldPlay
= !(aFlags
& REMOVING_SRC_STREAM
) && !mPaused
&&
5097 !mSuspendedByInactiveDocOrDocshell
;
5098 if (shouldPlay
== mSrcStreamIsPlaying
) {
5101 mSrcStreamIsPlaying
= shouldPlay
;
5103 LOG(LogLevel::Debug
,
5104 ("MediaElement %p %s playback of DOMMediaStream %p", this,
5105 shouldPlay
? "Setting up" : "Removing", mSrcStream
.get()));
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);
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.
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.
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
=
5162 ? MakeAndAddRef
<FirstFrameVideoOutput
>(container
, mAbstractMainThread
)
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();
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
) {
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
) {
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
) {
5270 if (aTrack
->Ended()) {
5278 LOG(LogLevel::Debug
, ("%p, Adding %sTrack with id %s", this,
5279 aTrack
->AsAudioStreamTrack() ? "Audio" : "Video",
5280 NS_ConvertUTF16toUTF8(id
).get()));
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.
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
) {
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
);
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 "
5338 void HTMLMediaElement::ProcessMediaFragmentURI() {
5340 mFragmentStart
= mFragmentEnd
= -1.0;
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());
5360 ConstructMediaTracks(aInfo
);
5363 SetMediaInfo(*aInfo
);
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
);
5390 // We only support playback of encrypted content via MSE by default.
5391 if (!mMediaSource
&& Preferences::GetBool("media.eme.mse-only", true)) {
5393 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR
,
5394 "Encrypted content not supported outside of MSE"));
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());
5443 Error(MEDIA_ERR_NETWORK
);
5447 void HTMLMediaElement::DecodeError(const MediaResult
& aError
) {
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();
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());
5469 Error(MEDIA_ERR_DECODE
, "Failed to decode media"_ns
);
5473 void HTMLMediaElement::DecodeWarning(const MediaResult
& aError
) {
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
);
5502 LOG(LogLevel::Debug
,
5503 ("%p, got duration by reaching the end of the resource", this));
5504 mSrcStreamPlaybackEnded
= true;
5505 DispatchAsyncEvent(u
"durationchange"_ns
);
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
)) {
5516 FireTimeUpdate(TimeupdateType::eMandatory
);
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
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();
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
);
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
) {
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
5607 (mProgressTime
.IsNull() && !aHaveNewProgress
) || !mDataTime
.IsNull(),
5608 "null TimeStamp mDataTime should not be used in comparison");
5609 if (mProgressTime
.IsNull()
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
5634 mWatchManager
.ManualNotify(&HTMLMediaElement::UpdateReadyStateInternal
);
5637 if (now
- mDataTime
>= TimeDuration::FromMilliseconds(STALL_MS
)) {
5638 if (!mMediaSource
) {
5639 DispatchAsyncEvent(u
"stalled"_ns
);
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.
5650 AddRemoveSelfReference();
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
) {
5685 mProgressTimer
->Cancel();
5686 mProgressTimer
= nullptr;
5689 void HTMLMediaElement::DownloadProgressed() {
5690 if (mNetworkState
!= NETWORK_LOADING
) {
5693 CheckProgress(true);
5696 bool HTMLMediaElement::ShouldCheckAllowOrigin() {
5697 return mCORSMode
!= CORS_NONE
;
5700 bool HTMLMediaElement::IsCORSSameOrigin() {
5702 RefPtr
<nsIPrincipal
> principal
= GetCurrentPrincipal();
5703 return (NS_SUCCEEDED(NodePrincipal()->Subsumes(principal
, &subsumes
)) &&
5705 ShouldCheckAllowOrigin();
5708 void HTMLMediaElement::UpdateReadyStateInternal() {
5709 if (!mDecoder
&& !mSrcStream
) {
5710 // Not initialized - bail out.
5711 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
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",
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",
5739 // Give it one last chance to remove the self reference if needed.
5740 AddRemoveSelfReference();
5744 if (IsVideo() && hasVideoTracks
&& !HasVideo()) {
5745 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
5746 "Stream waiting for video",
5751 LOG(LogLevel::Debug
,
5752 ("MediaElement %p UpdateReadyStateInternal() Stream has "
5753 "metadata; audioTracks=%d, videoTracks=%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);
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
5795 // 6. Suspend playback.
5796 // Note: Playback will already be stalled, as the next frame is
5798 mWaitingForKey
= WAITING_FOR_KEY_DISPATCHED
;
5799 DispatchAsyncEvent(u
"waitingforkey"_ns
);
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",
5815 ChangeReadyState(HAVE_METADATA
);
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",
5830 ChangeReadyState(HAVE_METADATA
);
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.
5840 if (nextFrameStatus
== NEXT_FRAME_UNAVAILABLE_BUFFERING
) {
5841 // Force HAVE_CURRENT_DATA when buffering.
5842 ChangeReadyState(HAVE_CURRENT_DATA
);
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
);
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",
5867 ChangeReadyState(HAVE_ENOUGH_DATA
);
5871 if (nextFrameStatus
!= MediaDecoderOwner::NEXT_FRAME_AVAILABLE
) {
5872 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
5873 "Next frame not available",
5875 ChangeReadyState(HAVE_CURRENT_DATA
);
5880 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
5881 "Stream HAVE_ENOUGH_DATA",
5883 ChangeReadyState(HAVE_ENOUGH_DATA
);
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",
5899 ChangeReadyState(HAVE_ENOUGH_DATA
);
5902 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
5903 "Default; Decoder has future data",
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
) {
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
) {
5935 UpdateAudioChannelPlayingState();
5937 // Handle raising of "waiting" event during seek (see 4.8.10.9)
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
);
5965 if (mDecoder
&& !mSuspendedByInactiveDocOrDocshell
) {
5966 MOZ_ASSERT(AllowedToPlay());
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",
5983 void HTMLMediaElement::ChangeNetworkState(nsMediaNetworkState aState
) {
5984 if (mNetworkState
== aState
) {
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.
6000 if (mNetworkState
== NETWORK_LOADING
) {
6001 // Start progress notification when entering NETWORK_LOADING.
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
) {
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
)) {
6028 if (!mAutoplaying
) {
6040 if (mSuspendedByInactiveDocOrDocshell
) {
6044 // Static document is used for print preview and printing, should not be
6046 if (OwnerDoc()->IsStaticDocument()) {
6050 if (ShouldBeSuspendedByInactiveDocShell()) {
6051 LOG(LogLevel::Debug
, ("%p prohibiting autoplay by the docShell", this));
6055 if (MediaPlaybackDelayPolicy::ShouldDelayPlayback(this)) {
6056 CreateResumeDelayedMediaPlaybackAgentIfNeeded();
6057 LOG(LogLevel::Debug
, ("%p delay playing from autoplay", this));
6061 return mReadyState
>= HAVE_ENOUGH_DATA
;
6064 void HTMLMediaElement::CheckAutoplayDataReady() {
6065 if (!CanActivateAutoplay()) {
6069 if (!AllowedToPlay()) {
6070 DispatchEventsWhenPlayWasNotAllowed();
6074 mAllowedToPlayPromise
.ResolveIfExists(true, __func__
);
6076 // We changed mPaused which can affect AddRemoveSelfReference
6077 AddRemoveSelfReference();
6078 UpdateSrcMediaStreamPlaying();
6079 UpdateAudioChannelPlayingState();
6080 StartMediaControlKeyListenerIfNeeded();
6083 SetPlayedOrSeeked(true);
6084 if (mCurrentPlayRangeStart
== -1.0) {
6085 mCurrentPlayRangeStart
= CurrentTime();
6087 MOZ_ASSERT(!mSuspendedByInactiveDocOrDocshell
);
6089 } else if (mSrcStream
) {
6090 SetPlayedOrSeeked(true);
6093 // https://html.spec.whatwg.org/multipage/media.html#ready-states:show-poster-flag
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()) {
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
6117 if (!IsInViewPort()) {
6121 // Element being used in picture-in-picture mode would be always visible.
6122 if (IsBeingUsedInPictureInPictureMode()) {
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
) {
6139 if (mVideoFrameContainer
) return mVideoFrameContainer
;
6141 // Only video frames need an image container.
6146 mVideoFrameContainer
= new VideoFrameContainer(
6147 this, MakeAndAddRef
<ImageContainer
>(ImageContainer::ASYNCHRONOUS
));
6149 return mVideoFrameContainer
;
6152 void HTMLMediaElement::PrincipalChanged(MediaStreamTrack
* aTrack
) {
6153 if (aTrack
!= mSelectedVideoStreamTrack
) {
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()) &&
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 "
6185 this, track
.get()));
6186 mSrcStreamVideoPrincipal
= track
->GetPrincipal();
6192 void HTMLMediaElement::PrincipalHandleChangedForVideoFrameContainer(
6193 VideoFrameContainer
* aContainer
,
6194 const PrincipalHandle
& aNewPrincipalHandle
) {
6195 MOZ_ASSERT(NS_IsMainThread());
6201 LOG(LogLevel::Debug
, ("HTMLMediaElement %p PrincipalHandle changed in "
6202 "VideoFrameContainer.",
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
);
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
);
6231 return nsContentUtils::DispatchTrustedEvent(
6232 OwnerDoc(), static_cast<nsIContent
*>(this), aName
, CanBubble::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
);
6249 mMainThreadEventTarget
->Dispatch(aRunner
.forget());
6252 bool HTMLMediaElement::IsPotentiallyPlaying() const {
6254 // playback has not stopped due to errors,
6255 // and the element has not paused for user interaction
6257 (mReadyState
== HAVE_ENOUGH_DATA
|| mReadyState
== HAVE_FUTURE_DATA
) &&
6261 bool HTMLMediaElement::IsPlaybackEnded() const {
6263 // the current playback position is equal to the effective end of the media
6264 // resource. See bug 449157.
6266 return mReadyState
>= HAVE_METADATA
&& mDecoder
->IsEnded();
6269 return mReadyState
>= HAVE_METADATA
&& mSrcStreamPlaybackEnded
;
6274 already_AddRefed
<nsIPrincipal
> HTMLMediaElement::GetCurrentPrincipal() {
6276 return mDecoder
->GetCurrentPrincipal();
6279 nsTArray
<RefPtr
<MediaStreamTrack
>> tracks
;
6280 mSrcStream
->GetTracks(tracks
);
6281 nsCOMPtr
<nsIPrincipal
> principal
= mSrcStream
->GetPrincipal();
6282 return principal
.forget();
6287 bool HTMLMediaElement::HadCrossOriginRedirects() {
6289 return mDecoder
->HadCrossOriginRedirects();
6294 already_AddRefed
<nsIPrincipal
> HTMLMediaElement::GetCurrentVideoPrincipal() {
6296 return mDecoder
->GetCurrentPrincipal();
6299 nsCOMPtr
<nsIPrincipal
> principal
= mSrcStreamVideoPrincipal
;
6300 return principal
.forget();
6305 void HTMLMediaElement::NotifyDecoderPrincipalChanged() {
6306 RefPtr
<nsIPrincipal
> principal
= GetCurrentPrincipal();
6307 bool isSameOrigin
= !principal
|| IsCORSSameOrigin();
6308 mDecoder
->UpdateSameOriginStatus(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());
6326 nsPresContext
* presContext
= frame
->PresContext();
6327 PresShell
* presShell
= presContext
->PresShell();
6328 presShell
->FrameNeedsReflow(frame
, IntrinsicDirty::StyleChange
,
6333 RefPtr
<ImageContainer
> imageContainer
= GetImageContainer();
6334 bool asyncInvalidate
=
6335 imageContainer
&& imageContainer
->IsAsync() && !aForceInvalidate
;
6337 if (aImageSizeChanged
) {
6338 frame
->InvalidateFrame();
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
) {
6368 mSuspendedByInactiveDocOrDocshell
= aSuspendElement
;
6369 UpdateSrcMediaStreamPlaying();
6370 UpdateAudioChannelPlayingState();
6372 if (aSuspendElement
) {
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();
6385 if (!mPaused
&& !mDecoder
->IsEnded()) {
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
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;
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;
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(); }));
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;
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;
6538 if (mSourcePointer
== nsINode::GetLastChild()) {
6539 return nullptr; // no more children
6542 if (!mSourcePointer
) {
6543 mSourcePointer
= nsINode::GetFirstChild();
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!");
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()));
6567 mDecoder
->SetLoadInBackground(!aDelay
);
6570 mLoadBlockedDoc
= OwnerDoc();
6571 mLoadBlockedDoc
->BlockOnload();
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
);
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
) {
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()) {
6654 // Number of milliseconds between timeupdate events as defined by spec.
6655 if (!mQueueTimeUpdateRunnerTime
.IsNull() &&
6656 TimeStamp::Now() - mQueueTimeUpdateRunnerTime
<
6657 TimeDuration::FromMilliseconds(TIMEUPDATE_MS
)) {
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
) {
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.
6696 mLoadingSrc
->GetSpec(aString
);
6697 } else if (mSrcMediaSource
) {
6698 // If playing an ObjectURL, and it's a MediaSource, return the value of the
6702 CopyUTF16toUTF8(src
, aString
);
6704 // Playing e.g. a MediaStream via an object URL - return an empty string
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
6718 void HTMLMediaElement::SetDefaultPlaybackRate(double aDefaultPlaybackRate
,
6720 if (mSrcAttrStream
) {
6724 if (aDefaultPlaybackRate
< 0) {
6725 aRv
.Throw(NS_ERROR_NOT_IMPLEMENTED
);
6729 double defaultPlaybackRate
= ClampPlaybackRate(aDefaultPlaybackRate
);
6731 if (mDefaultPlaybackRate
== defaultPlaybackRate
) {
6735 mDefaultPlaybackRate
= defaultPlaybackRate
;
6736 DispatchAsyncEvent(u
"ratechange"_ns
);
6739 void HTMLMediaElement::SetPlaybackRate(double aPlaybackRate
, ErrorResult
& aRv
) {
6740 if (mSrcAttrStream
) {
6744 // Changing the playback rate of a media that has more than two channels is
6746 if (aPlaybackRate
< 0) {
6747 aRv
.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
6751 if (mPlaybackRate
== aPlaybackRate
) {
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
);
6762 SetMutedInternal(mMuted
& ~MUTED_BY_INVALID_PLAYBACK_RATE
);
6766 mDecoder
->SetPlaybackRate(ClampPlaybackRate(mPlaybackRate
));
6768 DispatchAsyncEvent(u
"ratechange"_ns
);
6771 void HTMLMediaElement::SetPreservesPitch(bool aPreservesPitch
) {
6772 mPreservesPitch
= aPreservesPitch
;
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
: {
6794 case Visibility::ApproximatelyNonVisible
: {
6795 return "ApproximatelyNonVisible";
6797 case Visibility::ApproximatelyVisible
: {
6798 return "ApproximatelyVisible";
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
);
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.
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.
6855 RefPtr
<HTMLMediaElement
> self
= this;
6856 mDecoder
->SetCDMProxy(nullptr)
6858 mAbstractMainThread
, __func__
,
6860 self
->mSetCDMRequest
.Complete();
6862 self
->RemoveMediaKeys();
6863 if (self
->AttachNewMediaKeys()) {
6864 // No incoming MediaKeys object or MediaDecoder is not
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
);
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"));
6899 // 5.2 If the mediaKeys attribute is not null, run the following steps:
6901 return TryRemoveMediaKeysAssociation();
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__
));
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.
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
)
6932 mAbstractMainThread
, __func__
,
6934 self
->mSetCDMRequest
.Complete();
6935 self
->MakeAssociationWithCDMResolved();
6937 [self
](const MediaResult
& aResult
) {
6938 self
->mSetCDMRequest
.Complete();
6939 self
->SetCDMProxyFailure(aResult
);
6941 ->Track(mSetCDMRequest
);
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();
6956 SetCDMProxyFailure(MediaResult(
6957 NS_ERROR_DOM_INVALID_STATE_ERR
,
6958 "CDM crashed before binding MediaKeys object to HTMLMediaElement"));
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.
6973 MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR
,
6974 "Failed to bind MediaKeys object to HTMLMediaElement"));
6977 return TryMakeAssociationWithCDM(cdmProxy
);
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
);
6997 nsPIDOMWindowInner
* win
= OwnerDoc()->GetInnerWindow();
6999 aRv
.Throw(NS_ERROR_UNEXPECTED
);
7002 RefPtr
<DetailedPromise
> promise
= DetailedPromise::Create(
7003 win
->AsGlobal(), aRv
, "HTMLMediaElement.setMediaKeys"_ns
);
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:
7033 if (!DetachExistingMediaKeys() || !AttachNewMediaKeys()) {
7034 return promise
.forget();
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
);
7072 RefPtr
<MediaEncryptedEvent
> event
;
7073 if (IsCORSSameOrigin()) {
7074 event
= MediaEncryptedEvent::Constructor(this, aInitDataType
, aInitData
);
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() {
7138 return mDecoder
->NextFrameStatus();
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.
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"
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.
7206 void HTMLMediaElement::SetMediaInfo(const MediaInfo
& aInfo
) {
7207 const bool oldHasAudio
= mMediaInfo
.HasAudio();
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();
7217 mTitleChangeObserver
->Unsubscribe();
7219 if (mAudioChannelWrapper
) {
7220 mAudioChannelWrapper
->AudioCaptureTrackChangeIfNeeded();
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()) {
7235 DispatchAsyncEvent(aName
);
7238 void HTMLMediaElement::AudioCaptureTrackChange(bool aCapture
) {
7239 // No need to capture a silent media element.
7244 if (aCapture
&& !mStreamWindowCapturer
) {
7245 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
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
) {
7268 mOutputStreams
.RemoveElementAt(i
);
7272 mStreamWindowCapturer
= nullptr;
7273 if (mOutputStreams
.IsEmpty()) {
7274 mTracksCaptured
= nullptr;
7279 void HTMLMediaElement::NotifyCueDisplayStatesChanged() {
7280 if (!mTextTrackManager
) {
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
)));
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();
7326 aRv
.Throw(NS_ERROR_UNEXPECTED
);
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();
7341 aRv
.Throw(NS_ERROR_UNEXPECTED
);
7345 return Promise::Create(win
->AsGlobal(), aRv
);
7348 void HTMLMediaElement::AsyncResolvePendingPlayPromises() {
7349 if (mShuttingDown
) {
7353 nsCOMPtr
<nsIRunnable
> event
= new nsResolveOrRejectPendingPlayPromisesRunner(
7354 this, TakePendingPlayPromises());
7356 mMainThreadEventTarget
->Dispatch(event
.forget());
7359 void HTMLMediaElement::AsyncRejectPendingPlayPromises(nsresult aError
) {
7362 DispatchAsyncEvent(u
"pause"_ns
);
7365 if (mShuttingDown
) {
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!");
7385 mMediaKeys
->GetKeySystem(aInfo
.mKeySystem
);
7386 mMediaKeys
->GetSessionsInfo(aInfo
.mSessionsInfo
);
7389 void HTMLMediaElement::NotifyDecoderActivityChanges() const {
7391 mDecoder
->NotifyOwnerActivityChanged(IsActuallyInvisible(),
7396 Document
* HTMLMediaElement::GetDocument() const { return OwnerDoc(); }
7398 bool HTMLMediaElement::IsAudible() const {
7404 // Muted or the volume should not be ~0
7405 if (mMuted
|| (std::fabs(Volume()) <= 1e-7)) {
7409 return mIsAudioTrackAudible
;
7412 Maybe
<nsAutoString
> HTMLMediaElement::GetKeySystem() const {
7416 nsAutoString keySystem
;
7417 mMediaKeys
->GetKeySystem(keySystem
);
7418 return Some(keySystem
);
7421 void HTMLMediaElement::ConstructMediaTracks(const MediaInfo
* aInfo
) {
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
,
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
{
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.
7468 return do_AddRef(mElement
->OwnerDoc()->GetInnerWindow());
7472 WeakPtr
<HTMLMediaElement
> mElement
;
7475 already_AddRefed
<GMPCrashHelper
> HTMLMediaElement::CreateGMPCrashHelper() {
7476 return MakeAndAddRef
<MediaElementGMPCrashHelper
>(this);
7479 void HTMLMediaElement::MarkAsTainted() {
7480 mHasSuspendTaint
= true;
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
,
7494 nsCOMPtr
<nsPIDOMWindowInner
> win
= OwnerDoc()->GetInnerWindow();
7496 aRv
.Throw(NS_ERROR_UNEXPECTED
);
7500 RefPtr
<Promise
> promise
= Promise::Create(win
->AsGlobal(), aRv
);
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
);
7521 nsString
sinkId(aSinkId
);
7522 mediaDevices
->GetSinkDevice(sinkId
)
7524 mAbstractMainThread
, __func__
,
7525 [self
= RefPtr
<HTMLMediaElement
>(this),
7526 this](RefPtr
<AudioDeviceInfo
>&& aInfo
) {
7527 // Sink found switch output device.
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__
);
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
,
7552 return SinkInfoPromise::CreateAndReject(
7553 aValue
.RejectValue(), __func__
);
7557 // No media attached to the element save it for later.
7558 return SinkInfoPromise::CreateAndResolve(aInfo
, __func__
);
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();
7571 switch (aValue
.RejectValue()) {
7572 case NS_ERROR_ABORT
:
7573 promise
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
);
7575 case NS_ERROR_NOT_AVAILABLE
: {
7576 promise
->MaybeRejectWithNotFoundError(
7577 "The object can not be found here.");
7581 MOZ_ASSERT_UNREACHABLE("Invalid error.");
7587 return promise
.forget();
7590 void HTMLMediaElement::NotifyTextTrackModeChanged() {
7591 if (mPendingTextTrackChanged
) {
7594 mPendingTextTrackChanged
= true;
7595 mAbstractMainThread
->Dispatch(
7596 NS_NewRunnableFunction("HTMLMediaElement::NotifyTextTrackModeChanged",
7597 [this, self
= RefPtr
<HTMLMediaElement
>(this)]() {
7598 mPendingTextTrackChanged
= false;
7599 if (!mTextTrackManager
) {
7602 GetTextTracks()->CreateAndDispatchChangeEvent();
7603 // https://html.spec.whatwg.org/multipage/media.html#text-track-model:show-poster-flag
7605 mTextTrackManager
->TimeMarchesOn();
7610 void HTMLMediaElement::CreateResumeDelayedMediaPlaybackAgentIfNeeded() {
7611 if (mResumeDelayedPlaybackAgent
) {
7614 mResumeDelayedPlaybackAgent
=
7615 MediaPlaybackDelayPolicy::CreateResumeDelayedPlaybackAgent(this,
7617 if (!mResumeDelayedPlaybackAgent
) {
7618 LOG(LogLevel::Debug
,
7619 ("%p Failed to create a delayed playback agant", this));
7622 mResumeDelayedPlaybackAgent
->GetResumePromise()
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()) {
7653 mMediaControlKeyListener
->NotifyMediaStoppedPlaying();
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");
7674 MEDIACONTROL_LOG("Not listening because media is real-time");
7678 if (IsBeingUsedInPictureInPictureMode()) {
7679 MEDIACONTROL_LOG("Start listener because of being used in PiP mode");
7683 if (IsInFullScreen()) {
7684 MEDIACONTROL_LOG("Start listener because of being used in fullscreen");
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
7692 StaticPrefs::media_mediacontrol_eligible_media_duration_s()) {
7693 MEDIACONTROL_LOG("Not listening because media's duration %f is too short.",
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
7704 if (!IsAudible() || ComputedVolume() == 0.0f
) {
7705 MEDIACONTROL_LOG("Not listening because media is inaudible");
7711 void HTMLMediaElement::StartMediaControlKeyListenerIfNeeded() {
7712 if (!ShouldStartMediaControlKeyListener()) {
7715 mMediaControlKeyListener
->Start();
7718 void HTMLMediaElement::UpdateStreamName() {
7719 MOZ_ASSERT(NS_IsMainThread());
7721 nsAutoString aTitle
;
7722 OwnerDoc()->GetTitle(aTitle
);
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;
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);
7761 mMediaControlKeyListener
->SetPictureInPictureModeEnabled(false);
7765 bool HTMLMediaElement::IsBeingUsedInPictureInPictureMode() const {
7769 return static_cast<const HTMLVideoElement
*>(this)->IsCloningElementVisually();
7772 void HTMLMediaElement::NodeInfoChanged(Document
* aOldDoc
) {
7774 OwnerDoc()->AddMediaElementWithMSE();
7775 aOldDoc
->RemoveMediaElementWithMSE();
7778 nsGenericHTMLElement::NodeInfoChanged(aOldDoc
);
7781 } // namespace mozilla::dom