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/. */
9 // Some Windows header defines this, so undef it as it conflicts with our
10 // function of the same name.
11 # undef GetCurrentTime
14 #include "mozilla/dom/HTMLMediaElement.h"
15 #include "AudioChannelService.h"
16 #include "AudioDeviceInfo.h"
17 #include "AudioStreamTrack.h"
18 #include "AutoplayPolicy.h"
19 #include "ChannelMediaDecoder.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 #ifdef MOZ_ANDROID_HLS_SUPPORT
27 # include "HLSDecoder.h"
29 #include "HTMLMediaElement.h"
30 #include "ImageContainer.h"
32 #include "MP4Decoder.h"
33 #include "MediaContainerType.h"
34 #include "MediaError.h"
35 #include "MediaManager.h"
36 #include "MediaMetadataManager.h"
37 #include "MediaResource.h"
38 #include "MediaShutdownManager.h"
39 #include "MediaSourceDecoder.h"
40 #include "MediaStreamError.h"
41 #include "MediaStreamGraph.h"
42 #include "MediaStreamListener.h"
43 #include "MediaTrackList.h"
44 #include "SVGObserverUtils.h"
45 #include "TimeRanges.h"
46 #include "VideoFrameContainer.h"
47 #include "VideoOutput.h"
48 #include "VideoStreamTrack.h"
49 #include "base/basictypes.h"
51 #include "mozilla/ArrayUtils.h"
52 #include "mozilla/AsyncEventDispatcher.h"
53 #include "mozilla/EMEUtils.h"
54 #include "mozilla/EventDispatcher.h"
55 #include "mozilla/EventStateManager.h"
56 #include "mozilla/FloatingPoint.h"
57 #include "mozilla/MathAlgorithms.h"
58 #include "mozilla/NotNull.h"
59 #include "mozilla/Preferences.h"
60 #include "mozilla/PresShell.h"
61 #include "mozilla/Sprintf.h"
62 #include "mozilla/StaticPrefs.h"
63 #include "mozilla/Telemetry.h"
64 #include "mozilla/dom/AudioTrack.h"
65 #include "mozilla/dom/AudioTrackList.h"
66 #include "mozilla/dom/BlobURLProtocolHandler.h"
67 #include "mozilla/dom/ElementInlines.h"
68 #include "mozilla/dom/HTMLAudioElement.h"
69 #include "mozilla/dom/HTMLInputElement.h"
70 #include "mozilla/dom/HTMLMediaElementBinding.h"
71 #include "mozilla/dom/HTMLSourceElement.h"
72 #include "mozilla/dom/HTMLVideoElement.h"
73 #include "mozilla/dom/MediaEncryptedEvent.h"
74 #include "mozilla/dom/MediaErrorBinding.h"
75 #include "mozilla/dom/MediaSource.h"
76 #include "mozilla/dom/PlayPromise.h"
77 #include "mozilla/dom/Promise.h"
78 #include "mozilla/dom/TextTrack.h"
79 #include "mozilla/dom/VideoPlaybackQuality.h"
80 #include "mozilla/dom/VideoTrack.h"
81 #include "mozilla/dom/VideoTrackList.h"
82 #include "mozilla/dom/WakeLock.h"
83 #include "mozilla/dom/power/PowerManagerService.h"
84 #include "mozilla/net/UrlClassifierFeatureFactory.h"
85 #include "nsAttrValueInlines.h"
86 #include "nsContentPolicyUtils.h"
87 #include "nsContentUtils.h"
88 #include "nsCycleCollectionParticipant.h"
89 #include "nsDisplayList.h"
90 #include "nsDocShell.h"
92 #include "nsGenericHTMLElement.h"
93 #include "nsGkAtoms.h"
94 #include "nsIAsyncVerifyRedirectCallback.h"
95 #include "nsIAutoplay.h"
96 #include "nsICachingChannel.h"
97 #include "nsICategoryManager.h"
98 #include "nsIClassOfService.h"
99 #include "nsIContentPolicy.h"
100 #include "nsIContentSecurityPolicy.h"
101 #include "nsIDocShell.h"
102 #include "mozilla/dom/Document.h"
103 #include "nsIFrame.h"
104 #include "nsIObserverService.h"
105 #include "nsIPermissionManager.h"
106 #include "nsIRequest.h"
107 #include "nsIScriptError.h"
108 #include "nsIScriptSecurityManager.h"
109 #include "nsISupportsPrimitives.h"
110 #include "nsIThreadInternal.h"
111 #include "nsITimer.h"
112 #include "nsIXPConnect.h"
113 #include "nsJSUtils.h"
114 #include "nsLayoutUtils.h"
115 #include "nsMediaFragmentURIParser.h"
116 #include "nsMimeTypes.h"
117 #include "nsNetUtil.h"
118 #include "nsNodeInfoManager.h"
119 #include "nsPresContext.h"
120 #include "nsQueryObject.h"
123 #include "nsThreadUtils.h"
124 #include "nsURIHashKey.h"
125 #include "nsVideoFrame.h"
126 #include "ReferrerInfo.h"
127 #include "xpcpublic.h"
132 mozilla::LazyLogModule
gMediaElementLog("nsMediaElement");
133 static mozilla::LazyLogModule
gMediaElementEventsLog("nsMediaElementEvents");
135 extern mozilla::LazyLogModule gAutoplayPermissionLog
;
136 #define AUTOPLAY_LOG(msg, ...) \
137 MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
139 #define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg)
140 #define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg)
142 using namespace mozilla::layers
;
143 using mozilla::net::nsMediaFragmentURIParser
;
144 using namespace mozilla::dom::HTMLMediaElement_Binding
;
149 // Number of milliseconds between progress events as defined by spec
150 static const uint32_t PROGRESS_MS
= 350;
152 // Number of milliseconds of no data before a stall event is fired as defined by
154 static const uint32_t STALL_MS
= 3000;
156 // Used by AudioChannel for suppresssing the volume to this ratio.
157 #define FADED_VOLUME_RATIO 0.25
159 // These constants are arbitrary
160 // Minimum playbackRate for a media
161 static const double MIN_PLAYBACKRATE
= 1.0 / 16;
162 // Maximum playbackRate for a media
163 static const double MAX_PLAYBACKRATE
= 16.0;
164 // These are the limits beyonds which SoundTouch does not perform too well and
165 // when speech is hard to understand anyway. Threshold above which audio is
167 static const double THRESHOLD_HIGH_PLAYBACKRATE_AUDIO
= 4.0;
168 // Threshold under which audio is muted
169 static const double THRESHOLD_LOW_PLAYBACKRATE_AUDIO
= 0.25;
171 static double ClampPlaybackRate(double aPlaybackRate
) {
172 MOZ_ASSERT(aPlaybackRate
>= 0.0);
174 if (aPlaybackRate
== 0.0) {
175 return aPlaybackRate
;
177 if (aPlaybackRate
< MIN_PLAYBACKRATE
) {
178 return MIN_PLAYBACKRATE
;
180 if (aPlaybackRate
> MAX_PLAYBACKRATE
) {
181 return MAX_PLAYBACKRATE
;
183 return aPlaybackRate
;
186 // Media error values. These need to match the ones in MediaError.webidl.
187 static const unsigned short MEDIA_ERR_ABORTED
= 1;
188 static const unsigned short MEDIA_ERR_NETWORK
= 2;
189 static const unsigned short MEDIA_ERR_DECODE
= 3;
190 static const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED
= 4;
192 static void ResolvePromisesWithUndefined(
193 const nsTArray
<RefPtr
<PlayPromise
>>& aPromises
) {
194 for (auto& promise
: aPromises
) {
195 promise
->MaybeResolveWithUndefined();
199 static void RejectPromises(const nsTArray
<RefPtr
<PlayPromise
>>& aPromises
,
201 for (auto& promise
: aPromises
) {
202 promise
->MaybeReject(aError
);
206 // Under certain conditions there may be no-one holding references to
207 // a media element from script, DOM parent, etc, but the element may still
208 // fire meaningful events in the future so we can't destroy it yet:
209 // 1) If the element is delaying the load event (or would be, if it were
210 // in a document), then events up to loadeddata or error could be fired,
211 // so we need to stay alive.
212 // 2) If the element is not paused and playback has not ended, then
213 // we will (or might) play, sending timeupdate and ended events and possibly
214 // audio output, so we need to stay alive.
215 // 3) if the element is seeking then we will fire seeking events and possibly
216 // start playing afterward, so we need to stay alive.
217 // 4) If autoplay could start playback in this element (if we got enough data),
218 // then we need to stay alive.
219 // 5) if the element is currently loading, not suspended, and its source is
220 // not a MediaSource, then script might be waiting for progress events or a
221 // 'stalled' or 'suspend' event, so we need to stay alive.
222 // If we're already suspended then (all other conditions being met),
223 // it's OK to just disappear without firing any more events,
224 // since we have the freedom to remain suspended indefinitely. Note
225 // that we could use this 'suspended' loophole to garbage-collect a suspended
226 // element in case 4 even if it had 'autoplay' set, but we choose not to.
227 // If someone throws away all references to a loading 'autoplay' element
228 // sound should still eventually play.
229 // 6) If the source is a MediaSource, most loading events will not fire unless
230 // appendBuffer() is called on a SourceBuffer, in which case something is
231 // already referencing the SourceBuffer, which keeps the associated media
232 // element alive. Further, a MediaSource will never time out the resource
233 // fetch, and so should not keep the media element alive if it is
234 // unreferenced. A pending 'stalled' event keeps the media element alive.
236 // Media elements owned by inactive documents (i.e. documents not contained in
237 // any document viewer) should never hold a self-reference because none of the
238 // above conditions are allowed: the element will stop loading and playing
239 // and never resume loading or playing unless its owner document changes to
240 // an active document (which can only happen if there is an external reference
242 // Media elements with no owner doc should be able to hold a self-reference.
243 // Something native must have created the element and may expect it to
244 // stay alive to play.
246 // It's very important that any change in state which could change the value of
247 // needSelfReference in AddRemoveSelfReference be followed by a call to
248 // AddRemoveSelfReference before this element could die!
249 // It's especially important if needSelfReference would change to 'true',
250 // since if we neglect to add a self-reference, this element might be
251 // garbage collected while there are still event listeners that should
252 // receive events. If we neglect to remove the self-reference then the element
253 // just lives longer than it needs to.
255 class nsMediaEvent
: public Runnable
{
257 explicit nsMediaEvent(const char* aName
, HTMLMediaElement
* aElement
)
260 mLoadID(mElement
->GetCurrentLoadID()) {}
263 NS_IMETHOD
Run() override
= 0;
266 bool IsCancelled() { return mElement
->GetCurrentLoadID() != mLoadID
; }
268 RefPtr
<HTMLMediaElement
> mElement
;
272 class HTMLMediaElement::nsAsyncEventRunner
: public nsMediaEvent
{
277 nsAsyncEventRunner(const nsAString
& aName
, HTMLMediaElement
* aElement
)
278 : nsMediaEvent("HTMLMediaElement::nsAsyncEventRunner", aElement
),
281 NS_IMETHOD
Run() override
{
282 // Silently cancel if our load has been cancelled.
283 if (IsCancelled()) return NS_OK
;
285 return mElement
->DispatchEvent(mName
);
290 * If no error is passed while constructing an instance, the instance will
291 * resolve the passed promises with undefined; otherwise, the instance will
292 * reject the passed promises with the passed error.
294 * The constructor appends the constructed instance into the passed media
295 * element's mPendingPlayPromisesRunners member and once the the runner is run
296 * (whether fulfilled or canceled), it removes itself from
297 * mPendingPlayPromisesRunners.
299 class HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner
300 : public nsMediaEvent
{
301 nsTArray
<RefPtr
<PlayPromise
>> mPromises
;
305 nsResolveOrRejectPendingPlayPromisesRunner(
306 HTMLMediaElement
* aElement
, nsTArray
<RefPtr
<PlayPromise
>>&& aPromises
,
307 nsresult aError
= NS_OK
)
309 "HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner",
311 mPromises(std::move(aPromises
)),
313 mElement
->mPendingPlayPromisesRunners
.AppendElement(this);
316 void ResolveOrReject() {
317 if (NS_SUCCEEDED(mError
)) {
318 ResolvePromisesWithUndefined(mPromises
);
320 RejectPromises(mPromises
, mError
);
324 NS_IMETHOD
Run() override
{
325 if (!IsCancelled()) {
329 mElement
->mPendingPlayPromisesRunners
.RemoveElement(this);
334 class HTMLMediaElement::nsNotifyAboutPlayingRunner
335 : public nsResolveOrRejectPendingPlayPromisesRunner
{
337 nsNotifyAboutPlayingRunner(
338 HTMLMediaElement
* aElement
,
339 nsTArray
<RefPtr
<PlayPromise
>>&& aPendingPlayPromises
)
340 : nsResolveOrRejectPendingPlayPromisesRunner(
341 aElement
, std::move(aPendingPlayPromises
)) {}
343 NS_IMETHOD
Run() override
{
345 mElement
->mPendingPlayPromisesRunners
.RemoveElement(this);
349 mElement
->DispatchEvent(NS_LITERAL_STRING("playing"));
350 return nsResolveOrRejectPendingPlayPromisesRunner::Run();
354 class nsSourceErrorEventRunner
: public nsMediaEvent
{
356 nsCOMPtr
<nsIContent
> mSource
;
359 nsSourceErrorEventRunner(HTMLMediaElement
* aElement
, nsIContent
* aSource
)
360 : nsMediaEvent("dom::nsSourceErrorEventRunner", aElement
),
363 NS_IMETHOD
Run() override
{
364 // Silently cancel if our load has been cancelled.
365 if (IsCancelled()) return NS_OK
;
366 LOG_EVENT(LogLevel::Debug
,
367 ("%p Dispatching simple event source error", mElement
.get()));
368 return nsContentUtils::DispatchTrustedEvent(
369 mElement
->OwnerDoc(), mSource
, NS_LITERAL_STRING("error"),
370 CanBubble::eNo
, Cancelable::eNo
);
375 * This listener observes the first video frame to arrive with a non-empty size,
376 * and renders it to its VideoFrameContainer.
378 class HTMLMediaElement::FirstFrameListener
: public VideoOutput
{
380 FirstFrameListener(VideoFrameContainer
* aContainer
,
381 AbstractThread
* aMainThread
)
382 : VideoOutput(aContainer
, aMainThread
) {
383 MOZ_ASSERT(NS_IsMainThread());
386 // NB that this overrides VideoOutput::NotifyRealtimeTrackData, so we can
387 // filter out all frames but the first one with a real size. This allows us to
388 // later re-use the logic in VideoOutput for rendering that frame.
389 void NotifyRealtimeTrackData(MediaStreamGraph
* aGraph
,
390 StreamTime aTrackOffset
,
391 const MediaSegment
& aMedia
) override
{
392 MOZ_ASSERT(aMedia
.GetType() == MediaSegment::VIDEO
);
394 if (mInitialSizeFound
) {
398 const VideoSegment
& video
= static_cast<const VideoSegment
&>(aMedia
);
399 for (VideoSegment::ConstChunkIterator
c(video
); !c
.IsEnded(); c
.Next()) {
400 if (c
->mFrame
.GetIntrinsicSize() != gfx::IntSize(0, 0)) {
401 mInitialSizeFound
= true;
403 // Pick the first frame and run it through the rendering code.
404 VideoSegment segment
;
405 segment
.AppendFrame(do_AddRef(c
->mFrame
.GetImage()),
406 c
->mFrame
.GetIntrinsicSize(),
407 c
->mFrame
.GetPrincipalHandle(),
408 c
->mFrame
.GetForceBlack(), c
->mTimeStamp
);
409 VideoOutput::NotifyRealtimeTrackData(aGraph
, aTrackOffset
, segment
);
416 // Whether a frame with a concrete size has been received. May only be
417 // accessed on the MSG's appending thread. (this is a direct listener so we
418 // get called by whoever is producing this track's data)
419 bool mInitialSizeFound
= false;
422 class HTMLMediaElement::StreamCaptureTrackSource
423 : public MediaStreamTrackSource
,
424 public MediaStreamTrackSource::Sink
{
426 NS_DECL_ISUPPORTS_INHERITED
427 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StreamCaptureTrackSource
,
428 MediaStreamTrackSource
)
430 StreamCaptureTrackSource(HTMLMediaElement
* aElement
,
431 MediaStreamTrackSource
* aCapturedTrackSource
,
432 DOMMediaStream
* aOwningStream
,
433 TrackID aDestinationTrackID
)
434 : MediaStreamTrackSource(aCapturedTrackSource
->GetPrincipal(),
437 mCapturedTrackSource(aCapturedTrackSource
),
438 mOwningStream(aOwningStream
),
439 mDestinationTrackID(aDestinationTrackID
) {
440 MOZ_ASSERT(mElement
);
441 MOZ_ASSERT(mCapturedTrackSource
);
442 MOZ_ASSERT(mOwningStream
);
443 MOZ_ASSERT(IsTrackIDExplicit(mDestinationTrackID
));
445 mCapturedTrackSource
->RegisterSink(this);
448 void Destroy() override
{
449 if (mCapturedTrackSource
) {
450 mCapturedTrackSource
->UnregisterSink(this);
451 mCapturedTrackSource
= nullptr;
455 MediaSourceEnum
GetMediaSource() const override
{
456 return MediaSourceEnum::Other
;
459 CORSMode
GetCORSMode() const override
{
460 if (!mCapturedTrackSource
) {
461 // This could happen during shutdown.
465 return mCapturedTrackSource
->GetCORSMode();
468 void Stop() override
{
469 if (mElement
&& mElement
->mSrcStream
) {
470 // Only notify if we're still playing the source stream. GC might have
471 // cleared it before the track sources.
472 mElement
->NotifyOutputTrackStopped(mOwningStream
, mDestinationTrackID
);
475 mOwningStream
= nullptr;
481 * Do not keep the track source alive. The source lifetime is controlled by
482 * its associated tracks.
484 bool KeepsSourceAlive() const override
{ return false; }
487 * Do not keep the track source on. It is controlled by its associated tracks.
489 bool Enabled() const override
{ return false; }
491 void Disable() override
{}
493 void Enable() override
{}
495 void PrincipalChanged() override
{
496 if (!mCapturedTrackSource
) {
497 // This could happen during shutdown.
501 mPrincipal
= mCapturedTrackSource
->GetPrincipal();
502 MediaStreamTrackSource::PrincipalChanged();
505 void MutedChanged(bool aNewState
) override
{
506 if (!mCapturedTrackSource
) {
507 // This could happen during shutdown.
511 MediaStreamTrackSource::MutedChanged(aNewState
);
515 virtual ~StreamCaptureTrackSource() = default;
517 RefPtr
<HTMLMediaElement
> mElement
;
518 RefPtr
<MediaStreamTrackSource
> mCapturedTrackSource
;
519 RefPtr
<DOMMediaStream
> mOwningStream
;
520 TrackID mDestinationTrackID
;
523 NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::StreamCaptureTrackSource
,
524 MediaStreamTrackSource
)
525 NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::StreamCaptureTrackSource
,
526 MediaStreamTrackSource
)
527 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
528 HTMLMediaElement::StreamCaptureTrackSource
)
529 NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource
)
530 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::StreamCaptureTrackSource
,
531 MediaStreamTrackSource
, mElement
,
532 mCapturedTrackSource
, mOwningStream
)
535 * There is a reference cycle involving this class: MediaLoadListener
536 * holds a reference to the HTMLMediaElement, which holds a reference
537 * to an nsIChannel, which holds a reference to this listener.
538 * We break the reference cycle in OnStartRequest by clearing mElement.
540 class HTMLMediaElement::MediaLoadListener final
541 : public nsIStreamListener
,
542 public nsIChannelEventSink
,
543 public nsIInterfaceRequestor
,
545 public nsIThreadRetargetableStreamListener
{
546 ~MediaLoadListener() {}
548 NS_DECL_THREADSAFE_ISUPPORTS
549 NS_DECL_NSIREQUESTOBSERVER
550 NS_DECL_NSISTREAMLISTENER
551 NS_DECL_NSICHANNELEVENTSINK
553 NS_DECL_NSIINTERFACEREQUESTOR
554 NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
557 explicit MediaLoadListener(HTMLMediaElement
* aElement
)
558 : mElement(aElement
), mLoadID(aElement
->GetCurrentLoadID()) {
559 MOZ_ASSERT(mElement
, "Must pass an element to call back");
563 RefPtr
<HTMLMediaElement
> mElement
;
564 nsCOMPtr
<nsIStreamListener
> mNextListener
;
565 const uint32_t mLoadID
;
568 NS_IMPL_ISUPPORTS(HTMLMediaElement::MediaLoadListener
, nsIRequestObserver
,
569 nsIStreamListener
, nsIChannelEventSink
, nsIInterfaceRequestor
,
570 nsIObserver
, nsIThreadRetargetableStreamListener
)
573 HTMLMediaElement::MediaLoadListener::Observe(nsISupports
* aSubject
,
575 const char16_t
* aData
) {
576 nsContentUtils::UnregisterShutdownObserver(this);
578 // Clear mElement to break cycle so we don't leak on shutdown
584 HTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest
* aRequest
) {
585 nsContentUtils::UnregisterShutdownObserver(this);
588 // We've been notified by the shutdown observer, and are shutting down.
589 return NS_BINDING_ABORTED
;
592 // Media element playback is not currently supported when recording or
593 // replaying. See bug 1304146.
594 if (recordreplay::IsRecordingOrReplaying()) {
595 mElement
->ReportLoadError("Media elements not available when recording");
596 return NS_ERROR_NOT_AVAILABLE
;
599 // The element is only needed until we've had a chance to call
600 // InitializeDecoderForChannel. So make sure mElement is cleared here.
601 RefPtr
<HTMLMediaElement
> element
;
602 element
.swap(mElement
);
604 AbstractThread::AutoEnter
context(element
->AbstractMainThread());
606 if (mLoadID
!= element
->GetCurrentLoadID()) {
607 // The channel has been cancelled before we had a chance to create
608 // a decoder. Abort, don't dispatch an "error" event, as the new load
609 // may not be in an error state.
610 return NS_BINDING_ABORTED
;
613 // Don't continue to load if the request failed or has been canceled.
615 nsresult rv
= aRequest
->GetStatus(&status
);
616 NS_ENSURE_SUCCESS(rv
, rv
);
617 if (NS_FAILED(status
)) {
619 // Handle media not loading error because source was a tracking URL (or
620 // fingerprinting, cryptomining, etc).
621 // We make a note of this media node by including it in a dedicated
622 // array of blocked tracking nodes under its parent document.
623 if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
625 Document
* ownerDoc
= element
->OwnerDoc();
627 ownerDoc
->AddBlockedNodeByClassifier(element
);
630 element
->NotifyLoadError(
631 nsPrintfCString("%u: %s", uint32_t(status
), "Request failed"));
636 nsCOMPtr
<nsIHttpChannel
> hc
= do_QueryInterface(aRequest
);
638 if (hc
&& NS_SUCCEEDED(hc
->GetRequestSucceeded(&succeeded
)) && !succeeded
) {
639 uint32_t responseStatus
= 0;
640 Unused
<< hc
->GetResponseStatus(&responseStatus
);
641 nsAutoCString statusText
;
642 Unused
<< hc
->GetResponseStatusText(statusText
);
643 element
->NotifyLoadError(
644 nsPrintfCString("%u: %s", responseStatus
, statusText
.get()));
647 code
.AppendInt(responseStatus
);
649 element
->GetCurrentSrc(src
);
650 AutoTArray
<nsString
, 2> params
= {code
, src
};
651 element
->ReportLoadError("MediaLoadHttpError", params
);
652 return NS_BINDING_ABORTED
;
655 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
657 NS_SUCCEEDED(rv
= element
->InitializeDecoderForChannel(
658 channel
, getter_AddRefs(mNextListener
))) &&
660 rv
= mNextListener
->OnStartRequest(aRequest
);
662 // If InitializeDecoderForChannel() returned an error, fire a network error.
663 if (NS_FAILED(rv
) && !mNextListener
) {
664 // Load failed, attempt to load the next candidate resource. If there
665 // are none, this will trigger a MEDIA_ERR_SRC_NOT_SUPPORTED error.
666 element
->NotifyLoadError(NS_LITERAL_CSTRING("Failed to init decoder"));
668 // If InitializeDecoderForChannel did not return a listener (but may
669 // have otherwise succeeded), we abort the connection since we aren't
670 // interested in keeping the channel alive ourselves.
671 rv
= NS_BINDING_ABORTED
;
678 HTMLMediaElement::MediaLoadListener::OnStopRequest(nsIRequest
* aRequest
,
681 return mNextListener
->OnStopRequest(aRequest
, aStatus
);
687 HTMLMediaElement::MediaLoadListener::OnDataAvailable(nsIRequest
* aRequest
,
688 nsIInputStream
* aStream
,
691 if (!mNextListener
) {
693 "Must have a chained listener; OnStartRequest should have "
694 "canceled this request");
695 return NS_BINDING_ABORTED
;
697 return mNextListener
->OnDataAvailable(aRequest
, aStream
, aOffset
, aCount
);
701 HTMLMediaElement::MediaLoadListener::AsyncOnChannelRedirect(
702 nsIChannel
* aOldChannel
, nsIChannel
* aNewChannel
, uint32_t aFlags
,
703 nsIAsyncVerifyRedirectCallback
* cb
) {
704 // TODO is this really correct?? See bug #579329.
706 mElement
->OnChannelRedirect(aOldChannel
, aNewChannel
, aFlags
);
708 nsCOMPtr
<nsIChannelEventSink
> sink
= do_QueryInterface(mNextListener
);
710 return sink
->AsyncOnChannelRedirect(aOldChannel
, aNewChannel
, aFlags
, cb
);
712 cb
->OnRedirectVerifyCallback(NS_OK
);
717 HTMLMediaElement::MediaLoadListener::CheckListenerChain() {
718 MOZ_ASSERT(mNextListener
);
719 nsCOMPtr
<nsIThreadRetargetableStreamListener
> retargetable
=
720 do_QueryInterface(mNextListener
);
722 return retargetable
->CheckListenerChain();
724 return NS_ERROR_NO_INTERFACE
;
728 HTMLMediaElement::MediaLoadListener::GetInterface(const nsIID
& aIID
,
730 return QueryInterface(aIID
, aResult
);
733 void HTMLMediaElement::ReportLoadError(const char* aMsg
,
734 const nsTArray
<nsString
>& aParams
) {
735 ReportToConsole(nsIScriptError::warningFlag
, aMsg
, aParams
);
738 void HTMLMediaElement::ReportToConsole(
739 uint32_t aErrorFlags
, const char* aMsg
,
740 const nsTArray
<nsString
>& aParams
) const {
741 nsContentUtils::ReportToConsole(aErrorFlags
, NS_LITERAL_CSTRING("Media"),
742 OwnerDoc(), nsContentUtils::eDOM_PROPERTIES
,
746 class HTMLMediaElement::AudioChannelAgentCallback final
747 : public nsIAudioChannelAgentCallback
{
749 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
750 NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgentCallback
)
752 explicit AudioChannelAgentCallback(HTMLMediaElement
* aOwner
)
754 mAudioChannelVolume(1.0),
755 mPlayingThroughTheAudioChannel(false),
756 mAudioCapturedByWindow(false),
757 mSuspended(nsISuspendedTypes::NONE_SUSPENDED
),
758 mIsOwnerAudible(IsOwnerAudible()),
761 MaybeCreateAudioChannelAgent();
764 void UpdateAudioChannelPlayingState(bool aForcePlaying
= false) {
765 MOZ_ASSERT(!mIsShutDown
);
766 bool playingThroughTheAudioChannel
=
767 aForcePlaying
|| IsPlayingThroughTheAudioChannel();
769 if (playingThroughTheAudioChannel
!= mPlayingThroughTheAudioChannel
) {
770 if (!MaybeCreateAudioChannelAgent()) {
774 mPlayingThroughTheAudioChannel
= playingThroughTheAudioChannel
;
775 NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel
);
779 bool ShouldResetSuspend() const {
780 // The disposable-pause should be clear after media starts playing.
781 if (!mOwner
->Paused() &&
782 mSuspended
== nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE
) {
786 // If the blocked media is paused, we don't need to resume it. We reset the
787 // mSuspended in order to unregister the agent.
788 if (mOwner
->Paused() && mSuspended
== nsISuspendedTypes::SUSPENDED_BLOCK
) {
795 void NotifyPlayStateChanged() {
796 MOZ_ASSERT(!mIsShutDown
);
797 if (ShouldResetSuspend()) {
798 SetSuspended(nsISuspendedTypes::NONE_SUSPENDED
);
799 NotifyAudioPlaybackChanged(
800 AudioChannelService::AudibleChangedReasons::ePauseStateChanged
);
802 UpdateAudioChannelPlayingState();
805 NS_IMETHODIMP
WindowVolumeChanged(float aVolume
, bool aMuted
) override
{
806 MOZ_ASSERT(mAudioChannelAgent
);
809 AudioChannelService::GetAudioChannelLog(), LogLevel::Debug
,
810 ("HTMLMediaElement::AudioChannelAgentCallback, WindowVolumeChanged, "
811 "this = %p, aVolume = %f, aMuted = %s\n",
812 this, aVolume
, aMuted
? "true" : "false"));
814 if (mAudioChannelVolume
!= aVolume
) {
815 mAudioChannelVolume
= aVolume
;
816 mOwner
->SetVolumeInternal();
819 const uint32_t muted
= mOwner
->mMuted
;
820 if (aMuted
&& !mOwner
->ComputedMuted()) {
821 mOwner
->SetMutedInternal(muted
| MUTED_BY_AUDIO_CHANNEL
);
822 } else if (!aMuted
&& mOwner
->ComputedMuted()) {
823 mOwner
->SetMutedInternal(muted
& ~MUTED_BY_AUDIO_CHANNEL
);
829 NS_IMETHODIMP
WindowSuspendChanged(SuspendTypes aSuspend
) override
{
830 MOZ_ASSERT(mAudioChannelAgent
);
833 AudioChannelService::GetAudioChannelLog(), LogLevel::Debug
,
834 ("HTMLMediaElement::AudioChannelAgentCallback, WindowSuspendChanged, "
835 "this = %p, aSuspend = %s\n",
836 this, SuspendTypeToStr(aSuspend
)));
839 case nsISuspendedTypes::NONE_SUSPENDED
:
842 case nsISuspendedTypes::SUSPENDED_PAUSE
:
843 case nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE
:
844 case nsISuspendedTypes::SUSPENDED_BLOCK
:
847 case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE
:
851 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug
,
852 ("HTMLMediaElement::AudioChannelAgentCallback, "
853 "WindowSuspendChanged, "
854 "this = %p, Error : unknown suspended type!\n",
860 NS_IMETHODIMP
WindowAudioCaptureChanged(bool aCapture
) override
{
861 MOZ_ASSERT(mAudioChannelAgent
);
863 if (mAudioCapturedByWindow
!= aCapture
) {
864 mAudioCapturedByWindow
= aCapture
;
865 AudioCaptureStreamChangeIfNeeded();
870 void AudioCaptureStreamChangeIfNeeded() {
871 MOZ_ASSERT(!mIsShutDown
);
872 if (!IsPlayingStarted()) {
876 if (!mOwner
->HasAudio()) {
880 mOwner
->AudioCaptureStreamChange(mAudioCapturedByWindow
);
883 void NotifyAudioPlaybackChanged(AudibleChangedReasons aReason
) {
884 MOZ_ASSERT(!mIsShutDown
);
885 if (!IsPlayingStarted()) {
889 AudibleState newAudibleState
= IsOwnerAudible();
890 if (mIsOwnerAudible
== newAudibleState
) {
894 mIsOwnerAudible
= newAudibleState
;
895 mAudioChannelAgent
->NotifyStartedAudible(mIsOwnerAudible
, aReason
);
898 bool IsPlaybackBlocked() {
899 MOZ_ASSERT(!mIsShutDown
);
900 // If the tab hasn't been activated yet, the media element in that tab can't
901 // be playback now until the tab goes to foreground first time or user
902 // clicks the unblocking tab icon.
903 if (!IsTabActivated()) {
904 // Even we haven't start playing yet, we still need to notify the audio
905 // channe system because we need to receive the resume notification later.
906 UpdateAudioChannelPlayingState(true /* force to start */);
914 MOZ_ASSERT(!mIsShutDown
);
915 if (mAudioChannelAgent
) {
916 mAudioChannelAgent
->NotifyStoppedPlaying();
917 mAudioChannelAgent
= nullptr;
922 float GetEffectiveVolume() const {
923 MOZ_ASSERT(!mIsShutDown
);
924 return mOwner
->Volume() * mAudioChannelVolume
;
927 SuspendTypes
GetSuspendType() const {
928 MOZ_ASSERT(!mIsShutDown
);
933 ~AudioChannelAgentCallback() { MOZ_ASSERT(mIsShutDown
); };
935 bool MaybeCreateAudioChannelAgent() {
936 if (mAudioChannelAgent
) {
940 mAudioChannelAgent
= new AudioChannelAgent();
942 mAudioChannelAgent
->Init(mOwner
->OwnerDoc()->GetInnerWindow(), this);
943 if (NS_WARN_IF(NS_FAILED(rv
))) {
944 mAudioChannelAgent
= nullptr;
946 AudioChannelService::GetAudioChannelLog(), LogLevel::Debug
,
947 ("HTMLMediaElement::AudioChannelAgentCallback, Fail to initialize "
948 "the audio channel agent, this = %p\n",
956 void NotifyAudioChannelAgent(bool aPlaying
) {
957 MOZ_ASSERT(mAudioChannelAgent
);
960 AudioPlaybackConfig config
;
962 mAudioChannelAgent
->NotifyStartedPlaying(&config
, IsOwnerAudible());
963 if (NS_WARN_IF(NS_FAILED(rv
))) {
967 WindowVolumeChanged(config
.mVolume
, config
.mMuted
);
968 WindowSuspendChanged(config
.mSuspend
);
970 mAudioChannelAgent
->NotifyStoppedPlaying();
974 void SetSuspended(SuspendTypes aSuspend
) {
975 if (mSuspended
== aSuspend
) {
979 MaybeNotifyMediaResumed(aSuspend
);
980 mSuspended
= aSuspend
;
981 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug
,
982 ("HTMLMediaElement::AudioChannelAgentCallback, "
983 "SetAudioChannelSuspended, "
984 "this = %p, aSuspend = %s\n",
985 this, SuspendTypeToStr(aSuspend
)));
989 if (!IsSuspended()) {
990 MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug
,
991 ("HTMLMediaElement::AudioChannelAgentCallback, "
992 "ResumeFromAudioChannel, "
993 "this = %p, don't need to be resumed!\n",
998 SetSuspended(nsISuspendedTypes::NONE_SUSPENDED
);
999 IgnoredErrorResult rv
;
1000 RefPtr
<Promise
> toBeIgnored
= mOwner
->Play(rv
);
1002 toBeIgnored
&& toBeIgnored
->State() == Promise::PromiseState::Rejected
,
1005 NS_WARNING("Not able to resume from AudioChannel.");
1008 NotifyAudioPlaybackChanged(
1009 AudioChannelService::AudibleChangedReasons::ePauseStateChanged
);
1012 void Suspend(SuspendTypes aSuspend
) {
1013 if (IsSuspended()) {
1017 SetSuspended(aSuspend
);
1018 if (aSuspend
== nsISuspendedTypes::SUSPENDED_PAUSE
||
1019 aSuspend
== nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE
) {
1020 IgnoredErrorResult rv
;
1022 if (NS_WARN_IF(rv
.Failed())) {
1026 NotifyAudioPlaybackChanged(
1027 AudioChannelService::AudibleChangedReasons::ePauseStateChanged
);
1031 SetSuspended(nsISuspendedTypes::NONE_SUSPENDED
);
1035 bool IsPlayingStarted() {
1036 if (MaybeCreateAudioChannelAgent()) {
1037 return mAudioChannelAgent
->IsPlayingStarted();
1042 void MaybeNotifyMediaResumed(SuspendTypes aSuspend
) {
1043 // In fennec, we should send the notification when media is resumed from the
1044 // pause-disposable which was called by media control.
1045 if (mSuspended
!= nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE
&&
1046 aSuspend
!= nsISuspendedTypes::NONE_SUSPENDED
) {
1050 if (!IsPlayingStarted()) {
1054 uint64_t windowID
= mAudioChannelAgent
->WindowID();
1055 mOwner
->MainThreadEventTarget()->Dispatch(NS_NewRunnableFunction(
1056 "dom::HTMLMediaElement::AudioChannelAgentCallback::"
1057 "MaybeNotifyMediaResumed",
1058 [windowID
]() -> void {
1059 nsCOMPtr
<nsIObserverService
> observerService
=
1060 services::GetObserverService();
1061 if (NS_WARN_IF(!observerService
)) {
1065 nsCOMPtr
<nsISupportsPRUint64
> wrapper
=
1066 do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID
);
1067 if (NS_WARN_IF(!wrapper
)) {
1071 wrapper
->SetData(windowID
);
1072 observerService
->NotifyObservers(wrapper
, "media-playback-resumed",
1077 bool IsTabActivated() {
1078 if (MaybeCreateAudioChannelAgent()) {
1079 return !mAudioChannelAgent
->ShouldBlockMedia();
1084 bool IsSuspended() const {
1085 return (mSuspended
== nsISuspendedTypes::SUSPENDED_PAUSE
||
1086 mSuspended
== nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE
||
1087 mSuspended
== nsISuspendedTypes::SUSPENDED_BLOCK
);
1090 AudibleState
IsOwnerAudible() const {
1091 // Muted or the volume should not be ~0
1092 if (mOwner
->mMuted
|| (std::fabs(mOwner
->Volume()) <= 1e-7)) {
1093 return mOwner
->HasAudio()
1094 ? AudioChannelService::AudibleState::eMaybeAudible
1095 : AudioChannelService::AudibleState::eNotAudible
;
1099 if (!mOwner
->HasAudio()) {
1100 return AudioChannelService::AudibleState::eNotAudible
;
1103 // Might be audible but not yet.
1104 if (mOwner
->HasAudio() && !mOwner
->mIsAudioTrackAudible
) {
1105 return AudioChannelService::AudibleState::eMaybeAudible
;
1108 // Suspended or paused media doesn't produce any sound.
1109 if (mSuspended
!= nsISuspendedTypes::NONE_SUSPENDED
|| mOwner
->mPaused
) {
1110 return AudioChannelService::AudibleState::eNotAudible
;
1113 return AudioChannelService::AudibleState::eAudible
;
1116 bool IsPlayingThroughTheAudioChannel() const {
1117 // If we have an error, we are not playing.
1118 if (mOwner
->GetError()) {
1122 // We should consider any bfcached page or inactive document as non-playing.
1123 if (!mOwner
->IsActive()) {
1127 // It might be resumed from remote, we should keep the audio channel agent.
1128 if (IsSuspended()) {
1133 if (mOwner
->mPaused
) {
1138 if (!mOwner
->HasAudio()) {
1142 // A loop always is playing
1143 if (mOwner
->HasAttr(kNameSpaceID_None
, nsGkAtoms::loop
)) {
1147 // If we are actually playing...
1148 if (mOwner
->IsCurrentlyPlaying()) {
1152 // If we are playing an external stream.
1153 if (mOwner
->mSrcAttrStream
) {
1160 RefPtr
<AudioChannelAgent
> mAudioChannelAgent
;
1161 HTMLMediaElement
* mOwner
;
1163 // The audio channel volume
1164 float mAudioChannelVolume
;
1165 // Is this media element playing?
1166 bool mPlayingThroughTheAudioChannel
;
1167 // True if the sound is being captured by the window.
1168 bool mAudioCapturedByWindow
;
1169 // We have different kinds of suspended cases,
1170 // - SUSPENDED_PAUSE
1171 // It's used when we temporary lost platform audio focus. MediaElement can
1172 // only be resumed when we gain the audio focus again.
1173 // - SUSPENDED_PAUSE_DISPOSABLE
1174 // It's used when user press the pause button on the remote media-control.
1175 // MediaElement can be resumed by remote media-control or via play().
1176 // - SUSPENDED_BLOCK
1177 // It's used to reduce the power consumption, we won't play the auto-play
1178 // audio/video in the page we have never visited before. MediaElement would
1179 // be resumed when the page is active. See bug647429 for more details.
1180 // - SUSPENDED_STOP_DISPOSABLE
1181 // When we permanently lost platform audio focus, we should stop playing
1182 // and stop the audio channel agent. MediaElement can only be restarted by
1184 SuspendTypes mSuspended
;
1185 // Indicate whether media element is audible for users.
1186 AudibleState mIsOwnerAudible
;
1190 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::AudioChannelAgentCallback
)
1192 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
1193 HTMLMediaElement::AudioChannelAgentCallback
)
1194 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent
)
1195 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1197 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(
1198 HTMLMediaElement::AudioChannelAgentCallback
)
1199 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent
)
1200 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1202 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
1203 HTMLMediaElement::AudioChannelAgentCallback
)
1204 NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback
)
1205 NS_INTERFACE_MAP_END
1207 NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLMediaElement::AudioChannelAgentCallback
)
1208 NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLMediaElement::AudioChannelAgentCallback
)
1210 class HTMLMediaElement::ChannelLoader final
{
1212 NS_INLINE_DECL_REFCOUNTING(ChannelLoader
);
1214 void LoadInternal(HTMLMediaElement
* aElement
) {
1219 // determine what security checks need to be performed in AsyncOpen().
1220 nsSecurityFlags securityFlags
=
1221 aElement
->ShouldCheckAllowOrigin()
1222 ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
1223 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS
;
1225 if (aElement
->GetCORSMode() == CORS_USE_CREDENTIALS
) {
1226 securityFlags
|= nsILoadInfo::SEC_COOKIES_INCLUDE
;
1230 aElement
->IsAnyOfHTMLElements(nsGkAtoms::audio
, nsGkAtoms::video
));
1231 nsContentPolicyType contentPolicyType
=
1232 aElement
->IsHTMLElement(nsGkAtoms::audio
)
1233 ? nsIContentPolicy::TYPE_INTERNAL_AUDIO
1234 : nsIContentPolicy::TYPE_INTERNAL_VIDEO
;
1236 // If aElement has 'triggeringprincipal' attribute, we will use the value as
1237 // triggeringPrincipal for the channel, otherwise it will default to use
1238 // aElement->NodePrincipal().
1239 // This function returns true when aElement has 'triggeringprincipal', so if
1240 // setAttrs is true we will override the origin attributes on the channel
1242 nsCOMPtr
<nsIPrincipal
> triggeringPrincipal
;
1243 bool setAttrs
= nsContentUtils::QueryTriggeringPrincipal(
1244 aElement
, aElement
->mLoadingSrcTriggeringPrincipal
,
1245 getter_AddRefs(triggeringPrincipal
));
1247 nsCOMPtr
<nsILoadGroup
> loadGroup
= aElement
->GetDocumentLoadGroup();
1248 nsCOMPtr
<nsIChannel
> channel
;
1249 nsresult rv
= NS_NewChannelWithTriggeringPrincipal(
1250 getter_AddRefs(channel
), aElement
->mLoadingSrc
,
1251 static_cast<Element
*>(aElement
), triggeringPrincipal
, securityFlags
,
1253 nullptr, // aPerformanceStorage
1255 nullptr, // aCallbacks
1256 nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY
|
1257 nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE
|
1258 nsIChannel::LOAD_CALL_CONTENT_SNIFFERS
);
1260 if (NS_FAILED(rv
)) {
1261 // Notify load error so the element will try next resource candidate.
1262 aElement
->NotifyLoadError(NS_LITERAL_CSTRING("Fail to create channel"));
1267 nsCOMPtr
<nsILoadInfo
> loadInfo
= channel
->LoadInfo();
1268 // The function simply returns NS_OK, so we ignore the return value.
1269 Unused
<< loadInfo
->SetOriginAttributes(
1270 triggeringPrincipal
->OriginAttributesRef());
1273 nsCOMPtr
<nsIClassOfService
> cos(do_QueryInterface(channel
));
1275 if (aElement
->mUseUrgentStartForChannel
) {
1276 cos
->AddClassFlags(nsIClassOfService::UrgentStart
);
1278 // Reset the flag to avoid loading again without initiated by user
1280 aElement
->mUseUrgentStartForChannel
= false;
1283 // Unconditionally disable throttling since we want the media to fluently
1284 // play even when we switch the tab to background.
1285 cos
->AddClassFlags(nsIClassOfService::DontThrottle
);
1288 // The listener holds a strong reference to us. This creates a
1289 // reference cycle, once we've set mChannel, which is manually broken
1290 // in the listener's OnStartRequest method after it is finished with
1291 // the element. The cycle will also be broken if we get a shutdown
1292 // notification before OnStartRequest fires. Necko guarantees that
1293 // OnStartRequest will eventually fire if we don't shut down first.
1294 RefPtr
<MediaLoadListener
> loadListener
= new MediaLoadListener(aElement
);
1296 channel
->SetNotificationCallbacks(loadListener
);
1298 nsCOMPtr
<nsIHttpChannel
> hc
= do_QueryInterface(channel
);
1300 // Use a byte range request from the start of the resource.
1301 // This enables us to detect if the stream supports byte range
1302 // requests, and therefore seeking, early.
1303 rv
= hc
->SetRequestHeader(NS_LITERAL_CSTRING("Range"),
1304 NS_LITERAL_CSTRING("bytes=0-"), false);
1305 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1306 aElement
->SetRequestHeaders(hc
);
1309 rv
= channel
->AsyncOpen(loadListener
);
1310 if (NS_FAILED(rv
)) {
1311 // Notify load error so the element will try next resource candidate.
1312 aElement
->NotifyLoadError(NS_LITERAL_CSTRING("Failed to open channel"));
1316 // Else the channel must be open and starting to download. If it encounters
1317 // a non-catastrophic failure, it will set a new task to continue loading
1318 // another candidate. It's safe to set it as mChannel now.
1321 // loadListener will be unregistered either on shutdown or when
1322 // OnStartRequest for the channel we just opened fires.
1323 nsContentUtils::RegisterShutdownObserver(loadListener
);
1326 nsresult
Load(HTMLMediaElement
* aElement
) {
1327 MOZ_ASSERT(aElement
);
1328 // Per bug 1235183 comment 8, we can't spin the event loop from stable
1329 // state. Defer NS_NewChannel() to a new regular runnable.
1330 return aElement
->MainThreadEventTarget()->Dispatch(
1331 NewRunnableMethod
<HTMLMediaElement
*>("ChannelLoader::LoadInternal",
1332 this, &ChannelLoader::LoadInternal
,
1339 mChannel
->Cancel(NS_BINDING_ABORTED
);
1345 MOZ_ASSERT(mChannel
);
1346 // Decoder successfully created, the decoder now owns the MediaResource
1347 // which owns the channel.
1351 nsresult
Redirect(nsIChannel
* aChannel
, nsIChannel
* aNewChannel
,
1353 NS_ASSERTION(aChannel
== mChannel
, "Channels should match!");
1354 mChannel
= aNewChannel
;
1356 // Handle forwarding of Range header so that the intial detection
1357 // of seeking support (via result code 206) works across redirects.
1358 nsCOMPtr
<nsIHttpChannel
> http
= do_QueryInterface(aChannel
);
1359 NS_ENSURE_STATE(http
);
1361 NS_NAMED_LITERAL_CSTRING(rangeHdr
, "Range");
1363 nsAutoCString rangeVal
;
1364 if (NS_SUCCEEDED(http
->GetRequestHeader(rangeHdr
, rangeVal
))) {
1365 NS_ENSURE_STATE(!rangeVal
.IsEmpty());
1367 http
= do_QueryInterface(aNewChannel
);
1368 NS_ENSURE_STATE(http
);
1370 nsresult rv
= http
->SetRequestHeader(rangeHdr
, rangeVal
, false);
1371 NS_ENSURE_SUCCESS(rv
, rv
);
1378 ~ChannelLoader() { MOZ_ASSERT(!mChannel
); }
1379 // Holds a reference to the first channel we open to the media resource.
1380 // Once the decoder is created, control over the channel passes to the
1381 // decoder, and we null out this reference. We must store this in case
1382 // we need to cancel the channel before control of it passes to the decoder.
1383 nsCOMPtr
<nsIChannel
> mChannel
;
1385 bool mCancelled
= false;
1388 class HTMLMediaElement::ErrorSink
{
1390 explicit ErrorSink(HTMLMediaElement
* aOwner
) : mOwner(aOwner
) {
1394 void SetError(uint16_t aErrorCode
, const nsACString
& aErrorDetails
) {
1395 // Since we have multiple paths calling into DecodeError, e.g.
1396 // MediaKeys::Terminated and EMEH264Decoder::Error. We should take the 1st
1397 // one only in order not to fire multiple 'error' events.
1402 if (!IsValidErrorCode(aErrorCode
)) {
1403 NS_ASSERTION(false, "Undefined MediaError codes!");
1407 mError
= new MediaError(mOwner
, aErrorCode
, aErrorDetails
);
1408 mOwner
->DispatchAsyncEvent(NS_LITERAL_STRING("error"));
1409 if (mOwner
->ReadyState() == HAVE_NOTHING
&&
1410 aErrorCode
== MEDIA_ERR_ABORTED
) {
1411 // https://html.spec.whatwg.org/multipage/embedded-content.html#media-data-processing-steps-list
1412 // "If the media data fetching process is aborted by the user"
1413 mOwner
->DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
1414 mOwner
->ChangeNetworkState(NETWORK_EMPTY
);
1415 mOwner
->DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
1416 if (mOwner
->mDecoder
) {
1417 mOwner
->ShutdownDecoder();
1419 } else if (aErrorCode
== MEDIA_ERR_SRC_NOT_SUPPORTED
) {
1420 mOwner
->ChangeNetworkState(NETWORK_NO_SOURCE
);
1422 mOwner
->ChangeNetworkState(NETWORK_IDLE
);
1426 void ResetError() { mError
= nullptr; }
1428 RefPtr
<MediaError
> mError
;
1431 bool IsValidErrorCode(const uint16_t& aErrorCode
) const {
1432 return (aErrorCode
== MEDIA_ERR_DECODE
|| aErrorCode
== MEDIA_ERR_NETWORK
||
1433 aErrorCode
== MEDIA_ERR_ABORTED
||
1434 aErrorCode
== MEDIA_ERR_SRC_NOT_SUPPORTED
);
1437 // Media elememt's life cycle would be longer than error sink, so we use the
1438 // raw pointer and this class would only be referenced by media element.
1439 HTMLMediaElement
* mOwner
;
1442 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement
)
1444 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement
,
1445 nsGenericHTMLElement
)
1446 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource
)
1447 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcMediaSource
)
1448 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcStream
)
1449 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcAttrStream
)
1450 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourcePointer
)
1451 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadBlockedDoc
)
1452 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceLoadCandidate
)
1453 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelWrapper
)
1454 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mErrorSink
->mError
)
1455 for (uint32_t i
= 0; i
< tmp
->mOutputStreams
.Length(); ++i
) {
1456 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams
[i
].mStream
)
1458 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed
);
1459 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager
)
1460 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList
)
1461 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList
)
1462 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys
)
1463 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingMediaKeys
)
1464 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack
)
1465 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayPromises
)
1466 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSeekDOMPromise
)
1467 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSetMediaKeysDOMPromise
)
1468 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1470 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement
,
1471 nsGenericHTMLElement
)
1472 tmp
->RemoveMutationObserver(tmp
);
1473 if (tmp
->mSrcStream
) {
1474 // Need to EndMediaStreamPlayback to clear mSrcStream and make sure
1475 // everything gets unhooked correctly.
1476 tmp
->EndSrcMediaStreamPlayback();
1478 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcAttrStream
)
1479 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource
)
1480 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcMediaSource
)
1481 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourcePointer
)
1482 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoadBlockedDoc
)
1483 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate
)
1484 if (tmp
->mAudioChannelWrapper
) {
1485 tmp
->mAudioChannelWrapper
->Shutdown();
1487 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelWrapper
)
1488 NS_IMPL_CYCLE_COLLECTION_UNLINK(mErrorSink
->mError
)
1489 for (OutputMediaStream
& s
: tmp
->mOutputStreams
) {
1490 s
.mStream
->SetFinishedOnInactive(true);
1492 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams
)
1493 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed
)
1494 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager
)
1495 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList
)
1496 NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList
)
1497 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys
)
1498 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingMediaKeys
)
1499 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack
)
1500 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayPromises
)
1501 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSeekDOMPromise
)
1502 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetMediaKeysDOMPromise
)
1503 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1505 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLMediaElement
,
1506 nsGenericHTMLElement
)
1508 void HTMLMediaElement::ContentRemoved(nsIContent
* aChild
,
1509 nsIContent
* aPreviousSibling
) {
1510 if (aChild
== mSourcePointer
) {
1511 mSourcePointer
= aPreviousSibling
;
1515 already_AddRefed
<MediaSource
> HTMLMediaElement::GetMozMediaSourceObject()
1517 RefPtr
<MediaSource
> source
= mMediaSource
;
1518 return source
.forget();
1521 already_AddRefed
<Promise
> HTMLMediaElement::MozRequestDebugInfo(
1523 RefPtr
<Promise
> promise
= CreateDOMPromise(aRv
);
1524 if (NS_WARN_IF(aRv
.Failed())) {
1527 auto result
= MakeUnique
<dom::HTMLMediaElementDebugInfo
>();
1529 GetEMEInfo(result
->mEMEInfo
);
1531 if (mVideoFrameContainer
) {
1532 result
->mCompositorDroppedFrames
=
1533 mVideoFrameContainer
->GetDroppedImageCount();
1536 mDecoder
->RequestDebugInfo(result
->mDecoder
)
1538 mAbstractMainThread
, __func__
,
1539 [promise
, ptr
= std::move(result
)]() {
1540 promise
->MaybeResolve(ptr
.get());
1542 []() { UNREACHABLE(); });
1544 promise
->MaybeResolve(result
.get());
1546 return promise
.forget();
1550 void HTMLMediaElement::MozEnableDebugLog(const GlobalObject
&) {
1551 DecoderDoctorLogger::EnableLogging();
1554 already_AddRefed
<Promise
> HTMLMediaElement::MozRequestDebugLog(
1556 RefPtr
<Promise
> promise
= CreateDOMPromise(aRv
);
1557 if (NS_WARN_IF(aRv
.Failed())) {
1561 DecoderDoctorLogger::RetrieveMessages(this)->Then(
1562 mAbstractMainThread
, __func__
,
1563 [promise
](const nsACString
& aString
) {
1564 promise
->MaybeResolve(NS_ConvertUTF8toUTF16(aString
));
1566 [promise
](nsresult rv
) { promise
->MaybeReject(rv
); });
1568 return promise
.forget();
1571 void HTMLMediaElement::SetVisible(bool aVisible
) {
1572 mForcedHidden
= !aVisible
;
1574 mDecoder
->SetForcedHidden(!aVisible
);
1578 bool HTMLMediaElement::IsVideoDecodingSuspended() const {
1579 return mDecoder
&& mDecoder
->IsVideoDecodingSuspended();
1582 bool HTMLMediaElement::IsVisible() const {
1583 return mVisibilityState
== Visibility::ApproximatelyVisible
;
1586 already_AddRefed
<layers::Image
> HTMLMediaElement::GetCurrentImage() {
1589 // TODO: In bug 1345404, handle case when video decoder is already suspended.
1590 ImageContainer
* container
= GetImageContainer();
1595 AutoLockImage
lockImage(container
);
1596 RefPtr
<layers::Image
> image
= lockImage
.GetImage(TimeStamp::Now());
1597 return image
.forget();
1600 bool HTMLMediaElement::HasSuspendTaint() const {
1601 MOZ_ASSERT(!mDecoder
|| (mDecoder
->HasSuspendTaint() == mHasSuspendTaint
));
1602 return mHasSuspendTaint
;
1605 already_AddRefed
<DOMMediaStream
> HTMLMediaElement::GetSrcObject() const {
1606 NS_ASSERTION(!mSrcAttrStream
|| mSrcAttrStream
->GetPlaybackStream(),
1607 "MediaStream should have been set up properly");
1608 RefPtr
<DOMMediaStream
> stream
= mSrcAttrStream
;
1609 return stream
.forget();
1612 void HTMLMediaElement::SetSrcObject(DOMMediaStream
& aValue
) {
1613 SetSrcObject(&aValue
);
1616 void HTMLMediaElement::SetSrcObject(DOMMediaStream
* aValue
) {
1617 mSrcAttrStream
= aValue
;
1618 UpdateAudioChannelPlayingState();
1622 bool HTMLMediaElement::Ended() {
1623 return (mDecoder
&& mDecoder
->IsEnded()) ||
1624 (mSrcStream
&& mSrcStreamPlaybackEnded
);
1627 void HTMLMediaElement::GetCurrentSrc(nsAString
& aCurrentSrc
) {
1629 GetCurrentSpec(src
);
1630 CopyUTF8toUTF16(src
, aCurrentSrc
);
1633 nsresult
HTMLMediaElement::OnChannelRedirect(nsIChannel
* aChannel
,
1634 nsIChannel
* aNewChannel
,
1636 MOZ_ASSERT(mChannelLoader
);
1637 return mChannelLoader
->Redirect(aChannel
, aNewChannel
, aFlags
);
1640 void HTMLMediaElement::ShutdownDecoder() {
1641 RemoveMediaElementFromURITable();
1642 NS_ASSERTION(mDecoder
, "Must have decoder to shut down");
1644 mWaitingForKeyListener
.DisconnectIfExists();
1646 mMediaSource
->CompletePendingTransactions();
1648 if (!mOutputStreams
.IsEmpty()) {
1649 mNextAvailableMediaDecoderOutputTrackID
=
1650 mDecoder
->GetNextOutputStreamTrackID();
1652 mDecoder
->Shutdown();
1653 DDUNLINKCHILD(mDecoder
.get());
1655 ReportAudioTrackSilenceProportionTelemetry();
1658 void HTMLMediaElement::ReportPlayedTimeAfterBlockedTelemetry() {
1659 if (!mHasPlayEverBeenBlocked
) {
1662 mHasPlayEverBeenBlocked
= false;
1664 const double playTimeThreshold
= 7.0;
1665 const double playTimeAfterBlocked
= mCurrentLoadPlayTime
.Total();
1666 if (playTimeAfterBlocked
<= 0.0) {
1670 const bool isDurationLessThanTimeThresholdAndMediaPlayedToTheEnd
=
1671 Duration() < playTimeThreshold
&& Ended();
1672 LOG(LogLevel::Debug
, ("%p PLAYED_TIME_AFTER_AUTOPLAY_BLOCKED=%f, isVideo=%d",
1673 this, playTimeAfterBlocked
, IsVideo()));
1674 if (IsVideo() && playTimeAfterBlocked
>= playTimeThreshold
) {
1675 AccumulateCategorical(
1676 mozilla::Telemetry::LABELS_MEDIA_PLAYED_TIME_AFTER_AUTOPLAY_BLOCKED::
1678 } else if (IsVideo() && playTimeAfterBlocked
< playTimeThreshold
) {
1679 if (isDurationLessThanTimeThresholdAndMediaPlayedToTheEnd
) {
1680 AccumulateCategorical(
1681 mozilla::Telemetry::LABELS_MEDIA_PLAYED_TIME_AFTER_AUTOPLAY_BLOCKED::
1684 AccumulateCategorical(
1685 mozilla::Telemetry::LABELS_MEDIA_PLAYED_TIME_AFTER_AUTOPLAY_BLOCKED::
1688 } else if (!IsVideo() && playTimeAfterBlocked
>= playTimeThreshold
) {
1689 AccumulateCategorical(
1690 mozilla::Telemetry::LABELS_MEDIA_PLAYED_TIME_AFTER_AUTOPLAY_BLOCKED::
1692 } else if (!IsVideo() && playTimeAfterBlocked
< playTimeThreshold
) {
1693 if (isDurationLessThanTimeThresholdAndMediaPlayedToTheEnd
) {
1694 AccumulateCategorical(
1695 mozilla::Telemetry::LABELS_MEDIA_PLAYED_TIME_AFTER_AUTOPLAY_BLOCKED::
1698 AccumulateCategorical(
1699 mozilla::Telemetry::LABELS_MEDIA_PLAYED_TIME_AFTER_AUTOPLAY_BLOCKED::
1705 void HTMLMediaElement::AbortExistingLoads() {
1706 // Abort any already-running instance of the resource selection algorithm.
1707 mLoadWaitStatus
= NOT_WAITING
;
1709 // Set a new load ID. This will cause events which were enqueued
1710 // with a different load ID to silently be cancelled.
1713 // Immediately reject or resolve the already-dispatched
1714 // nsResolveOrRejectPendingPlayPromisesRunners. These runners won't be
1715 // executed again later since the mCurrentLoadID had been changed.
1716 for (auto& runner
: mPendingPlayPromisesRunners
) {
1717 runner
->ResolveOrReject();
1719 mPendingPlayPromisesRunners
.Clear();
1721 if (mChannelLoader
) {
1722 mChannelLoader
->Cancel();
1723 mChannelLoader
= nullptr;
1726 bool fireTimeUpdate
= false;
1728 // We need to remove FirstFrameListener before VideoTracks get emptied.
1729 if (mFirstFrameListener
) {
1730 mSelectedVideoStreamTrack
->RemoveVideoOutput(mFirstFrameListener
);
1731 mFirstFrameListener
= nullptr;
1734 // When aborting the existing loads, empty the objects in audio track list and
1735 // video track list, no events (in particular, no removetrack events) are
1736 // fired as part of this. Ending MediaStream sends track ended notifications,
1737 // so we empty the track lists prior.
1738 if (AudioTracks()) {
1739 AudioTracks()->EmptyTracks();
1741 if (VideoTracks()) {
1742 VideoTracks()->EmptyTracks();
1746 fireTimeUpdate
= mDecoder
->GetCurrentTime() != 0.0;
1750 EndSrcMediaStreamPlayback();
1753 RemoveMediaElementFromURITable();
1754 mLoadingSrc
= nullptr;
1755 mLoadingSrcTriggeringPrincipal
= nullptr;
1756 DDLOG(DDLogCategory::Property
, "loading_src", "");
1757 DDUNLINKCHILD(mMediaSource
.get());
1758 mMediaSource
= nullptr;
1760 if (mNetworkState
== NETWORK_LOADING
|| mNetworkState
== NETWORK_IDLE
) {
1761 DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
1764 bool hadVideo
= HasVideo();
1765 mErrorSink
->ResetError();
1766 mCurrentPlayRangeStart
= -1.0;
1767 mLoadedDataFired
= false;
1768 mAutoplaying
= true;
1769 mIsLoadingFromSourceChildren
= false;
1770 mSuspendedAfterFirstFrame
= false;
1771 mAllowSuspendAfterFirstFrame
= true;
1772 mHaveQueuedSelectResource
= false;
1773 mSuspendedForPreloadNone
= false;
1774 mDownloadSuspendedByCache
= false;
1775 mMediaInfo
= MediaInfo();
1776 mIsEncrypted
= false;
1777 mPendingEncryptedInitData
.Reset();
1778 mWaitingForKey
= NOT_WAITING_FOR_KEY
;
1779 mSourcePointer
= nullptr;
1780 mBlockedAsWithoutMetadata
= false;
1783 mAudioTrackSilenceStartedTime
= 0.0;
1785 if (mNetworkState
!= NETWORK_EMPTY
) {
1786 NS_ASSERTION(!mDecoder
&& !mSrcStream
,
1787 "How did someone setup a new stream/decoder already?");
1788 // ChangeNetworkState() will call UpdateAudioChannelPlayingState()
1789 // indirectly which depends on mPaused. So we need to update mPaused first.
1792 RejectPromises(TakePendingPlayPromises(), NS_ERROR_DOM_MEDIA_ABORT_ERR
);
1794 ChangeNetworkState(NETWORK_EMPTY
);
1795 ChangeReadyState(HAVE_NOTHING
);
1797 // TODO: Apply the rules for text track cue rendering Bug 865407
1798 if (mTextTrackManager
) {
1799 mTextTrackManager
->GetTextTracks()->SetCuesInactive();
1802 if (fireTimeUpdate
) {
1803 // Since we destroyed the decoder above, the current playback position
1804 // will now be reported as 0. The playback position was non-zero when
1805 // we destroyed the decoder, so fire a timeupdate event so that the
1806 // change will be reflected in the controls.
1807 FireTimeUpdate(false);
1809 DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
1810 UpdateAudioChannelPlayingState();
1813 if (IsVideo() && hadVideo
) {
1814 // Ensure we render transparent black after resetting video resolution.
1815 Maybe
<nsIntSize
> size
= Some(nsIntSize(0, 0));
1816 Invalidate(true, size
, false);
1819 // We may have changed mPaused, mAutoplaying, and other
1820 // things which can affect AddRemoveSelfReference
1821 AddRemoveSelfReference();
1823 mIsRunningSelectResource
= false;
1825 mEventDeliveryPaused
= false;
1826 mPendingEvents
.Clear();
1827 mCurrentLoadPlayTime
.Reset();
1829 AssertReadyStateIsNothing();
1832 void HTMLMediaElement::NoSupportedMediaSourceError(
1833 const nsACString
& aErrorDetails
) {
1837 mErrorSink
->SetError(MEDIA_ERR_SRC_NOT_SUPPORTED
, aErrorDetails
);
1838 ChangeDelayLoadStatus(false);
1839 UpdateAudioChannelPlayingState();
1840 RejectPromises(TakePendingPlayPromises(),
1841 NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR
);
1844 typedef void (HTMLMediaElement::*SyncSectionFn
)();
1846 // Runs a "synchronous section", a function that must run once the event loop
1847 // has reached a "stable state". See:
1848 // http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
1849 class nsSyncSection
: public nsMediaEvent
{
1851 nsCOMPtr
<nsIRunnable
> mRunnable
;
1854 nsSyncSection(HTMLMediaElement
* aElement
, nsIRunnable
* aRunnable
)
1855 : nsMediaEvent("dom::nsSyncSection", aElement
), mRunnable(aRunnable
) {}
1857 NS_IMETHOD
Run() override
{
1858 // Silently cancel if our load has been cancelled.
1859 if (IsCancelled()) return NS_OK
;
1865 void HTMLMediaElement::RunInStableState(nsIRunnable
* aRunnable
) {
1866 if (mShuttingDown
) {
1870 nsCOMPtr
<nsIRunnable
> event
= new nsSyncSection(this, aRunnable
);
1871 nsContentUtils::RunInStableState(event
.forget());
1874 void HTMLMediaElement::QueueLoadFromSourceTask() {
1875 if (!mIsLoadingFromSourceChildren
|| mShuttingDown
) {
1880 // Reset readyState to HAVE_NOTHING since we're going to load a new decoder.
1882 ChangeReadyState(HAVE_NOTHING
);
1885 AssertReadyStateIsNothing();
1887 ChangeDelayLoadStatus(true);
1888 ChangeNetworkState(NETWORK_LOADING
);
1889 RefPtr
<Runnable
> r
=
1890 NewRunnableMethod("HTMLMediaElement::LoadFromSourceChildren", this,
1891 &HTMLMediaElement::LoadFromSourceChildren
);
1892 RunInStableState(r
);
1895 void HTMLMediaElement::QueueSelectResourceTask() {
1896 // Don't allow multiple async select resource calls to be queued.
1897 if (mHaveQueuedSelectResource
) return;
1898 mHaveQueuedSelectResource
= true;
1899 ChangeNetworkState(NETWORK_NO_SOURCE
);
1900 RefPtr
<Runnable
> r
=
1901 NewRunnableMethod("HTMLMediaElement::SelectResourceWrapper", this,
1902 &HTMLMediaElement::SelectResourceWrapper
);
1903 RunInStableState(r
);
1906 static bool HasSourceChildren(nsIContent
* aElement
) {
1907 for (nsIContent
* child
= aElement
->GetFirstChild(); child
;
1908 child
= child
->GetNextSibling()) {
1909 if (child
->IsHTMLElement(nsGkAtoms::source
)) {
1916 static nsCString
DocumentOrigin(Document
* aDoc
) {
1918 return NS_LITERAL_CSTRING("null");
1920 nsCOMPtr
<nsIPrincipal
> principal
= aDoc
->NodePrincipal();
1922 return NS_LITERAL_CSTRING("null");
1925 if (NS_FAILED(principal
->GetOrigin(origin
))) {
1926 return NS_LITERAL_CSTRING("null");
1931 void HTMLMediaElement::Load() {
1932 LOG(LogLevel::Debug
,
1933 ("%p Load() hasSrcAttrStream=%d hasSrcAttr=%d hasSourceChildren=%d "
1934 "handlingInput=%d hasAutoplayAttr=%d IsAllowedToPlay=%d "
1935 "ownerDoc=%p (%s) ownerDocUserActivated=%d "
1936 "muted=%d volume=%f",
1937 this, !!mSrcAttrStream
, HasAttr(kNameSpaceID_None
, nsGkAtoms::src
),
1938 HasSourceChildren(this), EventStateManager::IsHandlingUserInput(),
1939 HasAttr(kNameSpaceID_None
, nsGkAtoms::autoplay
),
1940 AutoplayPolicy::IsAllowedToPlay(*this), OwnerDoc(),
1941 DocumentOrigin(OwnerDoc()).get(),
1942 OwnerDoc() ? OwnerDoc()->HasBeenUserGestureActivated() : 0, mMuted
,
1945 if (mIsRunningLoadMethod
) {
1949 mIsDoingExplicitLoad
= true;
1953 void HTMLMediaElement::DoLoad() {
1954 // Check if media is allowed for the docshell.
1955 nsCOMPtr
<nsIDocShell
> docShell
= OwnerDoc()->GetDocShell();
1956 if (docShell
&& !docShell
->GetAllowMedia()) {
1957 LOG(LogLevel::Debug
, ("%p Media not allowed", this));
1961 if (mIsRunningLoadMethod
) {
1965 if (EventStateManager::IsHandlingUserInput()) {
1966 // Detect if user has interacted with element so that play will not be
1967 // blocked when initiated by a script. This enables sites to capture user
1968 // intent to play by calling load() in the click handler of a "catalog
1969 // view" of a gallery of videos.
1971 // Mark the channel as urgent-start when autopaly so that it will play the
1972 // media from src after loading enough resource.
1973 if (HasAttr(kNameSpaceID_None
, nsGkAtoms::autoplay
)) {
1974 mUseUrgentStartForChannel
= true;
1978 SetPlayedOrSeeked(false);
1979 mIsRunningLoadMethod
= true;
1980 AbortExistingLoads();
1981 SetPlaybackRate(mDefaultPlaybackRate
, IgnoreErrors());
1982 QueueSelectResourceTask();
1984 mIsRunningLoadMethod
= false;
1987 void HTMLMediaElement::ResetState() {
1988 // There might be a pending MediaDecoder::PlaybackPositionChanged() which
1989 // will overwrite |mMediaInfo.mVideo.mDisplay| in UpdateMediaSize() to give
1990 // staled videoWidth and videoHeight. We have to call ForgetElement() here
1991 // such that the staled callbacks won't reach us.
1992 if (mVideoFrameContainer
) {
1993 mVideoFrameContainer
->ForgetElement();
1994 mVideoFrameContainer
= nullptr;
1998 void HTMLMediaElement::SelectResourceWrapper() {
2000 MaybeBeginCloningVisually();
2001 mIsRunningSelectResource
= false;
2002 mHaveQueuedSelectResource
= false;
2003 mIsDoingExplicitLoad
= false;
2006 void HTMLMediaElement::SelectResource() {
2007 if (!mSrcAttrStream
&& !HasAttr(kNameSpaceID_None
, nsGkAtoms::src
) &&
2008 !HasSourceChildren(this)) {
2009 // The media element has neither a src attribute nor any source
2010 // element children, abort the load.
2011 ChangeNetworkState(NETWORK_EMPTY
);
2012 ChangeDelayLoadStatus(false);
2016 ChangeDelayLoadStatus(true);
2018 ChangeNetworkState(NETWORK_LOADING
);
2019 DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
2021 // Delay setting mIsRunningSeletResource until after UpdatePreloadAction
2022 // so that we don't lose our state change by bailing out of the preload
2024 UpdatePreloadAction();
2025 mIsRunningSelectResource
= true;
2027 // If we have a 'src' attribute, use that exclusively.
2029 if (mSrcAttrStream
) {
2030 SetupSrcMediaStreamPlayback(mSrcAttrStream
);
2031 } else if (GetAttr(kNameSpaceID_None
, nsGkAtoms::src
, src
)) {
2032 nsCOMPtr
<nsIURI
> uri
;
2033 MediaResult rv
= NewURIFromString(src
, getter_AddRefs(uri
));
2034 if (NS_SUCCEEDED(rv
)) {
2035 LOG(LogLevel::Debug
, ("%p Trying load from src=%s", this,
2036 NS_ConvertUTF16toUTF8(src
).get()));
2038 !mIsLoadingFromSourceChildren
,
2039 "Should think we're not loading from source children by default");
2041 RemoveMediaElementFromURITable();
2043 mLoadingSrcTriggeringPrincipal
= mSrcAttrTriggeringPrincipal
;
2044 DDLOG(DDLogCategory::Property
, "loading_src",
2045 nsCString(NS_ConvertUTF16toUTF8(src
)));
2046 mMediaSource
= mSrcMediaSource
;
2047 DDLINKCHILD("mediasource", mMediaSource
.get());
2048 UpdatePreloadAction();
2049 if (mPreloadAction
== HTMLMediaElement::PRELOAD_NONE
&& !mMediaSource
) {
2050 // preload:none media, suspend the load here before we make any
2051 // network requests.
2056 rv
= LoadResource();
2057 if (NS_SUCCEEDED(rv
)) {
2061 AutoTArray
<nsString
, 1> params
= {src
};
2062 ReportLoadError("MediaLoadInvalidURI", params
);
2063 rv
= MediaResult(rv
.Code(), "MediaLoadInvalidURI");
2065 // The media element has neither a src attribute nor a source element child:
2066 // set the networkState to NETWORK_EMPTY, and abort these steps; the
2067 // synchronous section ends.
2068 mMainThreadEventTarget
->Dispatch(NewRunnableMethod
<nsCString
>(
2069 "HTMLMediaElement::NoSupportedMediaSourceError", this,
2070 &HTMLMediaElement::NoSupportedMediaSourceError
, rv
.Description()));
2072 // Otherwise, the source elements will be used.
2073 mIsLoadingFromSourceChildren
= true;
2074 LoadFromSourceChildren();
2078 void HTMLMediaElement::NotifyLoadError(const nsACString
& aErrorDetails
) {
2079 if (!mIsLoadingFromSourceChildren
) {
2080 LOG(LogLevel::Debug
, ("NotifyLoadError(), no supported media error"));
2081 NoSupportedMediaSourceError(aErrorDetails
);
2082 } else if (mSourceLoadCandidate
) {
2083 DispatchAsyncSourceError(mSourceLoadCandidate
);
2084 QueueLoadFromSourceTask();
2086 NS_WARNING("Should know the source we were loading from!");
2090 void HTMLMediaElement::NotifyMediaTrackEnabled(MediaTrack
* aTrack
) {
2099 LOG(LogLevel::Debug
, ("MediaElement %p %sTrack with id %s enabled", this,
2100 aTrack
->AsAudioTrack() ? "Audio" : "Video",
2101 NS_ConvertUTF16toUTF8(id
).get()));
2104 MOZ_ASSERT((aTrack
->AsAudioTrack() && aTrack
->AsAudioTrack()->Enabled()) ||
2105 (aTrack
->AsVideoTrack() && aTrack
->AsVideoTrack()->Selected()));
2107 if (aTrack
->AsAudioTrack()) {
2108 SetMutedInternal(mMuted
& ~MUTED_BY_AUDIO_TRACK
);
2109 } else if (aTrack
->AsVideoTrack()) {
2114 mDisableVideo
= false;
2116 MOZ_ASSERT(false, "Unknown track type");
2120 if (aTrack
->AsVideoTrack()) {
2121 MOZ_ASSERT(!mSelectedVideoStreamTrack
);
2123 mSelectedVideoStreamTrack
= aTrack
->AsVideoTrack()->GetVideoStreamTrack();
2124 VideoFrameContainer
* container
= GetVideoFrameContainer();
2126 HTMLVideoElement
* self
= static_cast<HTMLVideoElement
*>(this);
2127 if (mSrcStreamIsPlaying
) {
2128 mSelectedVideoStreamTrack
->AddVideoOutput(container
);
2129 MaybeBeginCloningVisually();
2130 } else if (self
->VideoWidth() <= 1 && self
->VideoHeight() <= 1) {
2131 // MediaInfo uses dummy values of 1 for width and height to
2132 // mark video as valid. We need a new first-frame listener
2133 // if size is 0x0 or 1x1.
2134 if (!mFirstFrameListener
) {
2135 mFirstFrameListener
=
2136 new FirstFrameListener(container
, mAbstractMainThread
);
2138 mSelectedVideoStreamTrack
->AddVideoOutput(mFirstFrameListener
);
2143 if (mReadyState
== HAVE_NOTHING
) {
2144 // No MediaStreamTracks are captured until we have metadata.
2147 for (OutputMediaStream
& ms
: mOutputStreams
) {
2148 if (aTrack
->AsVideoTrack() && ms
.mCapturingAudioOnly
) {
2149 // If the output stream is for audio only we ignore video tracks.
2152 AddCaptureMediaTrackToOutputStream(aTrack
, ms
);
2157 void HTMLMediaElement::NotifyMediaTrackDisabled(MediaTrack
* aTrack
) {
2166 LOG(LogLevel::Debug
, ("MediaElement %p %sTrack with id %s disabled", this,
2167 aTrack
->AsAudioTrack() ? "Audio" : "Video",
2168 NS_ConvertUTF16toUTF8(id
).get()));
2171 MOZ_ASSERT((!aTrack
->AsAudioTrack() || !aTrack
->AsAudioTrack()->Enabled()) &&
2172 (!aTrack
->AsVideoTrack() || !aTrack
->AsVideoTrack()->Selected()));
2174 if (aTrack
->AsAudioTrack()) {
2175 // If we don't have any alive track , we don't need to mute MediaElement.
2176 MOZ_DIAGNOSTIC_ASSERT(AudioTracks(), "Element can't have been unlinked");
2177 if (AudioTracks()->Length() > 0) {
2178 bool shouldMute
= true;
2179 for (uint32_t i
= 0; i
< AudioTracks()->Length(); ++i
) {
2180 if ((*AudioTracks())[i
]->Enabled()) {
2187 SetMutedInternal(mMuted
| MUTED_BY_AUDIO_TRACK
);
2190 } else if (aTrack
->AsVideoTrack()) {
2192 if (mFirstFrameListener
) {
2193 mSelectedVideoStreamTrack
->RemoveVideoOutput(mFirstFrameListener
);
2194 mFirstFrameListener
= nullptr;
2197 VideoFrameContainer
* container
= GetVideoFrameContainer();
2198 if (mSrcStreamIsPlaying
&& container
) {
2199 mSelectedVideoStreamTrack
->RemoveVideoOutput(container
);
2201 mSelectedVideoStreamTrack
= nullptr;
2205 if (mReadyState
== HAVE_NOTHING
) {
2206 // No MediaStreamTracks are captured until we have metadata, and code
2207 // below doesn't do anything for captured decoders.
2211 for (OutputMediaStream
& ms
: mOutputStreams
) {
2212 if (ms
.mCapturingDecoder
) {
2213 MOZ_ASSERT(!ms
.mCapturingMediaStream
);
2216 if (ms
.mCapturingAudioOnly
&& aTrack
->AsVideoTrack()) {
2219 MOZ_ASSERT(ms
.mCapturingMediaStream
);
2220 for (int32_t i
= ms
.mTrackPorts
.Length() - 1; i
>= 0; --i
) {
2221 if (ms
.mTrackPorts
[i
].first() == aTrack
->GetId()) {
2222 // The source of this track just ended. Force-notify that it ended.
2223 // If we bounce it to the MediaStreamGraph it might not be picked up,
2224 // for instance if the MediaInputPort was destroyed in the same
2225 // iteration as it was added.
2226 MediaStreamTrack
* outputTrack
= ms
.mStream
->FindOwnedDOMTrack(
2227 ms
.mTrackPorts
[i
].second()->GetDestination(),
2228 ms
.mTrackPorts
[i
].second()->GetDestinationTrackId());
2229 MOZ_ASSERT(outputTrack
);
2231 mMainThreadEventTarget
->Dispatch(
2232 NewRunnableMethod("MediaStreamTrack::OverrideEnded", outputTrack
,
2233 &MediaStreamTrack::OverrideEnded
));
2236 ms
.mTrackPorts
[i
].second()->Destroy();
2237 ms
.mTrackPorts
.RemoveElementAt(i
);
2242 for (auto pair
: ms
.mTrackPorts
) {
2243 MOZ_ASSERT(pair
.first() != aTrack
->GetId(),
2244 "The same MediaTrack was forwarded to the output stream more "
2245 "than once. This shouldn't happen.");
2251 void HTMLMediaElement::DealWithFailedElement(nsIContent
* aSourceElement
) {
2252 if (mShuttingDown
) {
2256 DispatchAsyncSourceError(aSourceElement
);
2257 mMainThreadEventTarget
->Dispatch(
2258 NewRunnableMethod("HTMLMediaElement::QueueLoadFromSourceTask", this,
2259 &HTMLMediaElement::QueueLoadFromSourceTask
));
2262 void HTMLMediaElement::NotifyOutputTrackStopped(DOMMediaStream
* aOwningStream
,
2263 TrackID aDestinationTrackID
) {
2264 for (OutputMediaStream
& ms
: mOutputStreams
) {
2265 if (!ms
.mCapturingMediaStream
) {
2269 if (ms
.mStream
!= aOwningStream
) {
2273 for (int32_t i
= ms
.mTrackPorts
.Length() - 1; i
>= 0; --i
) {
2274 MediaInputPort
* port
= ms
.mTrackPorts
[i
].second();
2275 if (port
->GetDestinationTrackId() != aDestinationTrackID
) {
2280 ms
.mTrackPorts
.RemoveElementAt(i
);
2285 // An output track ended but its port is already gone.
2286 // It was probably cleared by the removal of the source MediaTrack.
2289 void HTMLMediaElement::LoadFromSourceChildren() {
2290 NS_ASSERTION(mDelayingLoadEvent
,
2291 "Should delay load event (if in document) during load");
2292 NS_ASSERTION(mIsLoadingFromSourceChildren
,
2293 "Must remember we're loading from source children");
2295 AddMutationObserverUnlessExists(this);
2298 Element
* child
= GetNextSource();
2300 // Exhausted candidates, wait for more candidates to be appended to
2301 // the media element.
2302 mLoadWaitStatus
= WAITING_FOR_SOURCE
;
2303 ChangeNetworkState(NETWORK_NO_SOURCE
);
2304 ChangeDelayLoadStatus(false);
2305 ReportLoadError("MediaLoadExhaustedCandidates");
2309 // Must have src attribute.
2311 if (!child
->GetAttr(kNameSpaceID_None
, nsGkAtoms::src
, src
)) {
2312 ReportLoadError("MediaLoadSourceMissingSrc");
2313 DealWithFailedElement(child
);
2317 // If we have a type attribute, it must be a supported type.
2319 if (child
->GetAttr(kNameSpaceID_None
, nsGkAtoms::type
, type
)) {
2320 DecoderDoctorDiagnostics diagnostics
;
2321 CanPlayStatus canPlay
= GetCanPlay(type
, &diagnostics
);
2322 diagnostics
.StoreFormatDiagnostics(OwnerDoc(), type
,
2323 canPlay
!= CANPLAY_NO
, __func__
);
2324 if (canPlay
== CANPLAY_NO
) {
2325 AutoTArray
<nsString
, 2> params
= {type
, src
};
2326 ReportLoadError("MediaLoadUnsupportedTypeAttribute", params
);
2327 DealWithFailedElement(child
);
2331 HTMLSourceElement
* childSrc
= HTMLSourceElement::FromNode(child
);
2332 LOG(LogLevel::Debug
,
2333 ("%p Trying load from <source>=%s type=%s", this,
2334 NS_ConvertUTF16toUTF8(src
).get(), NS_ConvertUTF16toUTF8(type
).get()));
2336 nsCOMPtr
<nsIURI
> uri
;
2337 NewURIFromString(src
, getter_AddRefs(uri
));
2339 AutoTArray
<nsString
, 1> params
= {src
};
2340 ReportLoadError("MediaLoadInvalidURI", params
);
2341 DealWithFailedElement(child
);
2345 RemoveMediaElementFromURITable();
2347 mLoadingSrcTriggeringPrincipal
= childSrc
->GetSrcTriggeringPrincipal();
2348 DDLOG(DDLogCategory::Property
, "loading_src",
2349 nsCString(NS_ConvertUTF16toUTF8(src
)));
2350 mMediaSource
= childSrc
->GetSrcMediaSource();
2351 DDLINKCHILD("mediasource", mMediaSource
.get());
2352 NS_ASSERTION(mNetworkState
== NETWORK_LOADING
,
2353 "Network state should be loading");
2355 if (mPreloadAction
== HTMLMediaElement::PRELOAD_NONE
&& !mMediaSource
) {
2356 // preload:none media, suspend the load here before we make any
2357 // network requests.
2362 if (NS_SUCCEEDED(LoadResource())) {
2366 // If we fail to load, loop back and try loading the next resource.
2367 DispatchAsyncSourceError(child
);
2369 MOZ_ASSERT_UNREACHABLE("Execution should not reach here!");
2372 void HTMLMediaElement::SuspendLoad() {
2373 mSuspendedForPreloadNone
= true;
2374 ChangeNetworkState(NETWORK_IDLE
);
2375 ChangeDelayLoadStatus(false);
2378 void HTMLMediaElement::ResumeLoad(PreloadAction aAction
) {
2379 NS_ASSERTION(mSuspendedForPreloadNone
,
2380 "Must be halted for preload:none to resume from preload:none "
2382 mSuspendedForPreloadNone
= false;
2383 mPreloadAction
= aAction
;
2384 ChangeDelayLoadStatus(true);
2385 ChangeNetworkState(NETWORK_LOADING
);
2386 if (!mIsLoadingFromSourceChildren
) {
2387 // We were loading from the element's src attribute.
2388 MediaResult rv
= LoadResource();
2389 if (NS_FAILED(rv
)) {
2390 NoSupportedMediaSourceError(rv
.Description());
2393 // We were loading from a child <source> element. Try to resume the
2394 // load of that child, and if that fails, try the next child.
2395 if (NS_FAILED(LoadResource())) {
2396 LoadFromSourceChildren();
2401 bool HTMLMediaElement::AllowedToPlay() const {
2402 return AutoplayPolicy::IsAllowedToPlay(*this);
2405 uint32_t HTMLMediaElement::GetPreloadDefault() const {
2407 return HTMLMediaElement::PRELOAD_ATTR_METADATA
;
2409 if (OnCellularConnection()) {
2410 return Preferences::GetInt("media.preload.default.cellular",
2411 HTMLMediaElement::PRELOAD_ATTR_NONE
);
2413 return Preferences::GetInt("media.preload.default",
2414 HTMLMediaElement::PRELOAD_ATTR_METADATA
);
2417 uint32_t HTMLMediaElement::GetPreloadDefaultAuto() const {
2418 if (OnCellularConnection()) {
2419 return Preferences::GetInt("media.preload.auto.cellular",
2420 HTMLMediaElement::PRELOAD_ATTR_METADATA
);
2422 return Preferences::GetInt("media.preload.auto",
2423 HTMLMediaElement::PRELOAD_ENOUGH
);
2426 void HTMLMediaElement::UpdatePreloadAction() {
2427 PreloadAction nextAction
= PRELOAD_UNDEFINED
;
2428 // If autoplay is set, or we're playing, we should always preload data,
2429 // as we'll need it to play.
2430 if ((AutoplayPolicy::IsAllowedToPlay(*this) &&
2431 HasAttr(kNameSpaceID_None
, nsGkAtoms::autoplay
)) ||
2433 nextAction
= HTMLMediaElement::PRELOAD_ENOUGH
;
2435 // Find the appropriate preload action by looking at the attribute.
2436 const nsAttrValue
* val
=
2437 mAttrs
.GetAttr(nsGkAtoms::preload
, kNameSpaceID_None
);
2438 // MSE doesn't work if preload is none, so it ignores the pref when src is
2440 uint32_t preloadDefault
= GetPreloadDefault();
2441 uint32_t preloadAuto
= GetPreloadDefaultAuto();
2443 // Attribute is not set. Use the preload action specified by the
2444 // media.preload.default pref, or just preload metadata if not present.
2445 nextAction
= static_cast<PreloadAction
>(preloadDefault
);
2446 } else if (val
->Type() == nsAttrValue::eEnum
) {
2447 PreloadAttrValue attr
=
2448 static_cast<PreloadAttrValue
>(val
->GetEnumValue());
2449 if (attr
== HTMLMediaElement::PRELOAD_ATTR_EMPTY
||
2450 attr
== HTMLMediaElement::PRELOAD_ATTR_AUTO
) {
2451 nextAction
= static_cast<PreloadAction
>(preloadAuto
);
2452 } else if (attr
== HTMLMediaElement::PRELOAD_ATTR_METADATA
) {
2453 nextAction
= HTMLMediaElement::PRELOAD_METADATA
;
2454 } else if (attr
== HTMLMediaElement::PRELOAD_ATTR_NONE
) {
2455 nextAction
= HTMLMediaElement::PRELOAD_NONE
;
2458 // Use the suggested "missing value default" of "metadata", or the value
2459 // specified by the media.preload.default, if present.
2460 nextAction
= static_cast<PreloadAction
>(preloadDefault
);
2464 if (nextAction
== HTMLMediaElement::PRELOAD_NONE
&& mIsDoingExplicitLoad
) {
2465 nextAction
= HTMLMediaElement::PRELOAD_METADATA
;
2468 mPreloadAction
= nextAction
;
2470 if (nextAction
== HTMLMediaElement::PRELOAD_ENOUGH
) {
2471 if (mSuspendedForPreloadNone
) {
2472 // Our load was previouly suspended due to the media having preload
2473 // value "none". The preload value has changed to preload:auto, so
2475 ResumeLoad(PRELOAD_ENOUGH
);
2477 // Preload as much of the video as we can, i.e. don't suspend after
2479 StopSuspendingAfterFirstFrame();
2482 } else if (nextAction
== HTMLMediaElement::PRELOAD_METADATA
) {
2483 // Ensure that the video can be suspended after first frame.
2484 mAllowSuspendAfterFirstFrame
= true;
2485 if (mSuspendedForPreloadNone
) {
2486 // Our load was previouly suspended due to the media having preload
2487 // value "none". The preload value has changed to preload:metadata, so
2488 // resume the load. We'll pause the load again after we've read the
2490 ResumeLoad(PRELOAD_METADATA
);
2495 MediaResult
HTMLMediaElement::LoadResource() {
2496 AbstractThread::AutoEnter
context(AbstractMainThread());
2498 NS_ASSERTION(mDelayingLoadEvent
,
2499 "Should delay load event (if in document) during load");
2501 if (mChannelLoader
) {
2502 mChannelLoader
->Cancel();
2503 mChannelLoader
= nullptr;
2506 // Set the media element's CORS mode only when loading a resource
2507 mCORSMode
= AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin
));
2509 HTMLMediaElement
* other
= LookupMediaElementURITable(mLoadingSrc
);
2510 if (other
&& other
->mDecoder
) {
2512 // TODO: remove the cast by storing ChannelMediaDecoder in the URI table.
2513 nsresult rv
= InitializeDecoderAsClone(
2514 static_cast<ChannelMediaDecoder
*>(other
->mDecoder
.get()));
2515 if (NS_SUCCEEDED(rv
)) return rv
;
2519 MediaDecoderInit
decoderInit(
2520 this, mMuted
? 0.0 : mVolume
, mPreservesPitch
,
2521 ClampPlaybackRate(mPlaybackRate
),
2522 mPreloadAction
== HTMLMediaElement::PRELOAD_METADATA
, mHasSuspendTaint
,
2523 HasAttr(kNameSpaceID_None
, nsGkAtoms::loop
),
2524 MediaContainerType(MEDIAMIMETYPE("application/x.mediasource")));
2526 RefPtr
<MediaSourceDecoder
> decoder
= new MediaSourceDecoder(decoderInit
);
2527 if (!mMediaSource
->Attach(decoder
)) {
2528 // TODO: Handle failure: run "If the media data cannot be fetched at
2529 // all, due to network errors, causing the user agent to give up
2530 // trying to fetch the resource" section of resource fetch algorithm.
2531 decoder
->Shutdown();
2532 return MediaResult(NS_ERROR_FAILURE
, "Failed to attach MediaSource");
2534 ChangeDelayLoadStatus(false);
2535 nsresult rv
= decoder
->Load(mMediaSource
->GetPrincipal());
2536 if (NS_FAILED(rv
)) {
2537 decoder
->Shutdown();
2538 LOG(LogLevel::Debug
,
2539 ("%p Failed to load for decoder %p", this, decoder
.get()));
2540 return MediaResult(rv
, "Fail to load decoder");
2542 rv
= FinishDecoderSetup(decoder
);
2543 return MediaResult(rv
, "Failed to set up decoder");
2546 AssertReadyStateIsNothing();
2548 RefPtr
<ChannelLoader
> loader
= new ChannelLoader
;
2549 nsresult rv
= loader
->Load(this);
2550 if (NS_SUCCEEDED(rv
)) {
2551 mChannelLoader
= loader
.forget();
2553 return MediaResult(rv
, "Failed to load channel");
2556 nsresult
HTMLMediaElement::LoadWithChannel(nsIChannel
* aChannel
,
2557 nsIStreamListener
** aListener
) {
2558 NS_ENSURE_ARG_POINTER(aChannel
);
2559 NS_ENSURE_ARG_POINTER(aListener
);
2561 *aListener
= nullptr;
2563 // Make sure we don't reenter during synchronous abort events.
2564 if (mIsRunningLoadMethod
) return NS_OK
;
2565 mIsRunningLoadMethod
= true;
2566 AbortExistingLoads();
2567 mIsRunningLoadMethod
= false;
2569 mLoadingSrcTriggeringPrincipal
= nullptr;
2570 nsresult rv
= aChannel
->GetOriginalURI(getter_AddRefs(mLoadingSrc
));
2571 NS_ENSURE_SUCCESS(rv
, rv
);
2573 ChangeDelayLoadStatus(true);
2574 rv
= InitializeDecoderForChannel(aChannel
, aListener
);
2575 if (NS_FAILED(rv
)) {
2576 ChangeDelayLoadStatus(false);
2580 SetPlaybackRate(mDefaultPlaybackRate
, IgnoreErrors());
2581 DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
2586 bool HTMLMediaElement::Seeking() const {
2587 return mDecoder
&& mDecoder
->IsSeeking();
2590 double HTMLMediaElement::CurrentTime() const {
2591 if (MediaStream
* stream
= GetSrcMediaStream()) {
2592 MediaStreamGraph
* graph
= stream
->Graph();
2593 GraphTime currentGraphTime
=
2594 mSrcStreamPausedGraphTime
.valueOr(graph
->CurrentTime());
2595 StreamTime currentStreamTime
= currentGraphTime
- mSrcStreamGraphTimeOffset
;
2596 return stream
->StreamTimeToSeconds(currentStreamTime
);
2599 if (mDefaultPlaybackStartPosition
== 0.0 && mDecoder
) {
2600 return mDecoder
->GetCurrentTime();
2603 return mDefaultPlaybackStartPosition
;
2606 void HTMLMediaElement::FastSeek(double aTime
, ErrorResult
& aRv
) {
2607 LOG(LogLevel::Debug
, ("%p FastSeek(%f) called by JS", this, aTime
));
2608 LOG(LogLevel::Debug
, ("Reporting telemetry VIDEO_FASTSEEK_USED"));
2609 Telemetry::Accumulate(Telemetry::VIDEO_FASTSEEK_USED
, 1);
2610 RefPtr
<Promise
> tobeDropped
=
2611 Seek(aTime
, SeekTarget::PrevSyncPoint
, IgnoreErrors());
2614 already_AddRefed
<Promise
> HTMLMediaElement::SeekToNextFrame(ErrorResult
& aRv
) {
2615 /* This will cause JIT code to be kept around longer, to help performance
2616 * when using SeekToNextFrame to iterate through every frame of a video.
2618 nsPIDOMWindowInner
* win
= OwnerDoc()->GetInnerWindow();
2621 if (JSObject
* obj
= win
->AsGlobal()->GetGlobalJSObject()) {
2622 js::NotifyAnimationActivity(obj
);
2626 return Seek(CurrentTime(), SeekTarget::NextFrame
, aRv
);
2629 void HTMLMediaElement::SetCurrentTime(double aCurrentTime
, ErrorResult
& aRv
) {
2630 LOG(LogLevel::Debug
,
2631 ("%p SetCurrentTime(%f) called by JS", this, aCurrentTime
));
2632 RefPtr
<Promise
> tobeDropped
=
2633 Seek(aCurrentTime
, SeekTarget::Accurate
, IgnoreErrors());
2637 * Check if aValue is inside a range of aRanges, and if so returns true
2638 * and puts the range index in aIntervalIndex. If aValue is not
2639 * inside a range, returns false, and aIntervalIndex
2640 * is set to the index of the range which starts immediately after aValue
2641 * (and can be aRanges.Length() if aValue is after the last range).
2643 static bool IsInRanges(TimeRanges
& aRanges
, double aValue
,
2644 uint32_t& aIntervalIndex
) {
2645 uint32_t length
= aRanges
.Length();
2647 for (uint32_t i
= 0; i
< length
; i
++) {
2648 double start
= aRanges
.Start(i
);
2649 if (start
> aValue
) {
2653 double end
= aRanges
.End(i
);
2654 if (aValue
<= end
) {
2659 aIntervalIndex
= length
;
2663 already_AddRefed
<Promise
> HTMLMediaElement::Seek(double aTime
,
2664 SeekTarget::Type aSeekType
,
2666 // Note: Seek is called both by synchronous code that expects errors thrown in
2667 // aRv, as well as asynchronous code that expects a promise. Make sure all
2668 // synchronous errors are returned using aRv, not promise rejections.
2670 // aTime should be non-NaN.
2671 MOZ_ASSERT(!mozilla::IsNaN(aTime
));
2673 // Seeking step1, Set the media element's show poster flag to false.
2674 // https://html.spec.whatwg.org/multipage/media.html#dom-media-seek
2675 mShowPoster
= false;
2677 RefPtr
<Promise
> promise
= CreateDOMPromise(aRv
);
2678 if (NS_WARN_IF(aRv
.Failed())) {
2682 // Detect if user has interacted with element by seeking so that
2683 // play will not be blocked when initiated by a script.
2684 if (EventStateManager::IsHandlingUserInput()) {
2688 StopSuspendingAfterFirstFrame();
2690 if (mSrcAttrStream
) {
2691 // do nothing since media streams have an empty Seekable range.
2692 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2696 if (mPlayed
&& mCurrentPlayRangeStart
!= -1.0) {
2697 double rangeEndTime
= CurrentTime();
2698 LOG(LogLevel::Debug
, ("%p Adding \'played\' a range : [%f, %f]", this,
2699 mCurrentPlayRangeStart
, rangeEndTime
));
2700 // Multiple seek without playing, or seek while playing.
2701 if (mCurrentPlayRangeStart
!= rangeEndTime
) {
2702 mPlayed
->Add(mCurrentPlayRangeStart
, rangeEndTime
);
2704 // Reset the current played range start time. We'll re-set it once
2705 // the seek completes.
2706 mCurrentPlayRangeStart
= -1.0;
2709 if (mReadyState
== HAVE_NOTHING
) {
2710 mDefaultPlaybackStartPosition
= aTime
;
2711 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2716 // mDecoder must always be set in order to reach this point.
2717 NS_ASSERTION(mDecoder
, "SetCurrentTime failed: no decoder");
2718 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2722 // Clamp the seek target to inside the seekable ranges.
2723 media::TimeIntervals seekableIntervals
= mDecoder
->GetSeekable();
2724 if (seekableIntervals
.IsInvalid()) {
2725 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2728 RefPtr
<TimeRanges
> seekable
=
2729 new TimeRanges(ToSupports(OwnerDoc()), seekableIntervals
);
2730 uint32_t length
= seekable
->Length();
2732 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2736 // If the position we want to seek to is not in a seekable range, we seek
2737 // to the closest position in the seekable ranges instead. If two positions
2738 // are equally close, we seek to the closest position from the currentTime.
2739 // See seeking spec, point 7 :
2740 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#seeking
2742 bool isInRange
= IsInRanges(*seekable
, aTime
, range
);
2745 // aTime is before the first range in |seekable|, the closest point we can
2746 // seek to is the start of the first range.
2747 aTime
= seekable
->Start(0);
2748 } else if (range
== length
) {
2749 // Seek target is after the end last range in seekable data.
2750 // Clamp the seek target to the end of the last seekable range.
2751 aTime
= seekable
->End(length
- 1);
2753 double leftBound
= seekable
->End(range
- 1);
2754 double rightBound
= seekable
->Start(range
);
2755 double distanceLeft
= Abs(leftBound
- aTime
);
2756 double distanceRight
= Abs(rightBound
- aTime
);
2757 if (distanceLeft
== distanceRight
) {
2758 double currentTime
= CurrentTime();
2759 distanceLeft
= Abs(leftBound
- currentTime
);
2760 distanceRight
= Abs(rightBound
- currentTime
);
2762 aTime
= (distanceLeft
< distanceRight
) ? leftBound
: rightBound
;
2766 // TODO: The spec requires us to update the current time to reflect the
2767 // actual seek target before beginning the synchronous section, but
2768 // that requires changing all MediaDecoderReaders to support telling
2769 // us the fastSeek target, and it's currently not possible to get
2770 // this information as we don't yet control the demuxer for all
2771 // MediaDecoderReaders.
2773 mPlayingBeforeSeek
= IsPotentiallyPlaying();
2775 // If the audio track is silent before seeking, we should end current silence
2776 // range and start a new range after seeking. Since seek() could be called
2777 // multiple times before seekEnd() executed, we should only calculate silence
2778 // range when first time seek() called. Calculating on other seek() calls
2779 // would cause a wrong result. In order to get correct time, this checking
2780 // should be called before decoder->seek().
2781 if (IsAudioTrackCurrentlySilent() &&
2782 !mHasAccumulatedSilenceRangeBeforeSeekEnd
) {
2783 AccumulateAudioTrackSilence();
2784 mHasAccumulatedSilenceRangeBeforeSeekEnd
= true;
2787 // The media backend is responsible for dispatching the timeupdate
2788 // event if it changes the playback position as a result of the seek.
2789 LOG(LogLevel::Debug
, ("%p SetCurrentTime(%f) starting seek", this, aTime
));
2790 mDecoder
->Seek(aTime
, aSeekType
);
2792 // We changed whether we're seeking so we need to AddRemoveSelfReference.
2793 AddRemoveSelfReference();
2795 // Keep the DOM promise.
2796 mSeekDOMPromise
= promise
;
2798 return promise
.forget();
2801 double HTMLMediaElement::Duration() const {
2803 if (mSrcStreamPlaybackEnded
) {
2804 return CurrentTime();
2806 return std::numeric_limits
<double>::infinity();
2810 return mDecoder
->GetDuration();
2813 return std::numeric_limits
<double>::quiet_NaN();
2816 already_AddRefed
<TimeRanges
> HTMLMediaElement::Seekable() const {
2817 media::TimeIntervals seekable
=
2818 mDecoder
? mDecoder
->GetSeekable() : media::TimeIntervals();
2819 RefPtr
<TimeRanges
> ranges
= new TimeRanges(ToSupports(OwnerDoc()), seekable
);
2820 return ranges
.forget();
2823 already_AddRefed
<TimeRanges
> HTMLMediaElement::Played() {
2824 RefPtr
<TimeRanges
> ranges
= new TimeRanges(ToSupports(OwnerDoc()));
2826 uint32_t timeRangeCount
= 0;
2828 timeRangeCount
= mPlayed
->Length();
2830 for (uint32_t i
= 0; i
< timeRangeCount
; i
++) {
2831 double begin
= mPlayed
->Start(i
);
2832 double end
= mPlayed
->End(i
);
2833 ranges
->Add(begin
, end
);
2836 if (mCurrentPlayRangeStart
!= -1.0) {
2837 double now
= CurrentTime();
2838 if (mCurrentPlayRangeStart
!= now
) {
2839 ranges
->Add(mCurrentPlayRangeStart
, now
);
2843 ranges
->Normalize();
2844 return ranges
.forget();
2847 void HTMLMediaElement::Pause(ErrorResult
& aRv
) {
2848 LOG(LogLevel::Debug
, ("%p Pause() called by JS", this));
2849 if (mNetworkState
== NETWORK_EMPTY
) {
2850 LOG(LogLevel::Debug
, ("Loading due to Pause()"));
2852 } else if (mDecoder
) {
2856 bool oldPaused
= mPaused
;
2858 mAutoplaying
= false;
2859 // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
2860 AddRemoveSelfReference();
2861 UpdateSrcMediaStreamPlaying();
2862 if (mAudioChannelWrapper
) {
2863 mAudioChannelWrapper
->NotifyPlayStateChanged();
2867 FireTimeUpdate(false);
2868 DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
2869 AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_ABORT_ERR
);
2873 void HTMLMediaElement::SetVolume(double aVolume
, ErrorResult
& aRv
) {
2874 LOG(LogLevel::Debug
, ("%p SetVolume(%f) called by JS", this, aVolume
));
2876 if (aVolume
< 0.0 || aVolume
> 1.0) {
2877 aRv
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
2881 if (aVolume
== mVolume
) return;
2885 // Here we want just to update the volume.
2886 SetVolumeInternal();
2888 DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
2890 // We allow inaudible autoplay. But changing our volume may make this
2891 // media audible. So pause if we are no longer supposed to be autoplaying.
2892 PauseIfShouldNotBePlaying();
2895 void HTMLMediaElement::MozGetMetadata(JSContext
* cx
,
2896 JS::MutableHandle
<JSObject
*> aRetval
,
2898 if (mReadyState
< HAVE_METADATA
) {
2899 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2903 JS::Rooted
<JSObject
*> tags(cx
, JS_NewPlainObject(cx
));
2905 aRv
.Throw(NS_ERROR_FAILURE
);
2909 for (auto iter
= mTags
->ConstIter(); !iter
.Done(); iter
.Next()) {
2911 CopyUTF8toUTF16(iter
.UserData(), wideValue
);
2912 JS::Rooted
<JSString
*> string(cx
,
2913 JS_NewUCStringCopyZ(cx
, wideValue
.Data()));
2914 if (!string
|| !JS_DefineProperty(cx
, tags
, iter
.Key().Data(), string
,
2915 JSPROP_ENUMERATE
)) {
2916 NS_WARNING("couldn't create metadata object!");
2917 aRv
.Throw(NS_ERROR_FAILURE
);
2926 void HTMLMediaElement::SetMutedInternal(uint32_t aMuted
) {
2927 uint32_t oldMuted
= mMuted
;
2930 if (!!aMuted
== !!oldMuted
) {
2934 SetVolumeInternal();
2937 void HTMLMediaElement::PauseIfShouldNotBePlaying() {
2941 if (!AutoplayPolicy::IsAllowedToPlay(*this)) {
2942 AUTOPLAY_LOG("pause because not allowed to play, element=%p", this);
2945 OwnerDoc()->SetDocTreeHadPlayRevoked();
2949 void HTMLMediaElement::SetVolumeInternal() {
2950 float effectiveVolume
= ComputedVolume();
2953 mDecoder
->SetVolume(effectiveVolume
);
2954 } else if (MediaStream
* stream
= GetSrcMediaStream()) {
2955 if (mSrcStreamIsPlaying
) {
2956 stream
->SetAudioOutputVolume(this, effectiveVolume
);
2960 NotifyAudioPlaybackChanged(
2961 AudioChannelService::AudibleChangedReasons::eVolumeChanged
);
2964 void HTMLMediaElement::SetMuted(bool aMuted
) {
2965 LOG(LogLevel::Debug
, ("%p SetMuted(%d) called by JS", this, aMuted
));
2966 if (aMuted
== Muted()) {
2971 SetMutedInternal(mMuted
| MUTED_BY_CONTENT
);
2973 SetMutedInternal(mMuted
& ~MUTED_BY_CONTENT
);
2976 DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
2978 // We allow inaudible autoplay. But changing our mute status may make this
2979 // media audible. So pause if we are no longer supposed to be autoplaying.
2980 PauseIfShouldNotBePlaying();
2983 void HTMLMediaElement::SetCapturedOutputStreamsEnabled(bool aEnabled
) {
2984 for (OutputMediaStream
& ms
: mOutputStreams
) {
2985 if (ms
.mCapturingDecoder
) {
2986 MOZ_ASSERT(!ms
.mCapturingMediaStream
);
2989 for (auto pair
: ms
.mTrackPorts
) {
2990 MediaStream
* outputSource
= ms
.mStream
->GetInputStream();
2991 if (!outputSource
) {
2992 NS_ERROR("No output source stream");
2996 TrackID id
= pair
.second()->GetDestinationTrackId();
2997 outputSource
->SetTrackEnabled(
2998 id
, aEnabled
? DisabledTrackMode::ENABLED
2999 : DisabledTrackMode::SILENCE_FREEZE
);
3001 LOG(LogLevel::Debug
,
3002 ("%s track %d for captured MediaStream %p",
3003 aEnabled
? "Enabled" : "Disabled", id
, ms
.mStream
.get()));
3008 void HTMLMediaElement::AddCaptureMediaTrackToOutputStream(
3009 MediaTrack
* aTrack
, OutputMediaStream
& aOutputStream
, bool aAsyncAddtrack
) {
3010 if (aOutputStream
.mCapturingDecoder
) {
3011 MOZ_ASSERT(!aOutputStream
.mCapturingMediaStream
);
3014 aOutputStream
.mCapturingMediaStream
= true;
3016 if (aOutputStream
.mStream
== mSrcStream
) {
3017 // Cycle detected. This can happen since tracks are added async.
3018 // We avoid forwarding it to the output here or we'd get into an infloop.
3022 MediaStream
* outputSource
= aOutputStream
.mStream
->GetInputStream();
3023 if (!outputSource
) {
3024 NS_ERROR("No output source stream");
3028 ProcessedMediaStream
* processedOutputSource
=
3029 outputSource
->AsProcessedStream();
3030 if (!processedOutputSource
) {
3031 NS_ERROR("Input stream not a ProcessedMediaStream");
3036 MOZ_ASSERT(false, "Bad MediaTrack");
3040 MediaStreamTrack
* inputTrack
= mSrcStream
->GetTrackById(aTrack
->GetId());
3041 MOZ_ASSERT(inputTrack
);
3043 NS_ERROR("Input track not found in source stream");
3048 for (auto pair
: aOutputStream
.mTrackPorts
) {
3049 MOZ_ASSERT(pair
.first() != aTrack
->GetId(),
3050 "Captured track already captured to output stream");
3054 TrackID destinationTrackID
= aOutputStream
.mNextAvailableTrackID
++;
3055 RefPtr
<MediaStreamTrackSource
> source
=
3056 new StreamCaptureTrackSource(this, &inputTrack
->GetSource(),
3057 aOutputStream
.mStream
, destinationTrackID
);
3059 MediaSegment::Type type
= inputTrack
->AsAudioStreamTrack()
3060 ? MediaSegment::AUDIO
3061 : MediaSegment::VIDEO
;
3063 RefPtr
<MediaStreamTrack
> track
=
3064 aOutputStream
.mStream
->CreateDOMTrack(destinationTrackID
, type
, source
);
3066 if (aAsyncAddtrack
) {
3067 mMainThreadEventTarget
->Dispatch(
3068 NewRunnableMethod
<StoreRefPtrPassByPtr
<MediaStreamTrack
>>(
3069 "DOMMediaStream::AddTrackInternal", aOutputStream
.mStream
,
3070 &DOMMediaStream::AddTrackInternal
, track
));
3072 aOutputStream
.mStream
->AddTrackInternal(track
);
3075 // Track is muted initially, so we don't leak data if it's added while paused
3076 // and an MSG iteration passes before the mute comes into effect.
3077 processedOutputSource
->SetTrackEnabled(destinationTrackID
,
3078 DisabledTrackMode::SILENCE_FREEZE
);
3079 RefPtr
<MediaInputPort
> port
= inputTrack
->ForwardTrackContentsTo(
3080 processedOutputSource
, destinationTrackID
);
3082 Pair
<nsString
, RefPtr
<MediaInputPort
>> p(aTrack
->GetId(), port
);
3083 aOutputStream
.mTrackPorts
.AppendElement(std::move(p
));
3085 if (mSrcStreamIsPlaying
) {
3086 processedOutputSource
->SetTrackEnabled(destinationTrackID
,
3087 DisabledTrackMode::ENABLED
);
3090 LOG(LogLevel::Debug
,
3091 ("Created %s track %p with id %d from track %p through MediaInputPort %p",
3092 inputTrack
->AsAudioStreamTrack() ? "audio" : "video", track
.get(),
3093 destinationTrackID
, inputTrack
, port
.get()));
3096 bool HTMLMediaElement::CanBeCaptured(StreamCaptureType aCaptureType
) {
3097 // Don't bother capturing when the document has gone away
3098 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
3103 // Prevent capturing restricted video
3104 if (aCaptureType
== StreamCaptureType::CAPTURE_ALL_TRACKS
&&
3105 ContainsRestrictedContent()) {
3111 already_AddRefed
<DOMMediaStream
> HTMLMediaElement::CaptureStreamInternal(
3112 StreamCaptureBehavior aFinishBehavior
, StreamCaptureType aStreamCaptureType
,
3113 MediaStreamGraph
* aGraph
) {
3114 MOZ_RELEASE_ASSERT(aGraph
);
3115 MOZ_ASSERT(CanBeCaptured(aStreamCaptureType
));
3117 MarkAsContentSource(CallerAPI::CAPTURE_STREAM
);
3120 // We don't support routing to a different graph.
3121 if (!mOutputStreams
.IsEmpty() &&
3122 aGraph
!= mOutputStreams
[0].mStream
->GetInputStream()->Graph()) {
3126 OutputMediaStream
* out
= mOutputStreams
.AppendElement();
3127 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
3128 out
->mStream
= DOMMediaStream::CreateTrackUnionStreamAsInput(window
, aGraph
);
3129 out
->mStream
->SetFinishedOnInactive(false);
3130 out
->mFinishWhenEnded
=
3131 aFinishBehavior
== StreamCaptureBehavior::FINISH_WHEN_ENDED
;
3132 out
->mCapturingAudioOnly
=
3133 aStreamCaptureType
== StreamCaptureType::CAPTURE_AUDIO
;
3135 if (aStreamCaptureType
== StreamCaptureType::CAPTURE_AUDIO
) {
3137 // We don't support applying volume and mute to the captured stream, when
3138 // capturing a MediaStream.
3139 ReportToConsole(nsIScriptError::errorFlag
,
3140 "MediaElementAudioCaptureOfMediaStreamError");
3144 // mAudioCaptured tells the user that the audio played by this media element
3145 // is being routed to the captureStreams *instead* of being played to
3147 mAudioCaptured
= true;
3151 out
->mCapturingDecoder
= true;
3152 mDecoder
->AddOutputStream(out
->mStream
);
3153 } else if (mSrcStream
) {
3154 out
->mCapturingMediaStream
= true;
3157 if (mReadyState
== HAVE_NOTHING
) {
3158 // Do not expose the tracks until we have metadata.
3159 RefPtr
<DOMMediaStream
> result
= out
->mStream
;
3160 return result
.forget();
3164 MOZ_DIAGNOSTIC_ASSERT(AudioTracks(), "Element can't have been unlinked");
3165 for (size_t i
= 0; i
< AudioTracks()->Length(); ++i
) {
3166 AudioTrack
* t
= (*AudioTracks())[i
];
3168 AddCaptureMediaTrackToOutputStream(t
, *out
, false);
3171 if (IsVideo() && !out
->mCapturingAudioOnly
) {
3172 MOZ_DIAGNOSTIC_ASSERT(VideoTracks(), "Element can't have been unlinked");
3173 // Only add video tracks if we're a video element and the output stream
3175 for (size_t i
= 0; i
< VideoTracks()->Length(); ++i
) {
3176 VideoTrack
* t
= (*VideoTracks())[i
];
3177 if (t
->Selected()) {
3178 AddCaptureMediaTrackToOutputStream(t
, *out
, false);
3183 RefPtr
<DOMMediaStream
> result
= out
->mStream
;
3184 return result
.forget();
3187 already_AddRefed
<DOMMediaStream
> HTMLMediaElement::CaptureAudio(
3188 ErrorResult
& aRv
, MediaStreamGraph
* aGraph
) {
3189 MOZ_RELEASE_ASSERT(aGraph
);
3191 if (!CanBeCaptured(StreamCaptureType::CAPTURE_AUDIO
)) {
3192 aRv
.Throw(NS_ERROR_FAILURE
);
3196 RefPtr
<DOMMediaStream
> stream
=
3197 CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED
,
3198 StreamCaptureType::CAPTURE_AUDIO
, aGraph
);
3200 aRv
.Throw(NS_ERROR_FAILURE
);
3204 return stream
.forget();
3207 RefPtr
<GenericNonExclusivePromise
> HTMLMediaElement::GetAllowedToPlayPromise() {
3208 MOZ_ASSERT(NS_IsMainThread());
3209 MOZ_ASSERT(!mOutputStreams
.IsEmpty(),
3210 "This method should only be called during stream capturing!");
3211 if (AutoplayPolicy::IsAllowedToPlay(*this)) {
3212 AUTOPLAY_LOG("MediaElement %p has allowed to play, resolve promise", this);
3213 return GenericNonExclusivePromise::CreateAndResolve(true, __func__
);
3215 AUTOPLAY_LOG("create allow-to-play promise for MediaElement %p", this);
3216 return mAllowedToPlayPromise
.Ensure(__func__
);
3219 already_AddRefed
<DOMMediaStream
> HTMLMediaElement::MozCaptureStream(
3221 MediaStreamGraph::GraphDriverType graphDriverType
=
3222 HasAudio() ? MediaStreamGraph::AUDIO_THREAD_DRIVER
3223 : MediaStreamGraph::SYSTEM_THREAD_DRIVER
;
3225 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
3227 aRv
.Throw(NS_ERROR_FAILURE
);
3231 if (!CanBeCaptured(StreamCaptureType::CAPTURE_ALL_TRACKS
)) {
3232 aRv
.Throw(NS_ERROR_FAILURE
);
3236 MediaStreamGraph
* graph
= MediaStreamGraph::GetInstance(
3237 graphDriverType
, window
, MediaStreamGraph::REQUEST_DEFAULT_SAMPLE_RATE
);
3239 RefPtr
<DOMMediaStream
> stream
=
3240 CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED
,
3241 StreamCaptureType::CAPTURE_ALL_TRACKS
, graph
);
3243 aRv
.Throw(NS_ERROR_FAILURE
);
3247 return stream
.forget();
3250 already_AddRefed
<DOMMediaStream
> HTMLMediaElement::MozCaptureStreamUntilEnded(
3252 MediaStreamGraph::GraphDriverType graphDriverType
=
3253 HasAudio() ? MediaStreamGraph::AUDIO_THREAD_DRIVER
3254 : MediaStreamGraph::SYSTEM_THREAD_DRIVER
;
3256 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
3258 aRv
.Throw(NS_ERROR_FAILURE
);
3262 if (!CanBeCaptured(StreamCaptureType::CAPTURE_ALL_TRACKS
)) {
3263 aRv
.Throw(NS_ERROR_FAILURE
);
3267 MediaStreamGraph
* graph
= MediaStreamGraph::GetInstance(
3268 graphDriverType
, window
, MediaStreamGraph::REQUEST_DEFAULT_SAMPLE_RATE
);
3270 RefPtr
<DOMMediaStream
> stream
=
3271 CaptureStreamInternal(StreamCaptureBehavior::FINISH_WHEN_ENDED
,
3272 StreamCaptureType::CAPTURE_ALL_TRACKS
, graph
);
3274 aRv
.Throw(NS_ERROR_FAILURE
);
3278 return stream
.forget();
3281 class MediaElementSetForURI
: public nsURIHashKey
{
3283 explicit MediaElementSetForURI(const nsIURI
* aKey
) : nsURIHashKey(aKey
) {}
3284 MediaElementSetForURI(MediaElementSetForURI
&& aOther
)
3285 : nsURIHashKey(std::move(aOther
)),
3286 mElements(std::move(aOther
.mElements
)) {}
3287 nsTArray
<HTMLMediaElement
*> mElements
;
3290 typedef nsTHashtable
<MediaElementSetForURI
> MediaElementURITable
;
3291 // Elements in this table must have non-null mDecoder and mLoadingSrc, and those
3292 // can't change while the element is in the table. The table is keyed by
3293 // the element's mLoadingSrc. Each entry has a list of all elements with the
3294 // same mLoadingSrc.
3295 static MediaElementURITable
* gElementTable
;
3298 static bool URISafeEquals(nsIURI
* a1
, nsIURI
* a2
) {
3300 // Consider two empty URIs *not* equal!
3304 nsresult rv
= a1
->Equals(a2
, &equal
);
3305 return NS_SUCCEEDED(rv
) && equal
;
3307 // Returns the number of times aElement appears in the media element table
3308 // for aURI. If this returns other than 0 or 1, there's a bug somewhere!
3309 static unsigned MediaElementTableCount(HTMLMediaElement
* aElement
,
3311 if (!gElementTable
|| !aElement
) {
3314 uint32_t uriCount
= 0;
3315 uint32_t otherCount
= 0;
3316 for (auto it
= gElementTable
->ConstIter(); !it
.Done(); it
.Next()) {
3317 MediaElementSetForURI
* entry
= it
.Get();
3319 for (const auto& elem
: entry
->mElements
) {
3320 if (elem
== aElement
) {
3324 if (URISafeEquals(aURI
, entry
->GetKey())) {
3327 otherCount
+= count
;
3330 NS_ASSERTION(otherCount
== 0, "Should not have entries for unknown URIs");
3335 void HTMLMediaElement::AddMediaElementToURITable() {
3336 NS_ASSERTION(mDecoder
, "Call this only with decoder Load called");
3338 MediaElementTableCount(this, mLoadingSrc
) == 0,
3339 "Should not have entry for element in element table before addition");
3340 if (!gElementTable
) {
3341 gElementTable
= new MediaElementURITable();
3343 MediaElementSetForURI
* entry
= gElementTable
->PutEntry(mLoadingSrc
);
3344 entry
->mElements
.AppendElement(this);
3346 MediaElementTableCount(this, mLoadingSrc
) == 1,
3347 "Should have a single entry for element in element table after addition");
3350 void HTMLMediaElement::RemoveMediaElementFromURITable() {
3351 if (!mDecoder
|| !mLoadingSrc
|| !gElementTable
) {
3354 MediaElementSetForURI
* entry
= gElementTable
->GetEntry(mLoadingSrc
);
3358 entry
->mElements
.RemoveElement(this);
3359 if (entry
->mElements
.IsEmpty()) {
3360 gElementTable
->RemoveEntry(entry
);
3361 if (gElementTable
->Count() == 0) {
3362 delete gElementTable
;
3363 gElementTable
= nullptr;
3366 NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc
) == 0,
3367 "After remove, should no longer have an entry in element table");
3370 HTMLMediaElement
* HTMLMediaElement::LookupMediaElementURITable(nsIURI
* aURI
) {
3371 if (!gElementTable
) {
3374 MediaElementSetForURI
* entry
= gElementTable
->GetEntry(aURI
);
3378 for (uint32_t i
= 0; i
< entry
->mElements
.Length(); ++i
) {
3379 HTMLMediaElement
* elem
= entry
->mElements
[i
];
3381 // Look for elements that have the same principal and CORS mode.
3382 // Ditto for anything else that could cause us to send different headers.
3383 if (NS_SUCCEEDED(elem
->NodePrincipal()->Equals(NodePrincipal(), &equal
)) &&
3384 equal
&& elem
->mCORSMode
== mCORSMode
) {
3385 // See SetupDecoder() below. We only add a element to the table when
3386 // mDecoder is a ChannelMediaDecoder.
3387 auto decoder
= static_cast<ChannelMediaDecoder
*>(elem
->mDecoder
.get());
3388 NS_ASSERTION(decoder
, "Decoder gone");
3389 if (decoder
->CanClone()) {
3397 class HTMLMediaElement::ShutdownObserver
: public nsIObserver
{
3398 enum class Phase
: int8_t { Init
, Subscribed
, Unsubscribed
};
3403 NS_IMETHOD
Observe(nsISupports
*, const char* aTopic
,
3404 const char16_t
*) override
{
3405 if (mPhase
!= Phase::Subscribed
) {
3406 // Bail out if we are not subscribed for this might be called even after
3407 // |nsContentUtils::UnregisterShutdownObserver(this)|.
3410 MOZ_DIAGNOSTIC_ASSERT(mWeak
);
3411 if (strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
) == 0) {
3412 mWeak
->NotifyShutdownEvent();
3416 void Subscribe(HTMLMediaElement
* aPtr
) {
3417 MOZ_DIAGNOSTIC_ASSERT(mPhase
== Phase::Init
);
3418 MOZ_DIAGNOSTIC_ASSERT(!mWeak
);
3420 nsContentUtils::RegisterShutdownObserver(this);
3421 mPhase
= Phase::Subscribed
;
3423 void Unsubscribe() {
3424 MOZ_DIAGNOSTIC_ASSERT(mPhase
== Phase::Subscribed
);
3425 MOZ_DIAGNOSTIC_ASSERT(mWeak
);
3426 MOZ_DIAGNOSTIC_ASSERT(!mAddRefed
,
3427 "ReleaseMediaElement should have been called first");
3429 nsContentUtils::UnregisterShutdownObserver(this);
3430 mPhase
= Phase::Unsubscribed
;
3432 void AddRefMediaElement() {
3433 MOZ_DIAGNOSTIC_ASSERT(mWeak
);
3434 MOZ_DIAGNOSTIC_ASSERT(!mAddRefed
, "Should only ever AddRef once");
3438 void ReleaseMediaElement() {
3439 MOZ_DIAGNOSTIC_ASSERT(mWeak
);
3440 MOZ_DIAGNOSTIC_ASSERT(mAddRefed
, "Should only release after AddRef");
3446 virtual ~ShutdownObserver() {
3447 MOZ_DIAGNOSTIC_ASSERT(mPhase
== Phase::Unsubscribed
);
3448 MOZ_DIAGNOSTIC_ASSERT(!mWeak
);
3449 MOZ_DIAGNOSTIC_ASSERT(!mAddRefed
,
3450 "ReleaseMediaElement should have been called first");
3452 // Guaranteed to be valid by HTMLMediaElement.
3453 HTMLMediaElement
* mWeak
= nullptr;
3454 Phase mPhase
= Phase::Init
;
3455 bool mAddRefed
= false;
3458 NS_IMPL_ISUPPORTS(HTMLMediaElement::ShutdownObserver
, nsIObserver
)
3460 HTMLMediaElement::HTMLMediaElement(
3461 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
)
3462 : nsGenericHTMLElement(std::move(aNodeInfo
)),
3464 OwnerDoc()->AbstractMainThreadFor(TaskCategory::Other
)),
3465 mMainThreadEventTarget(OwnerDoc()->EventTargetFor(TaskCategory::Other
)),
3466 mAbstractMainThread(
3467 OwnerDoc()->AbstractMainThreadFor(TaskCategory::Other
)),
3468 mShutdownObserver(new ShutdownObserver
),
3469 mPlayed(new TimeRanges(ToSupports(OwnerDoc()))),
3470 mPaused(true, "HTMLMediaElement::mPaused"),
3471 mErrorSink(new ErrorSink(this)),
3472 mAudioChannelWrapper(new AudioChannelAgentCallback(this)),
3473 mSink(MakePair(nsString(), RefPtr
<AudioDeviceInfo
>())),
3474 mShowPoster(IsVideo()) {
3475 MOZ_ASSERT(mMainThreadEventTarget
);
3476 MOZ_ASSERT(mAbstractMainThread
);
3477 // Please don't add anything to this constructor or the initialization
3478 // list that can cause AddRef to be called. This prevents subclasses
3479 // from overriding AddRef in a way that works with our refcount
3480 // logging mechanisms. Put these things inside of the ::Init method
3484 void HTMLMediaElement::Init() {
3485 MOZ_ASSERT(mRefCnt
== 0 && !mRefCnt
.IsPurple(),
3486 "HTMLMediaElement::Init called when AddRef has been called "
3487 "at least once already, probably in the constructor. Please "
3488 "see the documentation in the HTMLMediaElement constructor.");
3489 MOZ_ASSERT(!mRefCnt
.IsPurple());
3491 mAudioTrackList
= new AudioTrackList(OwnerDoc()->GetParentObject(), this);
3492 mVideoTrackList
= new VideoTrackList(OwnerDoc()->GetParentObject(), this);
3494 DecoderDoctorLogger::LogConstruction(this);
3496 mWatchManager
.Watch(mPaused
, &HTMLMediaElement::UpdateWakeLock
);
3500 double defaultVolume
= Preferences::GetFloat("media.default_volume", 1.0);
3501 SetVolume(defaultVolume
, rv
);
3503 RegisterActivityObserver();
3504 NotifyOwnerDocumentActivityChanged();
3506 // We initialize the MediaShutdownManager as the HTMLMediaElement is always
3507 // constructed on the main thread, and not during stable state.
3508 // (MediaShutdownManager make use of nsIAsyncShutdownClient which is written
3510 MediaShutdownManager::InitStatics();
3512 mShutdownObserver
->Subscribe(this);
3513 mInitialized
= true;
3516 HTMLMediaElement::~HTMLMediaElement() {
3517 MOZ_ASSERT(mInitialized
,
3518 "HTMLMediaElement must be initialized before it is destroyed.");
3521 "How can we be destroyed if we're still holding a self reference?");
3523 mShutdownObserver
->Unsubscribe();
3525 if (mVideoFrameContainer
) {
3526 mVideoFrameContainer
->ForgetElement();
3528 UnregisterActivityObserver();
3530 mSetCDMRequest
.DisconnectIfExists();
3531 mAllowedToPlayPromise
.RejectIfExists(NS_ERROR_FAILURE
, __func__
);
3536 if (mProgressTimer
) {
3539 if (mVideoDecodeSuspendTimer
) {
3540 mVideoDecodeSuspendTimer
->Cancel();
3541 mVideoDecodeSuspendTimer
= nullptr;
3544 EndSrcMediaStreamPlayback();
3547 if (mCaptureStreamPort
) {
3548 mCaptureStreamPort
->Destroy();
3549 mCaptureStreamPort
= nullptr;
3552 NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc
) == 0,
3553 "Destroyed media element should no longer be in element table");
3555 if (mChannelLoader
) {
3556 mChannelLoader
->Cancel();
3559 if (mAudioChannelWrapper
) {
3560 mAudioChannelWrapper
->Shutdown();
3561 mAudioChannelWrapper
= nullptr;
3565 ReportPlayedTimeAfterBlockedTelemetry();
3567 DecoderDoctorLogger::LogDestruction(this);
3570 void HTMLMediaElement::StopSuspendingAfterFirstFrame() {
3571 mAllowSuspendAfterFirstFrame
= false;
3572 if (!mSuspendedAfterFirstFrame
) return;
3573 mSuspendedAfterFirstFrame
= false;
3579 void HTMLMediaElement::SetPlayedOrSeeked(bool aValue
) {
3580 if (aValue
== mHasPlayedOrSeeked
) {
3584 mHasPlayedOrSeeked
= aValue
;
3586 // Force a reflow so that the poster frame hides or shows immediately.
3587 nsIFrame
* frame
= GetPrimaryFrame();
3591 frame
->PresShell()->FrameNeedsReflow(frame
, IntrinsicDirty::TreeChange
,
3595 void HTMLMediaElement::NotifyXPCOMShutdown() { ShutdownDecoder(); }
3597 bool HTMLMediaElement::AudioChannelAgentDelayingPlayback() {
3598 return mAudioChannelWrapper
&& mAudioChannelWrapper
->IsPlaybackBlocked();
3601 void HTMLMediaElement::UpdateHadAudibleAutoplayState() {
3602 // If we're audible, and autoplaying...
3603 if ((Volume() > 0.0 && !Muted()) &&
3604 (!OwnerDoc()->HasBeenUserGestureActivated() || Autoplay())) {
3605 OwnerDoc()->SetDocTreeHadAudibleMedia();
3606 if (AutoplayPolicy::WouldBeAllowedToPlayIfAutoplayDisabled(*this)) {
3607 ScalarAdd(Telemetry::ScalarID::MEDIA_AUTOPLAY_WOULD_BE_ALLOWED_COUNT
, 1);
3608 if (mReadyState
>= HAVE_METADATA
&& !HasAudio()) {
3610 Telemetry::ScalarID::MEDIA_ALLOWED_AUTOPLAY_NO_AUDIO_TRACK_COUNT
,
3614 if (mReadyState
< HAVE_METADATA
) {
3615 mBlockedAsWithoutMetadata
= true;
3616 ScalarAdd(Telemetry::ScalarID::MEDIA_BLOCKED_NO_METADATA
, 1);
3618 ScalarAdd(Telemetry::ScalarID::MEDIA_AUTOPLAY_WOULD_NOT_BE_ALLOWED_COUNT
,
3624 already_AddRefed
<Promise
> HTMLMediaElement::Play(ErrorResult
& aRv
) {
3625 LOG(LogLevel::Debug
,
3626 ("%p Play() called by JS readyState=%d", this, mReadyState
));
3629 // When the play() method on a media element is invoked, the user agent must
3630 // run the following steps.
3632 RefPtr
<PlayPromise
> promise
= CreatePlayPromise(aRv
);
3633 if (NS_WARN_IF(aRv
.Failed())) {
3637 // 4.8.12.8 - Step 1:
3638 // If the media element is not allowed to play, return a promise rejected
3639 // with a "NotAllowedError" DOMException and abort these steps.
3640 // NOTE: we may require requesting permission from the user, so we do the
3641 // "not allowed" check below.
3643 // 4.8.12.8 - Step 2:
3644 // If the media element's error attribute is not null and its code
3645 // attribute has the value MEDIA_ERR_SRC_NOT_SUPPORTED, return a promise
3646 // rejected with a "NotSupportedError" DOMException and abort these steps.
3647 if (GetError() && GetError()->Code() == MEDIA_ERR_SRC_NOT_SUPPORTED
) {
3648 LOG(LogLevel::Debug
,
3649 ("%p Play() promise rejected because source not supported.", this));
3650 promise
->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR
);
3651 return promise
.forget();
3654 // 4.8.12.8 - Step 3:
3655 // Let promise be a new promise and append promise to the list of pending
3657 // Note: Promise appended to list of pending promises as needed below.
3659 if (AudioChannelAgentDelayingPlayback()) {
3660 // The audio channel agent may delay starting playback of a media resource
3661 // until the tab the media element is in has been in the foreground.
3662 // Save a reference to the promise, and return it. The AudioChannelAgent
3663 // will call Play() again if the tab is brought to the foreground, or the
3664 // audio tab indicator is clicked, which will resolve the promise if we end
3666 LOG(LogLevel::Debug
, ("%p Play() call delayed by AudioChannelAgent", this));
3668 mPendingPlayPromises
.AppendElement(promise
);
3669 return promise
.forget();
3672 if (AudioChannelAgentBlockedPlay()) {
3673 LOG(LogLevel::Debug
, ("%p play blocked by AudioChannelAgent.", this));
3674 promise
->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR
);
3675 return promise
.forget();
3678 UpdateHadAudibleAutoplayState();
3680 const bool handlingUserInput
= EventStateManager::IsHandlingUserInput();
3681 mPendingPlayPromises
.AppendElement(promise
);
3683 if (AutoplayPolicy::IsAllowedToPlay(*this)) {
3684 AUTOPLAY_LOG("allow MediaElement %p to play", this);
3685 mAllowedToPlayPromise
.ResolveIfExists(true, __func__
);
3686 PlayInternal(handlingUserInput
);
3687 UpdateCustomPolicyAfterPlayed();
3689 AUTOPLAY_LOG("reject MediaElement %p to play", this);
3690 AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR
);
3692 return promise
.forget();
3695 void HTMLMediaElement::DispatchEventsWhenPlayWasNotAllowed() {
3696 if (StaticPrefs::MediaBlockEventEnabled()) {
3697 DispatchAsyncEvent(NS_LITERAL_STRING("blocked"));
3699 #if defined(MOZ_WIDGET_ANDROID)
3700 RefPtr
<AsyncEventDispatcher
> asyncDispatcher
= new AsyncEventDispatcher(
3701 this, NS_LITERAL_STRING("MozAutoplayMediaBlocked"), CanBubble::eYes
,
3702 ChromeOnlyDispatch::eYes
);
3703 asyncDispatcher
->PostDOMEvent();
3705 OwnerDoc()->MaybeNotifyAutoplayBlocked();
3706 ReportToConsole(nsIScriptError::warningFlag
, "BlockAutoplayError");
3707 mHasPlayEverBeenBlocked
= true;
3708 mHasEverBeenBlockedForAutoplay
= true;
3711 void HTMLMediaElement::PlayInternal(bool aHandlingUserInput
) {
3712 if (mPreloadAction
== HTMLMediaElement::PRELOAD_NONE
) {
3713 // The media load algorithm will be initiated by a user interaction.
3714 // We want to boost the channel priority for better responsiveness.
3715 // Note this must be done before UpdatePreloadAction() which will
3716 // update |mPreloadAction|.
3717 mUseUrgentStartForChannel
= true;
3720 StopSuspendingAfterFirstFrame();
3721 SetPlayedOrSeeked(true);
3723 // 4.8.12.8 - Step 4:
3724 // If the media element's networkState attribute has the value NETWORK_EMPTY,
3725 // invoke the media element's resource selection algorithm.
3727 if (mSuspendedForPreloadNone
) {
3728 ResumeLoad(PRELOAD_ENOUGH
);
3731 // 4.8.12.8 - Step 5:
3732 // If the playback has ended and the direction of playback is forwards,
3733 // seek to the earliest possible position of the media resource.
3735 // Even if we just did Load() or ResumeLoad(), we could already have a decoder
3736 // here if we managed to clone an existing decoder.
3738 if (mDecoder
->IsEnded()) {
3741 if (!mPausedForInactiveDocumentOrChannel
) {
3746 if (mCurrentPlayRangeStart
== -1.0) {
3747 mCurrentPlayRangeStart
= CurrentTime();
3750 const bool oldPaused
= mPaused
;
3752 mAutoplaying
= false;
3754 // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
3755 // and our preload status.
3756 AddRemoveSelfReference();
3757 UpdatePreloadAction();
3758 UpdateSrcMediaStreamPlaying();
3760 // Once play() has been called in a user generated event handler,
3761 // it is allowed to autoplay. Note: we can reach here when not in
3762 // a user generated event handler if our readyState has not yet
3763 // reached HAVE_METADATA.
3764 mIsBlessed
|= aHandlingUserInput
;
3766 // TODO: If the playback has ended, then the user agent must set
3767 // seek to the effective start.
3769 // 4.8.12.8 - Step 6:
3770 // If the media element's paused attribute is true, run the following steps:
3772 // 6.1. Change the value of paused to false. (Already done.)
3773 // This step is uplifted because the "block-media-playback" feature needs
3774 // the mPaused to be false before UpdateAudioChannelPlayingState() being
3777 // 6.2. If the show poster flag is true, set the element's show poster flag
3778 // to false and run the time marches on steps.
3780 mShowPoster
= false;
3781 if (mTextTrackManager
) {
3782 mTextTrackManager
->TimeMarchesOn();
3786 // 6.3. Queue a task to fire a simple event named play at the element.
3787 DispatchAsyncEvent(NS_LITERAL_STRING("play"));
3789 // 6.4. If the media element's readyState attribute has the value
3790 // HAVE_NOTHING, HAVE_METADATA, or HAVE_CURRENT_DATA, queue a task to
3791 // fire a simple event named waiting at the element.
3792 // Otherwise, the media element's readyState attribute has the value
3793 // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA: notify about playing for the
3795 switch (mReadyState
) {
3797 DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
3800 case HAVE_CURRENT_DATA
:
3801 DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
3803 case HAVE_FUTURE_DATA
:
3804 case HAVE_ENOUGH_DATA
:
3805 NotifyAboutPlaying();
3808 } else if (mReadyState
>= HAVE_FUTURE_DATA
) {
3809 // 7. Otherwise, if the media element's readyState attribute has the value
3810 // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, take pending play promises and
3811 // queue a task to resolve pending play promises with the result.
3812 AsyncResolvePendingPlayPromises();
3815 // 8. Set the media element's autoplaying flag to false. (Already done.)
3817 // 9. Return promise.
3818 // (Done in caller.)
3821 void HTMLMediaElement::MaybeDoLoad() {
3822 if (mNetworkState
== NETWORK_EMPTY
) {
3827 void HTMLMediaElement::UpdateWakeLock() {
3828 MOZ_ASSERT(NS_IsMainThread());
3829 // Ensure we have a wake lock if we're playing audibly. This ensures the
3830 // device doesn't sleep while playing.
3831 bool playing
= !mPaused
;
3832 bool isAudible
= Volume() > 0.0 && !mMuted
&& mIsAudioTrackAudible
;
3833 // WakeLock when playing audible media.
3834 if (playing
&& isAudible
) {
3835 CreateAudioWakeLockIfNeeded();
3837 ReleaseAudioWakeLockIfExists();
3841 void HTMLMediaElement::CreateAudioWakeLockIfNeeded() {
3843 RefPtr
<power::PowerManagerService
> pmService
=
3844 power::PowerManagerService::GetInstance();
3845 NS_ENSURE_TRUE_VOID(pmService
);
3848 mWakeLock
= pmService
->NewWakeLock(NS_LITERAL_STRING("audio-playing"),
3849 OwnerDoc()->GetInnerWindow(), rv
);
3853 void HTMLMediaElement::ReleaseAudioWakeLockIfExists() {
3856 mWakeLock
->Unlock(rv
);
3857 rv
.SuppressException();
3858 mWakeLock
= nullptr;
3862 void HTMLMediaElement::WakeLockRelease() { ReleaseAudioWakeLockIfExists(); }
3864 HTMLMediaElement::OutputMediaStream::OutputMediaStream()
3865 : mNextAvailableTrackID(1),
3866 mFinishWhenEnded(false),
3867 mCapturingAudioOnly(false),
3868 mCapturingDecoder(false),
3869 mCapturingMediaStream(false) {}
3871 HTMLMediaElement::OutputMediaStream::~OutputMediaStream() {
3872 for (auto pair
: mTrackPorts
) {
3873 pair
.second()->Destroy();
3877 void HTMLMediaElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
) {
3878 if (!this->Controls() || !aVisitor
.mEvent
->mFlags
.mIsTrusted
) {
3879 nsGenericHTMLElement::GetEventTargetParent(aVisitor
);
3883 HTMLInputElement
* el
= nullptr;
3884 nsCOMPtr
<nsINode
> node
;
3886 // We will need to trap pointer, touch, and mouse events within the media
3887 // element, allowing media control exclusive consumption on these events,
3888 // and preventing the content from handling them.
3889 switch (aVisitor
.mEvent
->mMessage
) {
3893 // Always prevent touchmove captured in video element from being handled by
3894 // content, since we always do that for touchstart.
3898 case eMouseDoubleClick
:
3901 aVisitor
.mCanHandle
= false;
3904 // The *move events however are only comsumed when the range input is being
3908 node
= do_QueryInterface(aVisitor
.mEvent
->mOriginalTarget
);
3909 if (node
->IsInNativeAnonymousSubtree() || node
->IsInUAWidget()) {
3910 if (node
->IsHTMLElement(nsGkAtoms::input
)) {
3911 // The node is a <input type="range">
3912 el
= static_cast<HTMLInputElement
*>(node
.get());
3913 } else if (node
->GetParentNode() &&
3914 node
->GetParentNode()->IsHTMLElement(nsGkAtoms::input
)) {
3915 // The node is a child of <input type="range">
3916 el
= static_cast<HTMLInputElement
*>(node
->GetParentNode());
3919 if (el
&& el
->IsDraggingRange()) {
3920 aVisitor
.mCanHandle
= false;
3923 nsGenericHTMLElement::GetEventTargetParent(aVisitor
);
3927 nsGenericHTMLElement::GetEventTargetParent(aVisitor
);
3932 bool HTMLMediaElement::ParseAttribute(int32_t aNamespaceID
, nsAtom
* aAttribute
,
3933 const nsAString
& aValue
,
3934 nsIPrincipal
* aMaybeScriptedPrincipal
,
3935 nsAttrValue
& aResult
) {
3936 // Mappings from 'preload' attribute strings to an enumeration.
3937 static const nsAttrValue::EnumTable kPreloadTable
[] = {
3938 {"", HTMLMediaElement::PRELOAD_ATTR_EMPTY
},
3939 {"none", HTMLMediaElement::PRELOAD_ATTR_NONE
},
3940 {"metadata", HTMLMediaElement::PRELOAD_ATTR_METADATA
},
3941 {"auto", HTMLMediaElement::PRELOAD_ATTR_AUTO
},
3944 if (aNamespaceID
== kNameSpaceID_None
) {
3945 if (ParseImageAttribute(aAttribute
, aValue
, aResult
)) {
3948 if (aAttribute
== nsGkAtoms::crossorigin
) {
3949 ParseCORSValue(aValue
, aResult
);
3952 if (aAttribute
== nsGkAtoms::preload
) {
3953 return aResult
.ParseEnumValue(aValue
, kPreloadTable
, false);
3957 return nsGenericHTMLElement::ParseAttribute(aNamespaceID
, aAttribute
, aValue
,
3958 aMaybeScriptedPrincipal
, aResult
);
3961 void HTMLMediaElement::DoneCreatingElement() {
3962 if (HasAttr(kNameSpaceID_None
, nsGkAtoms::muted
)) {
3963 mMuted
|= MUTED_BY_CONTENT
;
3967 bool HTMLMediaElement::IsHTMLFocusable(bool aWithMouse
, bool* aIsFocusable
,
3968 int32_t* aTabIndex
) {
3969 if (nsGenericHTMLElement::IsHTMLFocusable(aWithMouse
, aIsFocusable
,
3974 *aIsFocusable
= true;
3978 int32_t HTMLMediaElement::TabIndexDefault() { return 0; }
3980 nsresult
HTMLMediaElement::AfterSetAttr(int32_t aNameSpaceID
, nsAtom
* aName
,
3981 const nsAttrValue
* aValue
,
3982 const nsAttrValue
* aOldValue
,
3983 nsIPrincipal
* aMaybeScriptedPrincipal
,
3985 if (aNameSpaceID
== kNameSpaceID_None
) {
3986 if (aName
== nsGkAtoms::src
) {
3987 mSrcMediaSource
= nullptr;
3988 mSrcAttrTriggeringPrincipal
= nsContentUtils::GetAttrTriggeringPrincipal(
3989 this, aValue
? aValue
->GetStringValue() : EmptyString(),
3990 aMaybeScriptedPrincipal
);
3992 nsString srcStr
= aValue
->GetStringValue();
3993 nsCOMPtr
<nsIURI
> uri
;
3994 NewURIFromString(srcStr
, getter_AddRefs(uri
));
3995 if (uri
&& IsMediaSourceURI(uri
)) {
3996 nsresult rv
= NS_GetSourceForMediaSourceURI(
3997 uri
, getter_AddRefs(mSrcMediaSource
));
3998 if (NS_FAILED(rv
)) {
4000 GetCurrentSrc(spec
);
4001 AutoTArray
<nsString
, 1> params
= {spec
};
4002 ReportLoadError("MediaLoadInvalidURI", params
);
4006 } else if (aName
== nsGkAtoms::autoplay
) {
4009 StopSuspendingAfterFirstFrame();
4010 CheckAutoplayDataReady();
4012 // This attribute can affect AddRemoveSelfReference
4013 AddRemoveSelfReference();
4014 UpdatePreloadAction();
4016 } else if (aName
== nsGkAtoms::preload
) {
4017 UpdatePreloadAction();
4018 } else if (aName
== nsGkAtoms::loop
) {
4020 mDecoder
->SetLooping(!!aValue
);
4022 } else if (aName
== nsGkAtoms::controls
&& IsInComposedDoc()) {
4023 NotifyUAWidgetSetupOrChange();
4027 // Since AfterMaybeChangeAttr may call DoLoad, make sure that it is called
4028 // *after* any possible changes to mSrcMediaSource.
4030 AfterMaybeChangeAttr(aNameSpaceID
, aName
, aNotify
);
4033 return nsGenericHTMLElement::AfterSetAttr(
4034 aNameSpaceID
, aName
, aValue
, aOldValue
, aMaybeScriptedPrincipal
, aNotify
);
4037 nsresult
HTMLMediaElement::OnAttrSetButNotChanged(
4038 int32_t aNamespaceID
, nsAtom
* aName
, const nsAttrValueOrString
& aValue
,
4040 AfterMaybeChangeAttr(aNamespaceID
, aName
, aNotify
);
4042 return nsGenericHTMLElement::OnAttrSetButNotChanged(aNamespaceID
, aName
,
4046 void HTMLMediaElement::AfterMaybeChangeAttr(int32_t aNamespaceID
, nsAtom
* aName
,
4048 if (aNamespaceID
== kNameSpaceID_None
) {
4049 if (aName
== nsGkAtoms::src
) {
4055 nsresult
HTMLMediaElement::BindToTree(BindContext
& aContext
, nsINode
& aParent
) {
4056 nsresult rv
= nsGenericHTMLElement::BindToTree(aContext
, aParent
);
4058 if (IsInComposedDoc()) {
4059 // Construct Shadow Root so web content can be hidden in the DOM.
4060 AttachAndSetUAShadowRoot();
4061 NotifyUAWidgetSetupOrChange();
4064 // FIXME(emilio, bug 1555946): mUnboundFromTree doesn't make any sense, should
4065 // just use IsInComposedDoc() in the relevant places or something.
4066 mUnboundFromTree
= false;
4068 if (IsInUncomposedDoc()) {
4069 // The preload action depends on the value of the autoplay attribute.
4070 // It's value may have changed, so update it.
4071 UpdatePreloadAction();
4074 NotifyDecoderActivityChanges();
4080 void HTMLMediaElement::VideoDecodeSuspendTimerCallback(nsITimer
* aTimer
,
4082 MOZ_ASSERT(NS_IsMainThread());
4083 auto element
= static_cast<HTMLMediaElement
*>(aClosure
);
4084 element
->mVideoDecodeSuspendTime
.Start();
4085 element
->mVideoDecodeSuspendTimer
= nullptr;
4088 void HTMLMediaElement::HiddenVideoStart() {
4089 MOZ_ASSERT(NS_IsMainThread());
4090 mHiddenPlayTime
.Start();
4091 if (mVideoDecodeSuspendTimer
) {
4092 // Already started, just keep it running.
4095 NS_NewTimerWithFuncCallback(
4096 getter_AddRefs(mVideoDecodeSuspendTimer
), VideoDecodeSuspendTimerCallback
,
4097 this, StaticPrefs::MediaSuspendBkgndVideoDelayMs(),
4098 nsITimer::TYPE_ONE_SHOT
,
4099 "HTMLMediaElement::VideoDecodeSuspendTimerCallback",
4100 mMainThreadEventTarget
);
4103 void HTMLMediaElement::HiddenVideoStop() {
4104 MOZ_ASSERT(NS_IsMainThread());
4105 mHiddenPlayTime
.Pause();
4106 mVideoDecodeSuspendTime
.Pause();
4107 if (!mVideoDecodeSuspendTimer
) {
4110 mVideoDecodeSuspendTimer
->Cancel();
4111 mVideoDecodeSuspendTimer
= nullptr;
4114 void HTMLMediaElement::ReportTelemetry() {
4115 // Report telemetry for videos when a page is unloaded. We
4116 // want to know data on what state the video is at when
4117 // the user has exited.
4118 enum UnloadedState
{
4126 UnloadedState state
= OTHER
;
4129 } else if (Ended()) {
4131 } else if (Paused()) {
4134 // For buffering we check if the current playback position is at the end
4135 // of a buffered range, within a margin of error. We also consider to be
4136 // buffering if the last frame status was buffering and the ready state is
4137 // HAVE_CURRENT_DATA to account for times where we are in a buffering state
4138 // regardless of what actual data we have buffered.
4139 bool stalled
= false;
4140 RefPtr
<TimeRanges
> ranges
= Buffered();
4141 const double errorMargin
= 0.05;
4142 double t
= CurrentTime();
4143 TimeRanges::index_type index
= ranges
->Find(t
, errorMargin
);
4145 index
!= TimeRanges::NoIndex
&& (ranges
->End(index
) - t
) < errorMargin
;
4146 stalled
|= mDecoder
&&
4147 NextFrameStatus() ==
4148 MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING
&&
4149 mReadyState
== HAVE_CURRENT_DATA
;
4155 Telemetry::Accumulate(Telemetry::VIDEO_UNLOAD_STATE
, state
);
4156 LOG(LogLevel::Debug
, ("%p VIDEO_UNLOAD_STATE = %d", this, state
));
4158 FrameStatisticsData data
;
4160 if (HTMLVideoElement
* vid
= HTMLVideoElement::FromNodeOrNull(this)) {
4161 FrameStatistics
* stats
= vid
->GetFrameStatistics();
4163 data
= stats
->GetFrameStatisticsData();
4164 uint64_t parsedFrames
= stats
->GetParsedFrames();
4166 uint64_t droppedFrames
= stats
->GetDroppedFrames();
4167 MOZ_ASSERT(droppedFrames
<= parsedFrames
);
4168 // Dropped frames <= total frames, so 'percentage' cannot be higher than
4169 // 100 and therefore can fit in a uint32_t (that Telemetry takes).
4170 uint32_t percentage
= 100 * droppedFrames
/ parsedFrames
;
4171 LOG(LogLevel::Debug
,
4172 ("Reporting telemetry DROPPED_FRAMES_IN_VIDEO_PLAYBACK"));
4173 Telemetry::Accumulate(Telemetry::VIDEO_DROPPED_FRAMES_PROPORTION
,
4179 if (mMediaInfo
.HasVideo() && mMediaInfo
.mVideo
.mImage
.height
> 0) {
4180 // We have a valid video.
4181 double playTime
= mPlayTime
.Total();
4182 double hiddenPlayTime
= mHiddenPlayTime
.Total();
4183 double videoDecodeSuspendTime
= mVideoDecodeSuspendTime
.Total();
4185 Telemetry::Accumulate(Telemetry::VIDEO_PLAY_TIME_MS
,
4186 SECONDS_TO_MS(playTime
));
4187 LOG(LogLevel::Debug
, ("%p VIDEO_PLAY_TIME_MS = %f", this, playTime
));
4189 Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_MS
,
4190 SECONDS_TO_MS(hiddenPlayTime
));
4191 LOG(LogLevel::Debug
,
4192 ("%p VIDEO_HIDDEN_PLAY_TIME_MS = %f", this, hiddenPlayTime
));
4194 if (playTime
> 0.0) {
4195 // We have actually played something -> Report some valid-video telemetry.
4197 // Keyed by audio+video or video alone, and by a resolution range.
4198 nsCString
key(mMediaInfo
.HasAudio() ? "AV," : "V,");
4199 static const struct {
4202 } sResolutions
[] = {{240, "0<h<=240"}, {480, "240<h<=480"},
4203 {576, "480<h<=576"}, {720, "576<h<=720"},
4204 {1080, "720<h<=1080"}, {2160, "1080<h<=2160"}};
4205 const char* resolution
= "h>2160";
4206 int32_t height
= mMediaInfo
.mVideo
.mImage
.height
;
4207 for (const auto& res
: sResolutions
) {
4208 if (height
<= res
.mH
) {
4209 resolution
= res
.mRes
;
4213 key
.AppendASCII(resolution
);
4215 uint32_t hiddenPercentage
=
4216 uint32_t(hiddenPlayTime
/ playTime
* 100.0 + 0.5);
4217 Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE
, key
,
4219 // Also accumulate all percentages in an "All" key.
4220 Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE
,
4221 NS_LITERAL_CSTRING("All"), hiddenPercentage
);
4222 LOG(LogLevel::Debug
,
4223 ("%p VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE = %u, keys: '%s' and 'All'",
4224 this, hiddenPercentage
, key
.get()));
4226 uint32_t videoDecodeSuspendPercentage
=
4227 uint32_t(videoDecodeSuspendTime
/ playTime
* 100.0 + 0.5);
4228 Telemetry::Accumulate(Telemetry::VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE
,
4229 key
, videoDecodeSuspendPercentage
);
4230 Telemetry::Accumulate(Telemetry::VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE
,
4231 NS_LITERAL_CSTRING("All"),
4232 videoDecodeSuspendPercentage
);
4233 LOG(LogLevel::Debug
,
4234 ("%p VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE = %u, keys: '%s' and "
4236 this, videoDecodeSuspendPercentage
, key
.get()));
4238 if (data
.mInterKeyframeCount
!= 0) {
4239 uint32_t average_ms
= uint32_t(std::min
<uint64_t>(
4240 double(data
.mInterKeyframeSum_us
) /
4241 double(data
.mInterKeyframeCount
) / 1000.0 +
4244 Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS
, key
,
4246 Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS
,
4247 NS_LITERAL_CSTRING("All"), average_ms
);
4248 LOG(LogLevel::Debug
,
4249 ("%p VIDEO_INTER_KEYFRAME_AVERAGE_MS = %u, keys: '%s' and 'All'",
4250 this, average_ms
, key
.get()));
4252 uint32_t max_ms
= uint32_t(std::min
<uint64_t>(
4253 (data
.mInterKeyFrameMax_us
+ 500) / 1000, UINT32_MAX
));
4254 Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS
, key
,
4256 Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS
,
4257 NS_LITERAL_CSTRING("All"), max_ms
);
4258 LOG(LogLevel::Debug
,
4259 ("%p VIDEO_INTER_KEYFRAME_MAX_MS = %u, keys: '%s' and 'All'", this,
4260 max_ms
, key
.get()));
4262 // Here, we have played *some* of the video, but didn't get more than 1
4263 // keyframe. Report '0' if we have played for longer than the video-
4264 // decode-suspend delay (showing recovery would be difficult).
4265 uint32_t suspendDelay_ms
= StaticPrefs::MediaSuspendBkgndVideoDelayMs();
4266 if (uint32_t(playTime
* 1000.0) > suspendDelay_ms
) {
4267 Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS
, key
, 0);
4268 Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS
,
4269 NS_LITERAL_CSTRING("All"), 0);
4270 LOG(LogLevel::Debug
,
4271 ("%p VIDEO_INTER_KEYFRAME_MAX_MS = 0 (only 1 keyframe), keys: "
4280 void HTMLMediaElement::UnbindFromTree(bool aNullParent
) {
4281 mUnboundFromTree
= true;
4282 mVisibilityState
= Visibility::Untracked
;
4284 if (IsInComposedDoc()) {
4285 NotifyUAWidgetTeardown();
4288 nsGenericHTMLElement::UnbindFromTree(aNullParent
);
4290 MOZ_ASSERT(IsHidden());
4291 NotifyDecoderActivityChanges();
4293 RefPtr
<HTMLMediaElement
> self(this);
4294 nsCOMPtr
<nsIRunnable
> task
=
4295 NS_NewRunnableFunction("dom::HTMLMediaElement::UnbindFromTree", [self
]() {
4296 if (self
->mUnboundFromTree
) {
4300 RunInStableState(task
);
4304 CanPlayStatus
HTMLMediaElement::GetCanPlay(
4305 const nsAString
& aType
, DecoderDoctorDiagnostics
* aDiagnostics
) {
4306 Maybe
<MediaContainerType
> containerType
= MakeMediaContainerType(aType
);
4307 if (!containerType
) {
4310 CanPlayStatus status
=
4311 DecoderTraits::CanHandleContainerType(*containerType
, aDiagnostics
);
4312 if (status
== CANPLAY_YES
&&
4313 (*containerType
).ExtendedType().Codecs().IsEmpty()) {
4314 // Per spec: 'Generally, a user agent should never return "probably" for a
4315 // type that allows the `codecs` parameter if that parameter is not
4316 // present.' As all our currently-supported types allow for `codecs`, we can
4317 // do this check here.
4318 // TODO: Instead, missing `codecs` should be checked in each decoder's
4319 // `IsSupportedType` call from `CanHandleCodecsType()`.
4321 return CANPLAY_MAYBE
;
4326 void HTMLMediaElement::CanPlayType(const nsAString
& aType
, nsAString
& aResult
) {
4327 DecoderDoctorDiagnostics diagnostics
;
4328 CanPlayStatus canPlay
= GetCanPlay(aType
, &diagnostics
);
4329 diagnostics
.StoreFormatDiagnostics(OwnerDoc(), aType
, canPlay
!= CANPLAY_NO
,
4336 aResult
.AssignLiteral("probably");
4339 aResult
.AssignLiteral("maybe");
4342 MOZ_ASSERT_UNREACHABLE("Unexpected case.");
4346 LOG(LogLevel::Debug
,
4347 ("%p CanPlayType(%s) = \"%s\"", this, NS_ConvertUTF16toUTF8(aType
).get(),
4348 NS_ConvertUTF16toUTF8(aResult
).get()));
4351 void HTMLMediaElement::AssertReadyStateIsNothing() {
4352 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4353 if (mReadyState
!= HAVE_NOTHING
) {
4356 "readyState=%d networkState=%d mLoadWaitStatus=%d "
4357 "mSourceLoadCandidate=%d "
4358 "mIsLoadingFromSourceChildren=%d mPreloadAction=%d "
4359 "mSuspendedForPreloadNone=%d error=%d",
4360 int(mReadyState
), int(mNetworkState
), int(mLoadWaitStatus
),
4361 !!mSourceLoadCandidate
, mIsLoadingFromSourceChildren
,
4362 int(mPreloadAction
), mSuspendedForPreloadNone
,
4363 GetError() ? GetError()->Code() : 0);
4364 MOZ_CRASH_UNSAFE_PRINTF("ReadyState should be HAVE_NOTHING! %s", buf
);
4369 nsresult
HTMLMediaElement::InitializeDecoderAsClone(
4370 ChannelMediaDecoder
* aOriginal
) {
4371 NS_ASSERTION(mLoadingSrc
, "mLoadingSrc must already be set");
4372 NS_ASSERTION(mDecoder
== nullptr, "Shouldn't have a decoder");
4373 AssertReadyStateIsNothing();
4375 MediaDecoderInit
decoderInit(
4376 this, mMuted
? 0.0 : mVolume
, mPreservesPitch
,
4377 ClampPlaybackRate(mPlaybackRate
),
4378 mPreloadAction
== HTMLMediaElement::PRELOAD_METADATA
, mHasSuspendTaint
,
4379 HasAttr(kNameSpaceID_None
, nsGkAtoms::loop
), aOriginal
->ContainerType());
4381 RefPtr
<ChannelMediaDecoder
> decoder
= aOriginal
->Clone(decoderInit
);
4382 if (!decoder
) return NS_ERROR_FAILURE
;
4384 LOG(LogLevel::Debug
,
4385 ("%p Cloned decoder %p from %p", this, decoder
.get(), aOriginal
));
4387 return FinishDecoderSetup(decoder
);
4390 template <typename DecoderType
, typename
... LoadArgs
>
4391 nsresult
HTMLMediaElement::SetupDecoder(DecoderType
* aDecoder
,
4392 LoadArgs
&&... aArgs
) {
4393 LOG(LogLevel::Debug
, ("%p Created decoder %p for type %s", this, aDecoder
,
4394 aDecoder
->ContainerType().OriginalString().Data()));
4396 nsresult rv
= aDecoder
->Load(std::forward
<LoadArgs
>(aArgs
)...);
4397 if (NS_FAILED(rv
)) {
4398 aDecoder
->Shutdown();
4399 LOG(LogLevel::Debug
, ("%p Failed to load for decoder %p", this, aDecoder
));
4403 rv
= FinishDecoderSetup(aDecoder
);
4404 // Only ChannelMediaDecoder supports resource cloning.
4405 if (IsSame
<DecoderType
, ChannelMediaDecoder
>::value
&& NS_SUCCEEDED(rv
)) {
4406 AddMediaElementToURITable();
4408 MediaElementTableCount(this, mLoadingSrc
) == 1,
4409 "Media element should have single table entry if decode initialized");
4415 nsresult
HTMLMediaElement::InitializeDecoderForChannel(
4416 nsIChannel
* aChannel
, nsIStreamListener
** aListener
) {
4417 NS_ASSERTION(mLoadingSrc
, "mLoadingSrc must already be set");
4418 AssertReadyStateIsNothing();
4420 DecoderDoctorDiagnostics diagnostics
;
4422 nsAutoCString mimeType
;
4423 aChannel
->GetContentType(mimeType
);
4424 NS_ASSERTION(!mimeType
.IsEmpty(), "We should have the Content-Type.");
4425 NS_ConvertUTF8toUTF16
mimeUTF16(mimeType
);
4427 RefPtr
<HTMLMediaElement
> self
= this;
4428 auto reportCanPlay
= [&, self
](bool aCanPlay
) {
4429 diagnostics
.StoreFormatDiagnostics(self
->OwnerDoc(), mimeUTF16
, aCanPlay
,
4433 self
->GetCurrentSrc(src
);
4434 AutoTArray
<nsString
, 2> params
= {mimeUTF16
, src
};
4435 self
->ReportLoadError("MediaLoadUnsupportedMimeType", params
);
4439 auto onExit
= MakeScopeExit([self
] {
4440 if (self
->mChannelLoader
) {
4441 self
->mChannelLoader
->Done();
4442 self
->mChannelLoader
= nullptr;
4446 Maybe
<MediaContainerType
> containerType
= MakeMediaContainerType(mimeType
);
4447 if (!containerType
) {
4448 reportCanPlay(false);
4449 return NS_ERROR_FAILURE
;
4452 MediaDecoderInit
decoderInit(
4453 this, mMuted
? 0.0 : mVolume
, mPreservesPitch
,
4454 ClampPlaybackRate(mPlaybackRate
),
4455 mPreloadAction
== HTMLMediaElement::PRELOAD_METADATA
, mHasSuspendTaint
,
4456 HasAttr(kNameSpaceID_None
, nsGkAtoms::loop
), *containerType
);
4458 #ifdef MOZ_ANDROID_HLS_SUPPORT
4459 if (HLSDecoder::IsSupportedType(*containerType
)) {
4460 RefPtr
<HLSDecoder
> decoder
= HLSDecoder::Create(decoderInit
);
4462 reportCanPlay(false);
4463 return NS_ERROR_OUT_OF_MEMORY
;
4465 reportCanPlay(true);
4466 return SetupDecoder(decoder
.get(), aChannel
);
4470 RefPtr
<ChannelMediaDecoder
> decoder
=
4471 ChannelMediaDecoder::Create(decoderInit
, &diagnostics
);
4473 reportCanPlay(false);
4474 return NS_ERROR_FAILURE
;
4477 reportCanPlay(true);
4478 bool isPrivateBrowsing
= NodePrincipal()->GetPrivateBrowsingId() > 0;
4479 return SetupDecoder(decoder
.get(), aChannel
, isPrivateBrowsing
, aListener
);
4482 nsresult
HTMLMediaElement::FinishDecoderSetup(MediaDecoder
* aDecoder
) {
4483 ChangeNetworkState(NETWORK_LOADING
);
4485 // Set mDecoder now so if methods like GetCurrentSrc get called between
4486 // here and Load(), they work.
4487 SetDecoder(aDecoder
);
4489 // Notify the decoder of the initial activity status.
4490 NotifyDecoderActivityChanges();
4492 // Update decoder principal before we start decoding, since it
4493 // can affect how we feed data to MediaStreams
4494 NotifyDecoderPrincipalChanged();
4496 // Set sink device if we have one. Otherwise the default is used.
4497 if (mSink
.second()) {
4499 ->SetSink(mSink
.second())
4501 ->Then(mAbstractMainThread
, __func__
,
4502 [](const GenericPromise::ResolveOrRejectValue
& aValue
) {
4503 MOZ_ASSERT(aValue
.IsResolve() && !aValue
.ResolveValue());
4510 // Set CORSMode now before any streams are added. It won't change over time.
4511 mDecoder
->SetOutputStreamCORSMode(mCORSMode
);
4513 if (!mOutputStreams
.IsEmpty()) {
4514 mDecoder
->SetNextOutputStreamTrackID(
4515 mNextAvailableMediaDecoderOutputTrackID
);
4518 for (OutputMediaStream
& ms
: mOutputStreams
) {
4519 if (ms
.mCapturingMediaStream
) {
4520 MOZ_ASSERT(!ms
.mCapturingDecoder
);
4524 ms
.mCapturingDecoder
= true;
4525 aDecoder
->AddOutputStream(ms
.mStream
);
4529 if (mMediaKeys
->GetCDMProxy()) {
4530 mDecoder
->SetCDMProxy(mMediaKeys
->GetCDMProxy());
4532 // CDM must have crashed.
4534 return NS_ERROR_FAILURE
;
4538 if (mChannelLoader
) {
4539 mChannelLoader
->Done();
4540 mChannelLoader
= nullptr;
4543 // We may want to suspend the new stream now.
4544 // This will also do an AddRemoveSelfReference.
4545 NotifyOwnerDocumentActivityChanged();
4547 if (mPausedForInactiveDocumentOrChannel
) {
4548 mDecoder
->Suspend();
4552 SetPlayedOrSeeked(true);
4553 if (!mPausedForInactiveDocumentOrChannel
) {
4558 MaybeBeginCloningVisually();
4563 class HTMLMediaElement::MediaStreamTrackListener
4564 : public DOMMediaStream::TrackListener
{
4566 explicit MediaStreamTrackListener(HTMLMediaElement
* aElement
)
4567 : mElement(aElement
) {}
4569 void NotifyTrackAdded(const RefPtr
<MediaStreamTrack
>& aTrack
) override
{
4573 mElement
->NotifyMediaStreamTrackAdded(aTrack
);
4576 void NotifyTrackRemoved(const RefPtr
<MediaStreamTrack
>& aTrack
) override
{
4580 mElement
->NotifyMediaStreamTrackRemoved(aTrack
);
4583 void NotifyActive() override
{
4588 // mediacapture-main says:
4589 // Note that once ended equals true the HTMLVideoElement will not play media
4590 // even if new MediaStreamTracks are added to the MediaStream (causing it to
4591 // return to the active state) unless autoplay is true or the web
4592 // application restarts the element, e.g., by calling play().
4594 // This is vague on exactly how to go from becoming active to playing, when
4595 // autoplaying. However, per the media element spec, to play an autoplaying
4596 // media element, we must load the source and reach readyState
4597 // HAVE_ENOUGH_DATA [1]. Hence, a MediaStream being assigned to a media
4598 // element and becoming active runs the load algorithm, so that it can
4599 // eventually be played.
4602 // https://html.spec.whatwg.org/multipage/media.html#ready-states:event-media-play
4604 LOG(LogLevel::Debug
, ("%p, mSrcStream %p became active, checking if we "
4605 "need to run the load algorithm",
4606 mElement
.get(), mElement
->mSrcStream
.get()));
4607 if (!mElement
->IsPlaybackEnded()) {
4610 if (!mElement
->Autoplay()) {
4613 LOG(LogLevel::Info
, ("%p, mSrcStream %p became active on autoplaying, "
4614 "ended element. Reloading.",
4615 mElement
.get(), mElement
->mSrcStream
.get()));
4619 void NotifyInactive() override
{
4623 if (mElement
->IsPlaybackEnded()) {
4626 LOG(LogLevel::Debug
, ("%p, mSrcStream %p became inactive", mElement
.get(),
4627 mElement
->mSrcStream
.get()));
4628 MOZ_ASSERT(!mElement
->mSrcStream
->Active());
4629 mElement
->PlaybackEnded();
4630 mElement
->UpdateReadyStateInternal();
4634 const WeakPtr
<HTMLMediaElement
> mElement
;
4637 void HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags
) {
4641 // We might be in cycle collection with mSrcStream->GetPlaybackStream()
4642 // already returning null due to unlinking.
4644 MediaStream
* stream
= GetSrcMediaStream();
4645 MediaStreamGraph
* graph
= stream
? stream
->Graph() : nullptr;
4646 bool shouldPlay
= !(aFlags
& REMOVING_SRC_STREAM
) && !mPaused
&&
4647 !mPausedForInactiveDocumentOrChannel
&& stream
;
4648 if (shouldPlay
== mSrcStreamIsPlaying
) {
4651 mSrcStreamIsPlaying
= shouldPlay
;
4653 LOG(LogLevel::Debug
,
4654 ("MediaElement %p %s playback of DOMMediaStream %p", this,
4655 shouldPlay
? "Setting up" : "Removing", mSrcStream
.get()));
4658 mSrcStreamPlaybackEnded
= false;
4659 mSrcStreamGraphTimeOffset
+=
4660 graph
->CurrentTime() - mSrcStreamPausedGraphTime
.ref();
4661 mSrcStreamPausedGraphTime
= Nothing();
4663 mWatchManager
.Watch(graph
->CurrentTime(),
4664 &HTMLMediaElement::UpdateSrcStreamTime
);
4666 stream
->AddAudioOutput(this);
4667 SetVolumeInternal();
4668 if (mSink
.second()) {
4670 "setSinkId() when playing a MediaStream is not supported yet and "
4674 VideoFrameContainer
* container
= GetVideoFrameContainer();
4675 if (mSelectedVideoStreamTrack
&& container
) {
4676 mSelectedVideoStreamTrack
->AddVideoOutput(container
);
4677 MaybeBeginCloningVisually();
4680 SetCapturedOutputStreamsEnabled(true); // Unmute
4681 // If the input is a media stream, we don't check its data and always regard
4682 // it as audible when it's playing.
4683 SetAudibleState(true);
4686 MOZ_DIAGNOSTIC_ASSERT(mSrcStreamPausedGraphTime
.isNothing());
4687 mSrcStreamPausedGraphTime
= Some(graph
->CurrentTime().Ref());
4689 mWatchManager
.Unwatch(graph
->CurrentTime(),
4690 &HTMLMediaElement::UpdateSrcStreamTime
);
4692 stream
->RemoveAudioOutput(this);
4693 VideoFrameContainer
* container
= GetVideoFrameContainer();
4694 if (mSelectedVideoStreamTrack
&& container
) {
4695 mSelectedVideoStreamTrack
->RemoveVideoOutput(container
);
4697 HTMLVideoElement
* self
= static_cast<HTMLVideoElement
*>(this);
4698 if (self
->VideoWidth() <= 1 && self
->VideoHeight() <= 1) {
4699 // MediaInfo uses dummy values of 1 for width and height to
4700 // mark video as valid. We need a new first-frame listener
4701 // if size is 0x0 or 1x1.
4702 if (!mFirstFrameListener
) {
4703 mFirstFrameListener
=
4704 new FirstFrameListener(container
, mAbstractMainThread
);
4706 mSelectedVideoStreamTrack
->AddVideoOutput(mFirstFrameListener
);
4710 SetCapturedOutputStreamsEnabled(false); // Mute
4712 // If stream is null, then DOMMediaStream::Destroy must have been
4713 // called and that will remove all listeners/outputs.
4717 void HTMLMediaElement::UpdateSrcStreamTime() {
4718 MOZ_ASSERT(NS_IsMainThread());
4720 if (mSrcStreamPlaybackEnded
) {
4721 // We do a separate FireTimeUpdate() when this is set.
4725 FireTimeUpdate(true);
4728 void HTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream
* aStream
) {
4729 NS_ASSERTION(!mSrcStream
&& !mFirstFrameListener
,
4730 "Should have been ended already");
4732 mSrcStream
= aStream
;
4734 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
4739 mSrcStreamPausedGraphTime
= Some(0);
4740 if (MediaStream
* stream
= GetSrcMediaStream()) {
4741 if (MediaStreamGraph
* graph
= stream
->Graph()) {
4742 // The current graph time will represent 0 for this media element.
4743 mSrcStreamGraphTimeOffset
= graph
->CurrentTime();
4744 mSrcStreamPausedGraphTime
= Some(mSrcStreamGraphTimeOffset
);
4748 UpdateSrcMediaStreamPlaying();
4750 // If we pause this media element, track changes in the underlying stream
4751 // will continue to fire events at this element and alter its track list.
4752 // That's simpler than delaying the events, but probably confusing...
4753 nsTArray
<RefPtr
<MediaStreamTrack
>> tracks
;
4754 mSrcStream
->GetTracks(tracks
);
4755 for (const RefPtr
<MediaStreamTrack
>& track
: tracks
) {
4756 NotifyMediaStreamTrackAdded(track
);
4759 mMediaStreamTrackListener
= MakeUnique
<MediaStreamTrackListener
>(this);
4760 mSrcStream
->RegisterTrackListener(mMediaStreamTrackListener
.get());
4762 mSrcStream
->AddPrincipalChangeObserver(this);
4763 mSrcStreamVideoPrincipal
= mSrcStream
->GetVideoPrincipal();
4765 ChangeNetworkState(NETWORK_IDLE
);
4766 ChangeDelayLoadStatus(false);
4768 // FirstFrameLoaded() will be called when the stream has tracks.
4771 void HTMLMediaElement::EndSrcMediaStreamPlayback() {
4772 MOZ_ASSERT(mSrcStream
);
4774 UpdateSrcMediaStreamPlaying(REMOVING_SRC_STREAM
);
4776 if (mFirstFrameListener
) {
4777 mSelectedVideoStreamTrack
->RemoveVideoOutput(mFirstFrameListener
);
4779 mSelectedVideoStreamTrack
= nullptr;
4780 mFirstFrameListener
= nullptr;
4782 mSrcStream
->UnregisterTrackListener(mMediaStreamTrackListener
.get());
4783 mMediaStreamTrackListener
= nullptr;
4784 mSrcStreamTracksAvailable
= false;
4785 mSrcStreamPlaybackEnded
= false;
4787 mSrcStream
->RemovePrincipalChangeObserver(this);
4788 mSrcStreamVideoPrincipal
= nullptr;
4790 for (OutputMediaStream
& ms
: mOutputStreams
) {
4791 for (auto pair
: ms
.mTrackPorts
) {
4792 pair
.second()->Destroy();
4794 ms
.mTrackPorts
.Clear();
4797 mSrcStream
= nullptr;
4800 static already_AddRefed
<AudioTrack
> CreateAudioTrack(
4801 AudioStreamTrack
* aStreamTrack
, nsIGlobalObject
* aOwnerGlobal
) {
4804 aStreamTrack
->GetId(id
);
4805 aStreamTrack
->GetLabel(label
, CallerType::System
);
4807 return MediaTrackList::CreateAudioTrack(
4808 aOwnerGlobal
, id
, NS_LITERAL_STRING("main"), label
, EmptyString(), true);
4811 static already_AddRefed
<VideoTrack
> CreateVideoTrack(
4812 VideoStreamTrack
* aStreamTrack
, nsIGlobalObject
* aOwnerGlobal
) {
4815 aStreamTrack
->GetId(id
);
4816 aStreamTrack
->GetLabel(label
, CallerType::System
);
4818 return MediaTrackList::CreateVideoTrack(aOwnerGlobal
, id
,
4819 NS_LITERAL_STRING("main"), label
,
4820 EmptyString(), aStreamTrack
);
4823 void HTMLMediaElement::NotifyMediaStreamTrackAdded(
4824 const RefPtr
<MediaStreamTrack
>& aTrack
) {
4827 if (aTrack
->Ended()) {
4835 LOG(LogLevel::Debug
, ("%p, Adding %sTrack with id %s", this,
4836 aTrack
->AsAudioStreamTrack() ? "Audio" : "Video",
4837 NS_ConvertUTF16toUTF8(id
).get()));
4840 if (AudioStreamTrack
* t
= aTrack
->AsAudioStreamTrack()) {
4841 MOZ_DIAGNOSTIC_ASSERT(AudioTracks(), "Element can't have been unlinked");
4842 RefPtr
<AudioTrack
> audioTrack
=
4843 CreateAudioTrack(t
, AudioTracks()->GetOwnerGlobal());
4844 AudioTracks()->AddTrack(audioTrack
);
4845 } else if (VideoStreamTrack
* t
= aTrack
->AsVideoStreamTrack()) {
4846 // TODO: Fix this per the spec on bug 1273443.
4850 MOZ_DIAGNOSTIC_ASSERT(VideoTracks(), "Element can't have been unlinked");
4851 RefPtr
<VideoTrack
> videoTrack
=
4852 CreateVideoTrack(t
, VideoTracks()->GetOwnerGlobal());
4853 VideoTracks()->AddTrack(videoTrack
);
4854 // New MediaStreamTrack added, set the new added video track as selected
4855 // video track when there is no selected track.
4856 if (VideoTracks()->SelectedIndex() == -1) {
4857 MOZ_ASSERT(!mSelectedVideoStreamTrack
);
4858 videoTrack
->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS
);
4862 UpdateReadyStateInternal();
4864 if (!mSrcStreamTracksAvailable
) {
4865 mAbstractMainThread
->Dispatch(NS_NewRunnableFunction(
4866 "HTMLMediaElement::NotifyMediaStreamTrackAdded->FirstFrameLoaded",
4867 [this, self
= RefPtr
<HTMLMediaElement
>(this), stream
= mSrcStream
]() {
4868 if (!mSrcStream
|| mSrcStream
!= stream
) {
4872 LOG(LogLevel::Debug
,
4873 ("MediaElement %p MediaStream tracks available", this));
4875 mSrcStreamTracksAvailable
= true;
4878 UpdateReadyStateInternal();
4883 void HTMLMediaElement::NotifyMediaStreamTrackRemoved(
4884 const RefPtr
<MediaStreamTrack
>& aTrack
) {
4890 LOG(LogLevel::Debug
, ("%p, Removing %sTrack with id %s", this,
4891 aTrack
->AsAudioStreamTrack() ? "Audio" : "Video",
4892 NS_ConvertUTF16toUTF8(id
).get()));
4894 MOZ_DIAGNOSTIC_ASSERT(AudioTracks() && VideoTracks(),
4895 "Element can't have been unlinked");
4896 if (MediaTrack
* t
= AudioTracks()->GetTrackById(id
)) {
4897 AudioTracks()->RemoveTrack(t
);
4898 } else if (MediaTrack
* t
= VideoTracks()->GetTrackById(id
)) {
4899 VideoTracks()->RemoveTrack(t
);
4901 NS_ASSERTION(aTrack
->AsVideoStreamTrack() && !IsVideo(),
4902 "MediaStreamTrack ended but did not exist in track lists. "
4903 "This is only allowed if a video element ends and we are an "
4909 void HTMLMediaElement::ProcessMediaFragmentURI() {
4910 nsMediaFragmentURIParser
parser(mLoadingSrc
);
4912 if (mDecoder
&& parser
.HasEndTime()) {
4913 mFragmentEnd
= parser
.GetEndTime();
4916 if (parser
.HasStartTime()) {
4917 SetCurrentTime(parser
.GetStartTime());
4918 mFragmentStart
= parser
.GetStartTime();
4922 void HTMLMediaElement::MetadataLoaded(const MediaInfo
* aInfo
,
4923 UniquePtr
<const MetadataTags
> aTags
) {
4924 MOZ_ASSERT(NS_IsMainThread());
4926 SetMediaInfo(*aInfo
);
4929 aInfo
->IsEncrypted() || mPendingEncryptedInitData
.IsEncrypted();
4930 mTags
= std::move(aTags
);
4931 mLoadedDataFired
= false;
4932 ChangeReadyState(HAVE_METADATA
);
4934 DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
4935 if (IsVideo() && HasVideo()) {
4936 DispatchAsyncEvent(NS_LITERAL_STRING("resize"));
4938 NS_ASSERTION(!HasVideo() || (mMediaInfo
.mVideo
.mDisplay
.width
> 0 &&
4939 mMediaInfo
.mVideo
.mDisplay
.height
> 0),
4940 "Video resolution must be known on 'loadedmetadata'");
4941 DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
4943 if (mBlockedAsWithoutMetadata
&& !HasAudio()) {
4944 mBlockedAsWithoutMetadata
= false;
4946 Telemetry::ScalarID::MEDIA_BLOCKED_NO_METADATA_ENDUP_NO_AUDIO_TRACK
, 1);
4949 if (mDecoder
&& mDecoder
->IsTransportSeekable() &&
4950 mDecoder
->IsMediaSeekable()) {
4951 ProcessMediaFragmentURI();
4952 mDecoder
->SetFragmentEndTime(mFragmentEnd
);
4955 // We only support playback of encrypted content via MSE by default.
4956 if (!mMediaSource
&& Preferences::GetBool("media.eme.mse-only", true)) {
4958 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR
,
4959 "Encrypted content not supported outside of MSE"));
4963 // Dispatch a distinct 'encrypted' event for each initData we have.
4964 for (const auto& initData
: mPendingEncryptedInitData
.mInitDatas
) {
4965 DispatchEncrypted(initData
.mInitData
, initData
.mType
);
4967 mPendingEncryptedInitData
.Reset();
4970 if (IsVideo() && aInfo
->HasVideo()) {
4971 // We are a video element playing video so update the screen wakelock
4972 NotifyOwnerDocumentActivityChanged();
4975 if (mDefaultPlaybackStartPosition
!= 0.0) {
4976 SetCurrentTime(mDefaultPlaybackStartPosition
);
4977 mDefaultPlaybackStartPosition
= 0.0;
4980 UpdateReadyStateInternal();
4986 for (OutputMediaStream
& ms
: mOutputStreams
) {
4987 if (AudioTracks()) {
4988 for (size_t i
= 0; i
< AudioTracks()->Length(); ++i
) {
4989 AudioTrack
* t
= (*AudioTracks())[i
];
4991 AddCaptureMediaTrackToOutputStream(t
, ms
);
4995 if (VideoTracks() && IsVideo() && !ms
.mCapturingAudioOnly
) {
4996 // Only add video tracks if we're a video element and the output stream
4998 for (size_t i
= 0; i
< VideoTracks()->Length(); ++i
) {
4999 VideoTrack
* t
= (*VideoTracks())[i
];
5000 if (t
->Selected()) {
5001 AddCaptureMediaTrackToOutputStream(t
, ms
);
5008 void HTMLMediaElement::FirstFrameLoaded() {
5009 LOG(LogLevel::Debug
,
5010 ("%p, FirstFrameLoaded() mFirstFrameLoaded=%d mWaitingForKey=%d", this,
5011 mFirstFrameLoaded
, mWaitingForKey
));
5013 NS_ASSERTION(!mSuspendedAfterFirstFrame
, "Should not have already suspended");
5015 if (!mFirstFrameLoaded
) {
5016 mFirstFrameLoaded
= true;
5017 UpdateReadyStateInternal();
5020 ChangeDelayLoadStatus(false);
5022 if (mDecoder
&& mAllowSuspendAfterFirstFrame
&& mPaused
&&
5023 !HasAttr(kNameSpaceID_None
, nsGkAtoms::autoplay
) &&
5024 mPreloadAction
== HTMLMediaElement::PRELOAD_METADATA
) {
5025 mSuspendedAfterFirstFrame
= true;
5026 mDecoder
->Suspend();
5030 void HTMLMediaElement::NetworkError(const MediaResult
& aError
) {
5031 if (mReadyState
== HAVE_NOTHING
) {
5032 NoSupportedMediaSourceError(aError
.Description());
5034 Error(MEDIA_ERR_NETWORK
);
5038 void HTMLMediaElement::DecodeError(const MediaResult
& aError
) {
5041 AutoTArray
<nsString
, 1> params
= {src
};
5042 ReportLoadError("MediaLoadDecodeError", params
);
5044 DecoderDoctorDiagnostics diagnostics
;
5045 diagnostics
.StoreDecodeError(OwnerDoc(), aError
, src
, __func__
);
5047 if (AudioTracks()) {
5048 AudioTracks()->EmptyTracks();
5050 if (VideoTracks()) {
5051 VideoTracks()->EmptyTracks();
5053 if (mIsLoadingFromSourceChildren
) {
5054 mErrorSink
->ResetError();
5055 if (mSourceLoadCandidate
) {
5056 DispatchAsyncSourceError(mSourceLoadCandidate
);
5057 QueueLoadFromSourceTask();
5059 NS_WARNING("Should know the source we were loading from!");
5061 } else if (mReadyState
== HAVE_NOTHING
) {
5062 NoSupportedMediaSourceError(aError
.Description());
5064 Error(MEDIA_ERR_DECODE
, aError
.Description());
5068 void HTMLMediaElement::DecodeWarning(const MediaResult
& aError
) {
5071 DecoderDoctorDiagnostics diagnostics
;
5072 diagnostics
.StoreDecodeWarning(OwnerDoc(), aError
, src
, __func__
);
5075 bool HTMLMediaElement::HasError() const { return GetError(); }
5077 void HTMLMediaElement::LoadAborted() { Error(MEDIA_ERR_ABORTED
); }
5079 void HTMLMediaElement::Error(uint16_t aErrorCode
,
5080 const nsACString
& aErrorDetails
) {
5081 mErrorSink
->SetError(aErrorCode
, aErrorDetails
);
5082 ChangeDelayLoadStatus(false);
5083 UpdateAudioChannelPlayingState();
5086 void HTMLMediaElement::PlaybackEnded() {
5087 // We changed state which can affect AddRemoveSelfReference
5088 AddRemoveSelfReference();
5090 NS_ASSERTION(!mDecoder
|| mDecoder
->IsEnded(),
5091 "Decoder fired ended, but not in ended state");
5093 // Discard all output streams that have finished now.
5094 for (int32_t i
= mOutputStreams
.Length() - 1; i
>= 0; --i
) {
5095 if (mOutputStreams
[i
].mFinishWhenEnded
) {
5096 LOG(LogLevel::Debug
,
5097 ("Playback ended. Letting output stream %p go inactive",
5098 mOutputStreams
[i
].mStream
.get()));
5099 mOutputStreams
[i
].mStream
->SetFinishedOnInactive(true);
5100 if (mOutputStreams
[i
].mCapturingDecoder
) {
5101 mDecoder
->RemoveOutputStream(mOutputStreams
[i
].mStream
);
5103 mOutputStreams
.RemoveElementAt(i
);
5108 LOG(LogLevel::Debug
,
5109 ("%p, got duration by reaching the end of the resource", this));
5110 mSrcStreamPlaybackEnded
= true;
5111 DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
5113 // mediacapture-main:
5114 // Setting the loop attribute has no effect since a MediaStream has no
5115 // defined end and therefore cannot be looped.
5116 if (HasAttr(kNameSpaceID_None
, nsGkAtoms::loop
)) {
5122 FireTimeUpdate(false);
5129 // A MediaStream that goes from inactive to active shall be eligible for
5130 // autoplay again according to the mediacapture-main spec.
5131 mAutoplaying
= true;
5134 DispatchAsyncEvent(NS_LITERAL_STRING("ended"));
5137 void HTMLMediaElement::SeekStarted() {
5138 DispatchAsyncEvent(NS_LITERAL_STRING("seeking"));
5141 void HTMLMediaElement::SeekCompleted() {
5142 mPlayingBeforeSeek
= false;
5143 SetPlayedOrSeeked(true);
5144 if (mTextTrackManager
) {
5145 mTextTrackManager
->DidSeek();
5147 FireTimeUpdate(false);
5148 DispatchAsyncEvent(NS_LITERAL_STRING("seeked"));
5149 // We changed whether we're seeking so we need to AddRemoveSelfReference
5150 AddRemoveSelfReference();
5151 if (mCurrentPlayRangeStart
== -1.0) {
5152 mCurrentPlayRangeStart
= CurrentTime();
5155 // After seeking completed, if the audio track is silent, start another new
5157 mHasAccumulatedSilenceRangeBeforeSeekEnd
= false;
5158 if (IsAudioTrackCurrentlySilent()) {
5159 UpdateAudioTrackSilenceRange(mIsAudioTrackAudible
);
5163 void HTMLMediaElement::NotifySuspendedByCache(bool aSuspendedByCache
) {
5164 mDownloadSuspendedByCache
= aSuspendedByCache
;
5165 UpdateReadyStateInternal();
5168 void HTMLMediaElement::DownloadSuspended() {
5169 if (mNetworkState
== NETWORK_LOADING
) {
5170 DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
5172 ChangeNetworkState(NETWORK_IDLE
);
5175 void HTMLMediaElement::DownloadResumed() {
5176 ChangeNetworkState(NETWORK_LOADING
);
5179 void HTMLMediaElement::CheckProgress(bool aHaveNewProgress
) {
5180 MOZ_ASSERT(NS_IsMainThread());
5181 MOZ_ASSERT(mNetworkState
== NETWORK_LOADING
);
5183 TimeStamp now
= TimeStamp::NowLoRes();
5185 if (aHaveNewProgress
) {
5189 // If this is the first progress, or PROGRESS_MS has passed since the last
5190 // progress event fired and more data has arrived since then, fire a
5193 (mProgressTime
.IsNull() && !aHaveNewProgress
) || !mDataTime
.IsNull(),
5194 "null TimeStamp mDataTime should not be used in comparison");
5195 if (mProgressTime
.IsNull()
5197 : (now
- mProgressTime
>=
5198 TimeDuration::FromMilliseconds(PROGRESS_MS
) &&
5199 mDataTime
> mProgressTime
)) {
5200 DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
5201 // Resolution() ensures that future data will have now > mProgressTime,
5202 // and so will trigger another event. mDataTime is not reset because it
5203 // is still required to detect stalled; it is similarly offset by
5204 // resolution to indicate the new data has not yet arrived.
5205 mProgressTime
= now
- TimeDuration::Resolution();
5206 if (mDataTime
> mProgressTime
) {
5207 mDataTime
= mProgressTime
;
5209 if (!mProgressTimer
) {
5210 NS_ASSERTION(aHaveNewProgress
,
5211 "timer dispatched when there was no timer");
5212 // Were stalled. Restart timer.
5213 StartProgressTimer();
5214 if (!mLoadedDataFired
) {
5215 ChangeDelayLoadStatus(true);
5218 // Download statistics may have been updated, force a recheck of the
5220 UpdateReadyStateInternal();
5223 if (now
- mDataTime
>= TimeDuration::FromMilliseconds(STALL_MS
)) {
5224 if (!mMediaSource
) {
5225 DispatchAsyncEvent(NS_LITERAL_STRING("stalled"));
5227 ChangeDelayLoadStatus(false);
5230 NS_ASSERTION(mProgressTimer
, "detected stalled without timer");
5231 // Stop timer events, which prevents repeated stalled events until there
5232 // is more progress.
5236 AddRemoveSelfReference();
5240 void HTMLMediaElement::ProgressTimerCallback(nsITimer
* aTimer
, void* aClosure
) {
5241 auto decoder
= static_cast<HTMLMediaElement
*>(aClosure
);
5242 decoder
->CheckProgress(false);
5245 void HTMLMediaElement::StartProgressTimer() {
5246 MOZ_ASSERT(NS_IsMainThread());
5247 MOZ_ASSERT(mNetworkState
== NETWORK_LOADING
);
5248 NS_ASSERTION(!mProgressTimer
, "Already started progress timer.");
5250 NS_NewTimerWithFuncCallback(
5251 getter_AddRefs(mProgressTimer
), ProgressTimerCallback
, this, PROGRESS_MS
,
5252 nsITimer::TYPE_REPEATING_SLACK
, "HTMLMediaElement::ProgressTimerCallback",
5253 mMainThreadEventTarget
);
5256 void HTMLMediaElement::StartProgress() {
5257 // Record the time now for detecting stalled.
5258 mDataTime
= TimeStamp::NowLoRes();
5259 // Reset mProgressTime so that mDataTime is not indicating bytes received
5260 // after the last progress event.
5261 mProgressTime
= TimeStamp();
5262 StartProgressTimer();
5265 void HTMLMediaElement::StopProgress() {
5266 MOZ_ASSERT(NS_IsMainThread());
5267 if (!mProgressTimer
) {
5271 mProgressTimer
->Cancel();
5272 mProgressTimer
= nullptr;
5275 void HTMLMediaElement::DownloadProgressed() {
5276 if (mNetworkState
!= NETWORK_LOADING
) {
5279 CheckProgress(true);
5282 bool HTMLMediaElement::ShouldCheckAllowOrigin() {
5283 return mCORSMode
!= CORS_NONE
;
5286 bool HTMLMediaElement::IsCORSSameOrigin() {
5288 RefPtr
<nsIPrincipal
> principal
= GetCurrentPrincipal();
5289 return (NS_SUCCEEDED(NodePrincipal()->Subsumes(principal
, &subsumes
)) &&
5291 ShouldCheckAllowOrigin();
5294 void HTMLMediaElement::UpdateReadyStateInternal() {
5295 if (!mDecoder
&& !mSrcStream
) {
5296 // Not initialized - bail out.
5297 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
5303 if (mDecoder
&& mReadyState
< HAVE_METADATA
) {
5304 // aNextFrame might have a next frame because the decoder can advance
5305 // on its own thread before MetadataLoaded gets a chance to run.
5306 // The arrival of more data can't change us out of this readyState.
5307 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
5308 "Decoder ready state < HAVE_METADATA",
5313 if (mSrcStream
&& mReadyState
< HAVE_METADATA
) {
5314 if (!mSrcStreamTracksAvailable
) {
5315 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
5316 "MediaStreamTracks not available yet",
5321 bool hasAudioTracks
= AudioTracks() && !AudioTracks()->IsEmpty();
5322 bool hasVideoTracks
= VideoTracks() && !VideoTracks()->IsEmpty();
5323 if (!hasAudioTracks
&& !hasVideoTracks
) {
5324 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
5325 "Stream with no tracks",
5327 // Give it one last chance to remove the self reference if needed.
5328 AddRemoveSelfReference();
5332 if (IsVideo() && hasVideoTracks
&& !HasVideo()) {
5333 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
5334 "Stream waiting for video",
5339 LOG(LogLevel::Debug
,
5340 ("MediaElement %p UpdateReadyStateInternal() Stream has "
5341 "metadata; audioTracks=%d, videoTracks=%d, "
5343 this, AudioTracks()->Length(), VideoTracks()->Length(), HasVideo()));
5345 // We are playing a stream that has video and a video frame is now set.
5346 // This means we have all metadata needed to change ready state.
5347 MediaInfo mediaInfo
= mMediaInfo
;
5348 if (hasAudioTracks
) {
5349 mediaInfo
.EnableAudio();
5351 if (hasVideoTracks
) {
5352 mediaInfo
.EnableVideo();
5354 MetadataLoaded(&mediaInfo
, nullptr);
5358 // readyState has changed, assuming it's following the pending mediasource
5359 // operations. Notify the Mediasource that the operations have completed.
5360 mMediaSource
->CompletePendingTransactions();
5363 enum NextFrameStatus nextFrameStatus
= NextFrameStatus();
5364 if (mWaitingForKey
== NOT_WAITING_FOR_KEY
) {
5365 if (nextFrameStatus
== NEXT_FRAME_UNAVAILABLE
&& mDecoder
&&
5366 !mDecoder
->IsEnded()) {
5367 nextFrameStatus
= mDecoder
->NextFrameBufferedStatus();
5369 } else if (mWaitingForKey
== WAITING_FOR_KEY
) {
5370 if (nextFrameStatus
== NEXT_FRAME_UNAVAILABLE
||
5371 nextFrameStatus
== NEXT_FRAME_UNAVAILABLE_BUFFERING
) {
5372 // http://w3c.github.io/encrypted-media/#wait-for-key
5373 // Continuing 7.3.4 Queue a "waitingforkey" Event
5374 // 4. Queue a task to fire a simple event named waitingforkey
5375 // at the media element.
5376 // 5. Set the readyState of media element to HAVE_METADATA.
5377 // NOTE: We'll change to HAVE_CURRENT_DATA or HAVE_METADATA
5378 // depending on whether we've loaded the first frame or not
5380 // 6. Suspend playback.
5381 // Note: Playback will already be stalled, as the next frame is
5383 mWaitingForKey
= WAITING_FOR_KEY_DISPATCHED
;
5384 DispatchAsyncEvent(NS_LITERAL_STRING("waitingforkey"));
5387 MOZ_ASSERT(mWaitingForKey
== WAITING_FOR_KEY_DISPATCHED
);
5388 if (nextFrameStatus
== NEXT_FRAME_AVAILABLE
) {
5389 // We have new frames after dispatching "waitingforkey".
5390 // This means we've got the key and can reset mWaitingForKey now.
5391 mWaitingForKey
= NOT_WAITING_FOR_KEY
;
5395 if (nextFrameStatus
== MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING
) {
5396 LOG(LogLevel::Debug
,
5397 ("MediaElement %p UpdateReadyStateInternal() "
5398 "NEXT_FRAME_UNAVAILABLE_SEEKING; Forcing HAVE_METADATA",
5400 ChangeReadyState(HAVE_METADATA
);
5404 if (IsVideo() && VideoTracks() && !VideoTracks()->IsEmpty() &&
5405 !IsPlaybackEnded() && GetImageContainer() &&
5406 !GetImageContainer()->HasCurrentImage()) {
5407 // Don't advance if we are playing video, but don't have a video frame.
5408 // Also, if video became available after advancing to HAVE_CURRENT_DATA
5409 // while we are still playing, we need to revert to HAVE_METADATA until
5410 // a video frame is available.
5411 LOG(LogLevel::Debug
,
5412 ("MediaElement %p UpdateReadyStateInternal() "
5413 "Playing video but no video frame; Forcing HAVE_METADATA",
5415 ChangeReadyState(HAVE_METADATA
);
5419 if (!mFirstFrameLoaded
) {
5420 // We haven't yet loaded the first frame, making us unable to determine
5421 // if we have enough valid data at the present stage.
5425 if (nextFrameStatus
== NEXT_FRAME_UNAVAILABLE_BUFFERING
) {
5426 // Force HAVE_CURRENT_DATA when buffering.
5427 ChangeReadyState(HAVE_CURRENT_DATA
);
5431 // TextTracks must be loaded for the HAVE_ENOUGH_DATA and
5432 // HAVE_FUTURE_DATA.
5433 // So force HAVE_CURRENT_DATA if text tracks not loaded.
5434 if (mTextTrackManager
&& !mTextTrackManager
->IsLoaded()) {
5435 ChangeReadyState(HAVE_CURRENT_DATA
);
5439 if (mDownloadSuspendedByCache
&& mDecoder
&& !mDecoder
->IsEnded()) {
5440 // The decoder has signaled that the download has been suspended by the
5441 // media cache. So move readyState into HAVE_ENOUGH_DATA, in case there's
5442 // script waiting for a "canplaythrough" event; without this forced
5443 // transition, we will never fire the "canplaythrough" event if the
5444 // media cache is too small, and scripts are bound to fail. Don't force
5445 // this transition if the decoder is in ended state; the readyState
5446 // should remain at HAVE_CURRENT_DATA in this case.
5447 // Note that this state transition includes the case where we finished
5448 // downloaded the whole data stream.
5449 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
5450 "Decoder download suspended by cache",
5452 ChangeReadyState(HAVE_ENOUGH_DATA
);
5456 if (nextFrameStatus
!= MediaDecoderOwner::NEXT_FRAME_AVAILABLE
) {
5457 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
5458 "Next frame not available",
5460 ChangeReadyState(HAVE_CURRENT_DATA
);
5465 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
5466 "Stream HAVE_ENOUGH_DATA",
5468 ChangeReadyState(HAVE_ENOUGH_DATA
);
5472 // Now see if we should set HAVE_ENOUGH_DATA.
5473 // If it's something we don't know the size of, then we can't
5474 // make a real estimate, so we go straight to HAVE_ENOUGH_DATA once
5475 // we've downloaded enough data that our download rate is considered
5476 // reliable. We have to move to HAVE_ENOUGH_DATA at some point or
5477 // autoplay elements for live streams will never play. Otherwise we
5478 // move to HAVE_ENOUGH_DATA if we can play through the entire media
5479 // without stopping to buffer.
5480 if (mDecoder
->CanPlayThrough()) {
5481 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
5482 "Decoder can play through",
5484 ChangeReadyState(HAVE_ENOUGH_DATA
);
5487 LOG(LogLevel::Debug
, ("MediaElement %p UpdateReadyStateInternal() "
5488 "Default; Decoder has future data",
5490 ChangeReadyState(HAVE_FUTURE_DATA
);
5493 static const char* const gReadyStateToString
[] = {
5494 "HAVE_NOTHING", "HAVE_METADATA", "HAVE_CURRENT_DATA", "HAVE_FUTURE_DATA",
5495 "HAVE_ENOUGH_DATA"};
5497 void HTMLMediaElement::ChangeReadyState(nsMediaReadyState aState
) {
5498 if (mReadyState
== aState
) {
5502 nsMediaReadyState oldState
= mReadyState
;
5503 mReadyState
= aState
;
5504 LOG(LogLevel::Debug
,
5505 ("%p Ready state changed to %s", this, gReadyStateToString
[aState
]));
5507 DDLOG(DDLogCategory::Property
, "ready_state", gReadyStateToString
[aState
]);
5509 // https://html.spec.whatwg.org/multipage/media.html#text-track-cue-active-flag
5510 // The user agent must synchronously unset cues' active flag whenever the
5511 // media element's readyState is changed back to HAVE_NOTHING.
5512 if (mReadyState
== HAVE_NOTHING
&& mTextTrackManager
) {
5513 mTextTrackManager
->NotifyReset();
5516 if (mNetworkState
== NETWORK_EMPTY
) {
5520 UpdateAudioChannelPlayingState();
5522 // Handle raising of "waiting" event during seek (see 4.8.10.9)
5524 // 4.8.12.7 Ready states:
5525 // "If the previous ready state was HAVE_FUTURE_DATA or more, and the new
5526 // ready state is HAVE_CURRENT_DATA or less
5527 // If the media element was potentially playing before its readyState
5528 // attribute changed to a value lower than HAVE_FUTURE_DATA, and the element
5529 // has not ended playback, and playback has not stopped due to errors,
5530 // paused for user interaction, or paused for in-band content, the user agent
5531 // must queue a task to fire a simple event named timeupdate at the element,
5532 // and queue a task to fire a simple event named waiting at the element."
5533 if (mPlayingBeforeSeek
&& mReadyState
< HAVE_FUTURE_DATA
) {
5534 DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
5535 } else if (oldState
>= HAVE_FUTURE_DATA
&& mReadyState
< HAVE_FUTURE_DATA
&&
5536 !Paused() && !Ended() && !mErrorSink
->mError
) {
5537 FireTimeUpdate(false);
5538 DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
5541 if (oldState
< HAVE_CURRENT_DATA
&& mReadyState
>= HAVE_CURRENT_DATA
&&
5542 !mLoadedDataFired
) {
5543 DispatchAsyncEvent(NS_LITERAL_STRING("loadeddata"));
5544 mLoadedDataFired
= true;
5547 if (oldState
< HAVE_FUTURE_DATA
&& mReadyState
>= HAVE_FUTURE_DATA
) {
5548 DispatchAsyncEvent(NS_LITERAL_STRING("canplay"));
5550 if (mDecoder
&& !mPausedForInactiveDocumentOrChannel
) {
5551 MOZ_ASSERT(AutoplayPolicy::IsAllowedToPlay(*this));
5554 NotifyAboutPlaying();
5558 CheckAutoplayDataReady();
5560 if (oldState
< HAVE_ENOUGH_DATA
&& mReadyState
>= HAVE_ENOUGH_DATA
) {
5561 DispatchAsyncEvent(NS_LITERAL_STRING("canplaythrough"));
5565 static const char* const gNetworkStateToString
[] = {"EMPTY", "IDLE", "LOADING",
5568 void HTMLMediaElement::ChangeNetworkState(nsMediaNetworkState aState
) {
5569 if (mNetworkState
== aState
) {
5573 nsMediaNetworkState oldState
= mNetworkState
;
5574 mNetworkState
= aState
;
5575 LOG(LogLevel::Debug
,
5576 ("%p Network state changed to %s", this, gNetworkStateToString
[aState
]));
5577 DDLOG(DDLogCategory::Property
, "network_state",
5578 gNetworkStateToString
[aState
]);
5580 if (oldState
== NETWORK_LOADING
) {
5581 // Stop progress notification when exiting NETWORK_LOADING.
5585 if (mNetworkState
== NETWORK_LOADING
) {
5586 // Start progress notification when entering NETWORK_LOADING.
5588 } else if (mNetworkState
== NETWORK_IDLE
&& !mErrorSink
->mError
) {
5589 // Fire 'suspend' event when entering NETWORK_IDLE and no error presented.
5590 DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
5593 // According to the resource selection (step2, step9-18), dedicated media
5594 // source failure step (step4) and aborting existing load (step4), set show
5595 // poster flag to true. https://html.spec.whatwg.org/multipage/media.html
5596 if (mNetworkState
== NETWORK_NO_SOURCE
|| mNetworkState
== NETWORK_EMPTY
) {
5600 // Changing mNetworkState affects AddRemoveSelfReference().
5601 AddRemoveSelfReference();
5604 bool HTMLMediaElement::CanActivateAutoplay() {
5605 // We also activate autoplay when playing a media source since the data
5606 // download is controlled by the script and there is no way to evaluate
5607 // MediaDecoder::CanPlayThrough().
5609 if (!HasAttr(kNameSpaceID_None
, nsGkAtoms::autoplay
)) {
5613 if (!mAutoplaying
) {
5625 if (mPausedForInactiveDocumentOrChannel
) {
5629 // Static document is used for print preview and printing, should not be
5631 if (OwnerDoc()->IsStaticDocument()) {
5635 if (mAudioChannelWrapper
) {
5636 // Note: SUSPENDED_PAUSE and SUSPENDED_BLOCK will be merged into one single
5638 if (mAudioChannelWrapper
->GetSuspendType() ==
5639 nsISuspendedTypes::SUSPENDED_PAUSE
||
5640 mAudioChannelWrapper
->GetSuspendType() ==
5641 nsISuspendedTypes::SUSPENDED_BLOCK
||
5642 mAudioChannelWrapper
->IsPlaybackBlocked()) {
5647 return mReadyState
>= HAVE_ENOUGH_DATA
;
5650 void HTMLMediaElement::CheckAutoplayDataReady() {
5651 if (!CanActivateAutoplay()) {
5655 UpdateHadAudibleAutoplayState();
5656 if (!AutoplayPolicy::IsAllowedToPlay(*this)) {
5657 DispatchEventsWhenPlayWasNotAllowed();
5661 mAllowedToPlayPromise
.ResolveIfExists(true, __func__
);
5663 // We changed mPaused which can affect AddRemoveSelfReference
5664 AddRemoveSelfReference();
5665 UpdateSrcMediaStreamPlaying();
5666 UpdateAudioChannelPlayingState();
5669 SetPlayedOrSeeked(true);
5670 if (mCurrentPlayRangeStart
== -1.0) {
5671 mCurrentPlayRangeStart
= CurrentTime();
5673 MOZ_ASSERT(!mPausedForInactiveDocumentOrChannel
);
5675 } else if (mSrcStream
) {
5676 SetPlayedOrSeeked(true);
5679 // https://html.spec.whatwg.org/multipage/media.html#ready-states:show-poster-flag
5681 mShowPoster
= false;
5682 if (mTextTrackManager
) {
5683 mTextTrackManager
->TimeMarchesOn();
5687 // For blocked media, the event would be pending until it is resumed.
5688 DispatchAsyncEvent(NS_LITERAL_STRING("play"));
5690 DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
5693 bool HTMLMediaElement::IsActive() const {
5694 Document
* ownerDoc
= OwnerDoc();
5695 return ownerDoc
&& ownerDoc
->IsActive() && ownerDoc
->IsVisible();
5698 bool HTMLMediaElement::IsHidden() const {
5699 return mUnboundFromTree
|| OwnerDoc()->Hidden();
5702 VideoFrameContainer
* HTMLMediaElement::GetVideoFrameContainer() {
5703 if (mShuttingDown
) {
5707 if (mVideoFrameContainer
) return mVideoFrameContainer
;
5709 // Only video frames need an image container.
5714 mVideoFrameContainer
= new VideoFrameContainer(
5715 this, LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS
));
5717 return mVideoFrameContainer
;
5720 void HTMLMediaElement::PrincipalChanged(DOMMediaStream
* aStream
) {
5721 LOG(LogLevel::Info
, ("HTMLMediaElement %p Stream principal changed.", this));
5722 nsContentUtils::CombineResourcePrincipals(&mSrcStreamVideoPrincipal
,
5723 aStream
->GetVideoPrincipal());
5725 LOG(LogLevel::Debug
,
5726 ("HTMLMediaElement %p Stream video principal changed to "
5727 "%p. Waiting for it to reach VideoFrameContainer before "
5729 this, aStream
->GetVideoPrincipal()));
5730 if (mVideoFrameContainer
) {
5731 UpdateSrcStreamVideoPrincipal(
5732 mVideoFrameContainer
->GetLastPrincipalHandle());
5736 void HTMLMediaElement::UpdateSrcStreamVideoPrincipal(
5737 const PrincipalHandle
& aPrincipalHandle
) {
5738 nsTArray
<RefPtr
<VideoStreamTrack
>> videoTracks
;
5739 mSrcStream
->GetVideoTracks(videoTracks
);
5741 PrincipalHandle
handle(aPrincipalHandle
);
5742 bool matchesTrackPrincipal
= false;
5743 for (const RefPtr
<VideoStreamTrack
>& track
: videoTracks
) {
5744 if (PrincipalHandleMatches(handle
, track
->GetPrincipal()) &&
5746 // When the PrincipalHandle for the VideoFrameContainer changes to that of
5747 // a track in mSrcStream we know that a removed track was displayed but
5749 matchesTrackPrincipal
= true;
5750 LOG(LogLevel::Debug
, ("HTMLMediaElement %p VideoFrameContainer's "
5751 "PrincipalHandle matches track %p. That's all we "
5753 this, track
.get()));
5758 if (matchesTrackPrincipal
) {
5759 mSrcStreamVideoPrincipal
= mSrcStream
->GetVideoPrincipal();
5763 void HTMLMediaElement::PrincipalHandleChangedForVideoFrameContainer(
5764 VideoFrameContainer
* aContainer
,
5765 const PrincipalHandle
& aNewPrincipalHandle
) {
5766 MOZ_ASSERT(NS_IsMainThread());
5772 LOG(LogLevel::Debug
, ("HTMLMediaElement %p PrincipalHandle changed in "
5773 "VideoFrameContainer.",
5776 UpdateSrcStreamVideoPrincipal(aNewPrincipalHandle
);
5779 nsresult
HTMLMediaElement::DispatchEvent(const nsAString
& aName
) {
5780 LOG_EVENT(LogLevel::Debug
, ("%p Dispatching event %s", this,
5781 NS_ConvertUTF16toUTF8(aName
).get()));
5783 // Save events that occur while in the bfcache. These will be dispatched
5784 // if the page comes out of the bfcache.
5785 if (mEventDeliveryPaused
) {
5786 mPendingEvents
.AppendElement(aName
);
5790 return nsContentUtils::DispatchTrustedEvent(
5791 OwnerDoc(), static_cast<nsIContent
*>(this), aName
, CanBubble::eNo
,
5795 void HTMLMediaElement::DispatchAsyncEvent(const nsAString
& aName
) {
5796 LOG_EVENT(LogLevel::Debug
,
5797 ("%p Queuing event %s", this, NS_ConvertUTF16toUTF8(aName
).get()));
5798 DDLOG(DDLogCategory::Event
, "HTMLMediaElement",
5799 nsCString(NS_ConvertUTF16toUTF8(aName
)));
5801 // Save events that occur while in the bfcache. These will be dispatched
5802 // if the page comes out of the bfcache.
5803 if (mEventDeliveryPaused
) {
5804 mPendingEvents
.AppendElement(aName
);
5808 nsCOMPtr
<nsIRunnable
> event
;
5810 if (aName
.EqualsLiteral("playing")) {
5811 event
= new nsNotifyAboutPlayingRunner(this, TakePendingPlayPromises());
5813 event
= new nsAsyncEventRunner(aName
, this);
5816 mMainThreadEventTarget
->Dispatch(event
.forget());
5818 if ((aName
.EqualsLiteral("play") || aName
.EqualsLiteral("playing"))) {
5820 mCurrentLoadPlayTime
.Start();
5824 } else if (aName
.EqualsLiteral("waiting")) {
5826 mCurrentLoadPlayTime
.Pause();
5828 } else if (aName
.EqualsLiteral("pause")) {
5830 mCurrentLoadPlayTime
.Pause();
5834 // It would happen when (1) media aborts current load (2) media pauses (3)
5835 // media end (4) media unbind from tree (because we would pause it)
5836 if (aName
.EqualsLiteral("pause")) {
5837 ReportPlayedTimeAfterBlockedTelemetry();
5841 nsresult
HTMLMediaElement::DispatchPendingMediaEvents() {
5842 NS_ASSERTION(!mEventDeliveryPaused
,
5843 "Must not be in bfcache when dispatching pending media events");
5845 uint32_t count
= mPendingEvents
.Length();
5846 for (uint32_t i
= 0; i
< count
; ++i
) {
5847 DispatchAsyncEvent(mPendingEvents
[i
]);
5849 mPendingEvents
.Clear();
5854 bool HTMLMediaElement::IsPotentiallyPlaying() const {
5856 // playback has not stopped due to errors,
5857 // and the element has not paused for user interaction
5859 (mReadyState
== HAVE_ENOUGH_DATA
|| mReadyState
== HAVE_FUTURE_DATA
) &&
5863 bool HTMLMediaElement::IsPlaybackEnded() const {
5865 // the current playback position is equal to the effective end of the media
5866 // resource. See bug 449157.
5868 return mReadyState
>= HAVE_METADATA
&& mDecoder
->IsEnded();
5869 } else if (mSrcStream
) {
5870 return mReadyState
>= HAVE_METADATA
&& mSrcStreamPlaybackEnded
;
5876 already_AddRefed
<nsIPrincipal
> HTMLMediaElement::GetCurrentPrincipal() {
5878 return mDecoder
->GetCurrentPrincipal();
5881 nsCOMPtr
<nsIPrincipal
> principal
= mSrcStream
->GetPrincipal();
5882 return principal
.forget();
5887 bool HTMLMediaElement::HadCrossOriginRedirects() {
5889 return mDecoder
->HadCrossOriginRedirects();
5894 already_AddRefed
<nsIPrincipal
> HTMLMediaElement::GetCurrentVideoPrincipal() {
5896 return mDecoder
->GetCurrentPrincipal();
5899 nsCOMPtr
<nsIPrincipal
> principal
= mSrcStreamVideoPrincipal
;
5900 return principal
.forget();
5905 void HTMLMediaElement::NotifyDecoderPrincipalChanged() {
5906 RefPtr
<nsIPrincipal
> principal
= GetCurrentPrincipal();
5908 mDecoder
->UpdateSameOriginStatus(!principal
|| IsCORSSameOrigin());
5911 void HTMLMediaElement::Invalidate(bool aImageSizeChanged
,
5912 Maybe
<nsIntSize
>& aNewIntrinsicSize
,
5913 bool aForceInvalidate
) {
5914 nsIFrame
* frame
= GetPrimaryFrame();
5915 if (aNewIntrinsicSize
) {
5916 UpdateMediaSize(aNewIntrinsicSize
.value());
5918 nsPresContext
* presContext
= frame
->PresContext();
5919 PresShell
* presShell
= presContext
->PresShell();
5920 presShell
->FrameNeedsReflow(frame
, IntrinsicDirty::StyleChange
,
5925 RefPtr
<ImageContainer
> imageContainer
= GetImageContainer();
5926 bool asyncInvalidate
=
5927 imageContainer
&& imageContainer
->IsAsync() && !aForceInvalidate
;
5929 if (aImageSizeChanged
) {
5930 frame
->InvalidateFrame();
5932 frame
->InvalidateLayer(DisplayItemType::TYPE_VIDEO
, nullptr, nullptr,
5933 asyncInvalidate
? nsIFrame::UPDATE_IS_ASYNC
: 0);
5937 SVGObserverUtils::InvalidateDirectRenderingObservers(this);
5940 void HTMLMediaElement::UpdateMediaSize(const nsIntSize
& aSize
) {
5941 MOZ_ASSERT(NS_IsMainThread());
5943 if (IsVideo() && mReadyState
!= HAVE_NOTHING
&&
5944 mMediaInfo
.mVideo
.mDisplay
!= aSize
) {
5945 DispatchAsyncEvent(NS_LITERAL_STRING("resize"));
5948 mMediaInfo
.mVideo
.mDisplay
= aSize
;
5949 UpdateReadyStateInternal();
5951 if (mFirstFrameListener
) {
5952 mSelectedVideoStreamTrack
->RemoveVideoOutput(mFirstFrameListener
);
5953 // The first-frame listener won't be needed again for this stream.
5954 mFirstFrameListener
= nullptr;
5958 void HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement
,
5959 bool aSuspendEvents
) {
5960 LOG(LogLevel::Debug
,
5961 ("%p SuspendOrResumeElement(pause=%d, suspendEvents=%d) hidden=%d", this,
5962 aPauseElement
, aSuspendEvents
, OwnerDoc()->Hidden()));
5964 if (aPauseElement
!= mPausedForInactiveDocumentOrChannel
) {
5965 mPausedForInactiveDocumentOrChannel
= aPauseElement
;
5966 UpdateSrcMediaStreamPlaying();
5967 UpdateAudioChannelPlayingState();
5968 if (aPauseElement
) {
5969 mCurrentLoadPlayTime
.Pause();
5972 // For EME content, we may force destruction of the CDM client (and CDM
5973 // instance if this is the last client for that CDM instance) and
5974 // the CDM's decoder. This ensures the CDM gets reliable and prompt
5975 // shutdown notifications, as it may have book-keeping it needs
5976 // to do on shutdown.
5978 nsAutoString keySystem
;
5979 mMediaKeys
->GetKeySystem(keySystem
);
5983 mDecoder
->Suspend();
5985 mEventDeliveryPaused
= aSuspendEvents
;
5988 mCurrentLoadPlayTime
.Start();
5992 if (!mPaused
&& !mDecoder
->IsEnded()) {
5996 if (mEventDeliveryPaused
) {
5997 mEventDeliveryPaused
= false;
5998 DispatchPendingMediaEvents();
6000 // If the media element has been blocked and isn't still allowed to play
6001 // when it comes back from the bfcache, we would notify front end to show
6002 // the blocking icon in order to inform user that the site is still being
6004 if (mHasEverBeenBlockedForAutoplay
&&
6005 !AutoplayPolicy::IsAllowedToPlay(*this)) {
6006 OwnerDoc()->MaybeNotifyAutoplayBlocked();
6012 bool HTMLMediaElement::IsBeingDestroyed() {
6013 Document
* ownerDoc
= OwnerDoc();
6014 nsIDocShell
* docShell
= ownerDoc
? ownerDoc
->GetDocShell() : nullptr;
6015 bool isBeingDestroyed
= false;
6017 docShell
->IsBeingDestroyed(&isBeingDestroyed
);
6019 return isBeingDestroyed
;
6022 void HTMLMediaElement::NotifyOwnerDocumentActivityChanged() {
6023 bool visible
= !IsHidden();
6025 // Visible -> Just pause hidden play time (no-op if already paused).
6027 } else if (mPlayTime
.IsStarted()) {
6028 // Not visible, play time is running -> Start hidden play time if needed.
6032 if (mDecoder
&& !IsBeingDestroyed()) {
6033 NotifyDecoderActivityChanges();
6036 bool pauseElement
= ShouldElementBePaused();
6037 SuspendOrResumeElement(pauseElement
, !IsActive());
6039 // If the owning document has become inactive we should shutdown the CDM.
6040 if (!OwnerDoc()->IsCurrentActiveDocument() && mMediaKeys
) {
6041 // We don't shutdown MediaKeys here because it also listens for document
6042 // activity and will take care of shutting down itself.
6043 DDUNLINKCHILD(mMediaKeys
.get());
6044 mMediaKeys
= nullptr;
6050 AddRemoveSelfReference();
6053 void HTMLMediaElement::AddRemoveSelfReference() {
6054 // XXX we could release earlier here in many situations if we examined
6055 // which event listeners are attached. Right now we assume there is a
6056 // potential listener for every event. We would also have to keep the
6057 // element alive if it was playing and producing audio output --- right now
6058 // that's covered by the !mPaused check.
6059 Document
* ownerDoc
= OwnerDoc();
6061 // See the comment at the top of this file for the explanation of this
6062 // boolean expression.
6063 bool needSelfReference
=
6064 !mShuttingDown
&& ownerDoc
->IsActive() &&
6065 (mDelayingLoadEvent
|| (!mPaused
&& !Ended()) ||
6066 (mDecoder
&& mDecoder
->IsSeeking()) || CanActivateAutoplay() ||
6067 (mMediaSource
? mProgressTimer
: mNetworkState
== NETWORK_LOADING
));
6069 if (needSelfReference
!= mHasSelfReference
) {
6070 mHasSelfReference
= needSelfReference
;
6071 RefPtr
<HTMLMediaElement
> self
= this;
6072 if (needSelfReference
) {
6073 // The shutdown observer will hold a strong reference to us. This
6074 // will do to keep us alive. We need to know about shutdown so that
6075 // we can release our self-reference.
6076 mMainThreadEventTarget
->Dispatch(NS_NewRunnableFunction(
6077 "dom::HTMLMediaElement::AddSelfReference",
6078 [self
]() { self
->mShutdownObserver
->AddRefMediaElement(); }));
6080 // Dispatch Release asynchronously so that we don't destroy this object
6081 // inside a call stack of method calls on this object
6082 mMainThreadEventTarget
->Dispatch(NS_NewRunnableFunction(
6083 "dom::HTMLMediaElement::AddSelfReference",
6084 [self
]() { self
->mShutdownObserver
->ReleaseMediaElement(); }));
6089 void HTMLMediaElement::NotifyShutdownEvent() {
6090 mShuttingDown
= true;
6092 AddRemoveSelfReference();
6095 void HTMLMediaElement::DispatchAsyncSourceError(nsIContent
* aSourceElement
) {
6096 LOG_EVENT(LogLevel::Debug
, ("%p Queuing simple source error event", this));
6098 nsCOMPtr
<nsIRunnable
> event
=
6099 new nsSourceErrorEventRunner(this, aSourceElement
);
6100 mMainThreadEventTarget
->Dispatch(event
.forget());
6103 void HTMLMediaElement::NotifyAddedSource() {
6104 // If a source element is inserted as a child of a media element
6105 // that has no src attribute and whose networkState has the value
6106 // NETWORK_EMPTY, the user agent must invoke the media element's
6107 // resource selection algorithm.
6108 if (!HasAttr(kNameSpaceID_None
, nsGkAtoms::src
) &&
6109 mNetworkState
== NETWORK_EMPTY
) {
6110 AssertReadyStateIsNothing();
6111 QueueSelectResourceTask();
6114 // A load was paused in the resource selection algorithm, waiting for
6115 // a new source child to be added, resume the resource selection algorithm.
6116 if (mLoadWaitStatus
== WAITING_FOR_SOURCE
) {
6117 // Rest the flag so we don't queue multiple LoadFromSourceTask() when
6118 // multiple <source> are attached in an event loop.
6119 mLoadWaitStatus
= NOT_WAITING
;
6120 QueueLoadFromSourceTask();
6124 Element
* HTMLMediaElement::GetNextSource() {
6125 mSourceLoadCandidate
= nullptr;
6128 if (mSourcePointer
== nsINode::GetLastChild()) {
6129 return nullptr; // no more children
6132 if (!mSourcePointer
) {
6133 mSourcePointer
= nsINode::GetFirstChild();
6135 mSourcePointer
= mSourcePointer
->GetNextSibling();
6137 nsIContent
* child
= mSourcePointer
;
6139 // If child is a <source> element, it is the next candidate.
6140 if (child
&& child
->IsHTMLElement(nsGkAtoms::source
)) {
6141 mSourceLoadCandidate
= child
;
6142 return child
->AsElement();
6145 MOZ_ASSERT_UNREACHABLE("Execution should not reach here!");
6149 void HTMLMediaElement::ChangeDelayLoadStatus(bool aDelay
) {
6150 if (mDelayingLoadEvent
== aDelay
) return;
6152 mDelayingLoadEvent
= aDelay
;
6154 LOG(LogLevel::Debug
, ("%p ChangeDelayLoadStatus(%d) doc=0x%p", this, aDelay
,
6155 mLoadBlockedDoc
.get()));
6157 mDecoder
->SetLoadInBackground(!aDelay
);
6160 mLoadBlockedDoc
= OwnerDoc();
6161 mLoadBlockedDoc
->BlockOnload();
6163 // mLoadBlockedDoc might be null due to GC unlinking
6164 if (mLoadBlockedDoc
) {
6165 mLoadBlockedDoc
->UnblockOnload(false);
6166 mLoadBlockedDoc
= nullptr;
6170 // We changed mDelayingLoadEvent which can affect AddRemoveSelfReference
6171 AddRemoveSelfReference();
6174 already_AddRefed
<nsILoadGroup
> HTMLMediaElement::GetDocumentLoadGroup() {
6175 if (!OwnerDoc()->IsActive()) {
6176 NS_WARNING("Load group requested for media element in inactive document.");
6178 return OwnerDoc()->GetDocumentLoadGroup();
6181 nsresult
HTMLMediaElement::CopyInnerTo(Element
* aDest
) {
6182 nsresult rv
= nsGenericHTMLElement::CopyInnerTo(aDest
);
6183 NS_ENSURE_SUCCESS(rv
, rv
);
6184 if (aDest
->OwnerDoc()->IsStaticDocument()) {
6185 HTMLMediaElement
* dest
= static_cast<HTMLMediaElement
*>(aDest
);
6186 dest
->SetMediaInfo(mMediaInfo
);
6191 already_AddRefed
<TimeRanges
> HTMLMediaElement::Buffered() const {
6192 media::TimeIntervals buffered
=
6193 mDecoder
? mDecoder
->GetBuffered() : media::TimeIntervals();
6194 RefPtr
<TimeRanges
> ranges
= new TimeRanges(ToSupports(OwnerDoc()), buffered
);
6195 return ranges
.forget();
6198 void HTMLMediaElement::SetRequestHeaders(nsIHttpChannel
* aChannel
) {
6199 // Send Accept header for video and audio types only (Bug 489071)
6200 SetAcceptHeader(aChannel
);
6202 // Apache doesn't send Content-Length when gzip transfer encoding is used,
6203 // which prevents us from estimating the video length (if explicit
6204 // Content-Duration and a length spec in the container are not present either)
6205 // and from seeking. So, disable the standard "Accept-Encoding: gzip,deflate"
6206 // that we usually send. See bug 614760.
6207 DebugOnly
<nsresult
> rv
= aChannel
->SetRequestHeader(
6208 NS_LITERAL_CSTRING("Accept-Encoding"), EmptyCString(), false);
6209 MOZ_ASSERT(NS_SUCCEEDED(rv
));
6211 // Set the Referer header
6212 nsCOMPtr
<nsIReferrerInfo
> referrerInfo
= new ReferrerInfo();
6213 referrerInfo
->InitWithDocument(OwnerDoc());
6214 rv
= aChannel
->SetReferrerInfoWithoutClone(referrerInfo
);
6215 MOZ_ASSERT(NS_SUCCEEDED(rv
));
6218 void HTMLMediaElement::FireTimeUpdate(bool aPeriodic
) {
6219 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
6221 TimeStamp now
= TimeStamp::Now();
6222 double time
= CurrentTime();
6224 // Fire a timeupdate event if this is not a periodic update (i.e. it's a
6225 // timeupdate event mandated by the spec), or if it's a periodic update
6226 // and TIMEUPDATE_MS has passed since the last timeupdate event fired and
6227 // the time has changed.
6228 if (!aPeriodic
|| (mLastCurrentTime
!= time
&&
6229 (mTimeUpdateTime
.IsNull() ||
6230 now
- mTimeUpdateTime
>=
6231 TimeDuration::FromMilliseconds(TIMEUPDATE_MS
)))) {
6232 DispatchAsyncEvent(NS_LITERAL_STRING("timeupdate"));
6233 mTimeUpdateTime
= now
;
6234 mLastCurrentTime
= time
;
6236 if (mFragmentEnd
>= 0.0 && time
>= mFragmentEnd
) {
6238 mFragmentEnd
= -1.0;
6239 mFragmentStart
= -1.0;
6240 mDecoder
->SetFragmentEndTime(mFragmentEnd
);
6243 // Update the cues displaying on the video.
6244 // Here mTextTrackManager can be null if the cycle collector has unlinked
6245 // us before our parent. In that case UnbindFromTree will call us
6246 // when our parent is unlinked.
6247 if (mTextTrackManager
) {
6248 mTextTrackManager
->TimeMarchesOn();
6252 MediaStream
* HTMLMediaElement::GetSrcMediaStream() const {
6256 return mSrcStream
->GetPlaybackStream();
6259 MediaError
* HTMLMediaElement::GetError() const { return mErrorSink
->mError
; }
6261 void HTMLMediaElement::GetCurrentSpec(nsCString
& aString
) {
6263 mLoadingSrc
->GetSpec(aString
);
6269 double HTMLMediaElement::MozFragmentEnd() {
6270 double duration
= Duration();
6272 // If there is no end fragment, or the fragment end is greater than the
6273 // duration, return the duration.
6274 return (mFragmentEnd
< 0.0 || mFragmentEnd
> duration
) ? duration
6278 void HTMLMediaElement::SetDefaultPlaybackRate(double aDefaultPlaybackRate
,
6280 if (mSrcAttrStream
) {
6284 if (aDefaultPlaybackRate
< 0) {
6285 aRv
.Throw(NS_ERROR_NOT_IMPLEMENTED
);
6289 double defaultPlaybackRate
= ClampPlaybackRate(aDefaultPlaybackRate
);
6291 if (mDefaultPlaybackRate
== defaultPlaybackRate
) {
6295 mDefaultPlaybackRate
= defaultPlaybackRate
;
6296 DispatchAsyncEvent(NS_LITERAL_STRING("ratechange"));
6299 void HTMLMediaElement::SetPlaybackRate(double aPlaybackRate
, ErrorResult
& aRv
) {
6300 if (mSrcAttrStream
) {
6304 // Changing the playback rate of a media that has more than two channels is
6306 if (aPlaybackRate
< 0) {
6307 aRv
.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
6311 if (mPlaybackRate
== aPlaybackRate
) {
6315 mPlaybackRate
= aPlaybackRate
;
6317 if (mPlaybackRate
!= 0.0 &&
6318 (mPlaybackRate
> THRESHOLD_HIGH_PLAYBACKRATE_AUDIO
||
6319 mPlaybackRate
< THRESHOLD_LOW_PLAYBACKRATE_AUDIO
)) {
6320 SetMutedInternal(mMuted
| MUTED_BY_INVALID_PLAYBACK_RATE
);
6322 SetMutedInternal(mMuted
& ~MUTED_BY_INVALID_PLAYBACK_RATE
);
6326 mDecoder
->SetPlaybackRate(ClampPlaybackRate(mPlaybackRate
));
6328 DispatchAsyncEvent(NS_LITERAL_STRING("ratechange"));
6331 void HTMLMediaElement::SetMozPreservesPitch(bool aPreservesPitch
) {
6332 mPreservesPitch
= aPreservesPitch
;
6334 mDecoder
->SetPreservesPitch(mPreservesPitch
);
6338 ImageContainer
* HTMLMediaElement::GetImageContainer() {
6339 VideoFrameContainer
* container
= GetVideoFrameContainer();
6340 return container
? container
->GetImageContainer() : nullptr;
6343 void HTMLMediaElement::UpdateAudioChannelPlayingState(bool aForcePlaying
) {
6344 if (mAudioChannelWrapper
) {
6345 mAudioChannelWrapper
->UpdateAudioChannelPlayingState(aForcePlaying
);
6349 bool HTMLMediaElement::AudioChannelAgentBlockedPlay() {
6350 if (!mAudioChannelWrapper
) {
6351 // If the mAudioChannelWrapper doesn't exist that means the CC happened.
6352 LOG(LogLevel::Debug
,
6353 ("%p AudioChannelAgentBlockedPlay() returning true due to null "
6354 "AudioChannelAgent.",
6359 // Note: SUSPENDED_PAUSE and SUSPENDED_BLOCK will be merged into one single
6361 const auto suspendType
= mAudioChannelWrapper
->GetSuspendType();
6362 return suspendType
== nsISuspendedTypes::SUSPENDED_PAUSE
||
6363 suspendType
== nsISuspendedTypes::SUSPENDED_BLOCK
;
6366 static const char* VisibilityString(Visibility aVisibility
) {
6367 switch (aVisibility
) {
6368 case Visibility::Untracked
: {
6371 case Visibility::ApproximatelyNonVisible
: {
6372 return "ApproximatelyNonVisible";
6374 case Visibility::ApproximatelyVisible
: {
6375 return "ApproximatelyVisible";
6382 void HTMLMediaElement::OnVisibilityChange(Visibility aNewVisibility
) {
6383 LOG(LogLevel::Debug
,
6384 ("OnVisibilityChange(): %s\n", VisibilityString(aNewVisibility
)));
6386 mVisibilityState
= aNewVisibility
;
6387 if (StaticPrefs::media_test_video_suspend()) {
6388 DispatchAsyncEvent(NS_LITERAL_STRING("visibilitychanged"));
6395 switch (aNewVisibility
) {
6396 case Visibility::Untracked
: {
6397 MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility");
6400 case Visibility::ApproximatelyNonVisible
: {
6401 if (mPlayTime
.IsStarted()) {
6402 // Not visible, play time is running -> Start hidden play time if
6408 case Visibility::ApproximatelyVisible
: {
6409 // Visible -> Just pause hidden play time (no-op if already paused).
6415 NotifyDecoderActivityChanges();
6418 MediaKeys
* HTMLMediaElement::GetMediaKeys() const { return mMediaKeys
; }
6420 bool HTMLMediaElement::ContainsRestrictedContent() {
6421 return GetMediaKeys() != nullptr;
6424 void HTMLMediaElement::SetCDMProxyFailure(const MediaResult
& aResult
) {
6425 LOG(LogLevel::Debug
, ("%s", __func__
));
6426 MOZ_ASSERT(mSetMediaKeysDOMPromise
);
6428 ResetSetMediaKeysTempVariables();
6430 mSetMediaKeysDOMPromise
->MaybeReject(aResult
.Code(), aResult
.Message());
6433 void HTMLMediaElement::RemoveMediaKeys() {
6434 LOG(LogLevel::Debug
, ("%s", __func__
));
6435 // 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
6436 // to decrypt media data and remove the association with the media element.
6438 mMediaKeys
->Unbind();
6440 mMediaKeys
= nullptr;
6443 bool HTMLMediaElement::TryRemoveMediaKeysAssociation() {
6444 MOZ_ASSERT(mMediaKeys
);
6445 LOG(LogLevel::Debug
, ("%s", __func__
));
6446 // 5.2.1 If the user agent or CDM do not support removing the association,
6447 // let this object's attaching media keys value be false and reject promise
6448 // with a new DOMException whose name is NotSupportedError.
6449 // 5.2.2 If the association cannot currently be removed, let this object's
6450 // attaching media keys value be false and reject promise with a new
6451 // DOMException whose name is InvalidStateError.
6453 RefPtr
<HTMLMediaElement
> self
= this;
6454 mDecoder
->SetCDMProxy(nullptr)
6456 mAbstractMainThread
, __func__
,
6458 self
->mSetCDMRequest
.Complete();
6460 self
->RemoveMediaKeys();
6461 if (self
->AttachNewMediaKeys()) {
6462 // No incoming MediaKeys object or MediaDecoder is not
6464 self
->MakeAssociationWithCDMResolved();
6467 [self
](const MediaResult
& aResult
) {
6468 self
->mSetCDMRequest
.Complete();
6469 // 5.2.4 If the preceding step failed, let this object's
6470 // attaching media keys value be false and reject promise with
6471 // a new DOMException whose name is the appropriate error name.
6472 self
->SetCDMProxyFailure(aResult
);
6474 ->Track(mSetCDMRequest
);
6482 bool HTMLMediaElement::DetachExistingMediaKeys() {
6483 LOG(LogLevel::Debug
, ("%s", __func__
));
6484 MOZ_ASSERT(mSetMediaKeysDOMPromise
);
6485 // 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
6486 // already in use by another media element, and the user agent is unable
6487 // to use it with this element, let this object's attaching media keys
6488 // value be false and reject promise with a new DOMException whose name
6489 // is QuotaExceededError.
6490 if (mIncomingMediaKeys
&& mIncomingMediaKeys
->IsBoundToMediaElement()) {
6491 SetCDMProxyFailure(MediaResult(
6492 NS_ERROR_DOM_QUOTA_EXCEEDED_ERR
,
6493 "MediaKeys object is already bound to another HTMLMediaElement"));
6497 // 5.2 If the mediaKeys attribute is not null, run the following steps:
6499 return TryRemoveMediaKeysAssociation();
6504 void HTMLMediaElement::MakeAssociationWithCDMResolved() {
6505 LOG(LogLevel::Debug
, ("%s", __func__
));
6506 MOZ_ASSERT(mSetMediaKeysDOMPromise
);
6508 // 5.4 Set the mediaKeys attribute to mediaKeys.
6509 mMediaKeys
= mIncomingMediaKeys
;
6510 // 5.5 Let this object's attaching media keys value be false.
6511 ResetSetMediaKeysTempVariables();
6512 // 5.6 Resolve promise.
6513 mSetMediaKeysDOMPromise
->MaybeResolveWithUndefined();
6514 mSetMediaKeysDOMPromise
= nullptr;
6517 bool HTMLMediaElement::TryMakeAssociationWithCDM(CDMProxy
* aProxy
) {
6518 LOG(LogLevel::Debug
, ("%s", __func__
));
6521 // 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
6522 // algorithm on the media element.
6523 // Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
6525 // CDMProxy is set asynchronously in MediaFormatReader, once it's done,
6526 // HTMLMediaElement should resolve or reject the DOM promise.
6527 RefPtr
<HTMLMediaElement
> self
= this;
6528 mDecoder
->SetCDMProxy(aProxy
)
6530 mAbstractMainThread
, __func__
,
6532 self
->mSetCDMRequest
.Complete();
6533 self
->MakeAssociationWithCDMResolved();
6535 [self
](const MediaResult
& aResult
) {
6536 self
->mSetCDMRequest
.Complete();
6537 self
->SetCDMProxyFailure(aResult
);
6539 ->Track(mSetCDMRequest
);
6545 bool HTMLMediaElement::AttachNewMediaKeys() {
6546 LOG(LogLevel::Debug
,
6547 ("%s incoming MediaKeys(%p)", __func__
, mIncomingMediaKeys
.get()));
6548 MOZ_ASSERT(mSetMediaKeysDOMPromise
);
6550 // 5.3. If mediaKeys is not null, run the following steps:
6551 if (mIncomingMediaKeys
) {
6552 auto cdmProxy
= mIncomingMediaKeys
->GetCDMProxy();
6554 SetCDMProxyFailure(MediaResult(
6555 NS_ERROR_DOM_INVALID_STATE_ERR
,
6556 "CDM crashed before binding MediaKeys object to HTMLMediaElement"));
6560 // 5.3.1 Associate the CDM instance represented by mediaKeys with the
6561 // media element for decrypting media data.
6562 if (NS_FAILED(mIncomingMediaKeys
->Bind(this))) {
6563 // 5.3.2 If the preceding step failed, run the following steps:
6565 // 5.3.2.1 Set the mediaKeys attribute to null.
6566 mMediaKeys
= nullptr;
6567 // 5.3.2.2 Let this object's attaching media keys value be false.
6568 // 5.3.2.3 Reject promise with a new DOMException whose name is
6569 // the appropriate error name.
6571 MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR
,
6572 "Failed to bind MediaKeys object to HTMLMediaElement"));
6575 return TryMakeAssociationWithCDM(cdmProxy
);
6580 void HTMLMediaElement::ResetSetMediaKeysTempVariables() {
6581 mAttachingMediaKey
= false;
6582 mIncomingMediaKeys
= nullptr;
6585 already_AddRefed
<Promise
> HTMLMediaElement::SetMediaKeys(
6586 mozilla::dom::MediaKeys
* aMediaKeys
, ErrorResult
& aRv
) {
6587 LOG(LogLevel::Debug
, ("%p SetMediaKeys(%p) mMediaKeys=%p mDecoder=%p", this,
6588 aMediaKeys
, mMediaKeys
.get(), mDecoder
.get()));
6590 if (MozAudioCaptured()) {
6591 aRv
.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
6595 nsPIDOMWindowInner
* win
= OwnerDoc()->GetInnerWindow();
6597 aRv
.Throw(NS_ERROR_UNEXPECTED
);
6600 RefPtr
<DetailedPromise
> promise
= DetailedPromise::Create(
6601 win
->AsGlobal(), aRv
,
6602 NS_LITERAL_CSTRING("HTMLMediaElement.setMediaKeys"));
6607 // 1. If mediaKeys and the mediaKeys attribute are the same object,
6608 // return a resolved promise.
6609 if (mMediaKeys
== aMediaKeys
) {
6610 promise
->MaybeResolveWithUndefined();
6611 return promise
.forget();
6614 // 2. If this object's attaching media keys value is true, return a
6615 // promise rejected with a new DOMException whose name is InvalidStateError.
6616 if (mAttachingMediaKey
) {
6617 promise
->MaybeReject(
6618 NS_ERROR_DOM_INVALID_STATE_ERR
,
6619 NS_LITERAL_CSTRING("A MediaKeys object is in attaching operation."));
6620 return promise
.forget();
6623 // 3. Let this object's attaching media keys value be true.
6624 mAttachingMediaKey
= true;
6625 mIncomingMediaKeys
= aMediaKeys
;
6627 // 4. Let promise be a new promise.
6628 mSetMediaKeysDOMPromise
= promise
;
6630 // 5. Run the following steps in parallel:
6633 if (!DetachExistingMediaKeys() || !AttachNewMediaKeys()) {
6634 return promise
.forget();
6638 MakeAssociationWithCDMResolved();
6640 // 6. Return promise.
6641 return promise
.forget();
6644 EventHandlerNonNull
* HTMLMediaElement::GetOnencrypted() {
6645 return EventTarget::GetEventHandler(nsGkAtoms::onencrypted
);
6648 void HTMLMediaElement::SetOnencrypted(EventHandlerNonNull
* aCallback
) {
6649 EventTarget::SetEventHandler(nsGkAtoms::onencrypted
, aCallback
);
6652 EventHandlerNonNull
* HTMLMediaElement::GetOnwaitingforkey() {
6653 return EventTarget::GetEventHandler(nsGkAtoms::onwaitingforkey
);
6656 void HTMLMediaElement::SetOnwaitingforkey(EventHandlerNonNull
* aCallback
) {
6657 EventTarget::SetEventHandler(nsGkAtoms::onwaitingforkey
, aCallback
);
6660 void HTMLMediaElement::DispatchEncrypted(const nsTArray
<uint8_t>& aInitData
,
6661 const nsAString
& aInitDataType
) {
6662 LOG(LogLevel::Debug
, ("%p DispatchEncrypted initDataType='%s'", this,
6663 NS_ConvertUTF16toUTF8(aInitDataType
).get()));
6665 if (mReadyState
== HAVE_NOTHING
) {
6666 // Ready state not HAVE_METADATA (yet), don't dispatch encrypted now.
6667 // Queueing for later dispatch in MetadataLoaded.
6668 mPendingEncryptedInitData
.AddInitData(aInitDataType
, aInitData
);
6672 RefPtr
<MediaEncryptedEvent
> event
;
6673 if (IsCORSSameOrigin()) {
6674 event
= MediaEncryptedEvent::Constructor(this, aInitDataType
, aInitData
);
6676 event
= MediaEncryptedEvent::Constructor(this);
6679 RefPtr
<AsyncEventDispatcher
> asyncDispatcher
=
6680 new AsyncEventDispatcher(this, event
);
6681 asyncDispatcher
->PostDOMEvent();
6684 bool HTMLMediaElement::IsEventAttributeNameInternal(nsAtom
* aName
) {
6685 return aName
== nsGkAtoms::onencrypted
||
6686 nsGenericHTMLElement::IsEventAttributeNameInternal(aName
);
6689 already_AddRefed
<nsIPrincipal
> HTMLMediaElement::GetTopLevelPrincipal() {
6690 RefPtr
<nsIPrincipal
> principal
;
6691 nsCOMPtr
<nsPIDOMWindowInner
> window
= OwnerDoc()->GetInnerWindow();
6695 // XXXkhuey better hope we always have an outer ...
6696 nsCOMPtr
<nsPIDOMWindowOuter
> top
= window
->GetOuterWindow()->GetTop();
6700 Document
* doc
= top
->GetExtantDoc();
6704 principal
= doc
->NodePrincipal();
6705 return principal
.forget();
6708 void HTMLMediaElement::NotifyWaitingForKey() {
6709 LOG(LogLevel::Debug
, ("%p, NotifyWaitingForKey()", this));
6711 // http://w3c.github.io/encrypted-media/#wait-for-key
6712 // 7.3.4 Queue a "waitingforkey" Event
6713 // 1. Let the media element be the specified HTMLMediaElement object.
6714 // 2. If the media element's waiting for key value is true, abort these steps.
6715 if (mWaitingForKey
== NOT_WAITING_FOR_KEY
) {
6716 // 3. Set the media element's waiting for key value to true.
6717 // Note: algorithm continues in UpdateReadyStateInternal() when all decoded
6718 // data enqueued in the MDSM is consumed.
6719 mWaitingForKey
= WAITING_FOR_KEY
;
6720 UpdateReadyStateInternal();
6724 AudioTrackList
* HTMLMediaElement::AudioTracks() { return mAudioTrackList
; }
6726 VideoTrackList
* HTMLMediaElement::VideoTracks() { return mVideoTrackList
; }
6728 TextTrackList
* HTMLMediaElement::GetTextTracks() {
6729 return GetOrCreateTextTrackManager()->GetTextTracks();
6732 already_AddRefed
<TextTrack
> HTMLMediaElement::AddTextTrack(
6733 TextTrackKind aKind
, const nsAString
& aLabel
, const nsAString
& aLanguage
) {
6734 return GetOrCreateTextTrackManager()->AddTextTrack(
6735 aKind
, aLabel
, aLanguage
, TextTrackMode::Hidden
,
6736 TextTrackReadyState::Loaded
, TextTrackSource::AddTextTrack
);
6739 void HTMLMediaElement::PopulatePendingTextTrackList() {
6740 if (mTextTrackManager
) {
6741 mTextTrackManager
->PopulatePendingList();
6745 TextTrackManager
* HTMLMediaElement::GetOrCreateTextTrackManager() {
6746 if (!mTextTrackManager
) {
6747 mTextTrackManager
= new TextTrackManager(this);
6748 mTextTrackManager
->AddListeners();
6750 return mTextTrackManager
;
6753 MediaDecoderOwner::NextFrameStatus
HTMLMediaElement::NextFrameStatus() {
6755 return mDecoder
->NextFrameStatus();
6758 if (mSrcStreamTracksAvailable
&& !mSrcStreamPlaybackEnded
) {
6759 return NEXT_FRAME_AVAILABLE
;
6761 return NEXT_FRAME_UNAVAILABLE
;
6763 return NEXT_FRAME_UNINITIALIZED
;
6766 void HTMLMediaElement::SetDecoder(MediaDecoder
* aDecoder
) {
6767 MOZ_ASSERT(aDecoder
); // Use ShutdownDecoder() to clear.
6771 mDecoder
= aDecoder
;
6772 DDLINKCHILD("decoder", mDecoder
.get());
6773 if (mDecoder
&& mForcedHidden
) {
6774 mDecoder
->SetForcedHidden(mForcedHidden
);
6778 float HTMLMediaElement::ComputedVolume() const {
6781 : mAudioChannelWrapper
? mAudioChannelWrapper
->GetEffectiveVolume()
6785 bool HTMLMediaElement::ComputedMuted() const {
6786 return (mMuted
& MUTED_BY_AUDIO_CHANNEL
);
6789 nsSuspendedTypes
HTMLMediaElement::ComputedSuspended() const {
6790 return mAudioChannelWrapper
? mAudioChannelWrapper
->GetSuspendType()
6791 : nsISuspendedTypes::NONE_SUSPENDED
;
6794 bool HTMLMediaElement::IsCurrentlyPlaying() const {
6795 // We have playable data, but we still need to check whether data is "real"
6797 return mReadyState
>= HAVE_CURRENT_DATA
&& !IsPlaybackEnded();
6800 void HTMLMediaElement::SetAudibleState(bool aAudible
) {
6801 if (mIsAudioTrackAudible
!= aAudible
) {
6802 UpdateAudioTrackSilenceRange(aAudible
);
6803 mIsAudioTrackAudible
= aAudible
;
6804 NotifyAudioPlaybackChanged(
6805 AudioChannelService::AudibleChangedReasons::eDataAudibleChanged
);
6809 bool HTMLMediaElement::IsAudioTrackCurrentlySilent() const {
6810 return HasAudio() && !mIsAudioTrackAudible
;
6813 void HTMLMediaElement::UpdateAudioTrackSilenceRange(bool aAudible
) {
6819 mAudioTrackSilenceStartedTime
= CurrentTime();
6823 AccumulateAudioTrackSilence();
6826 void HTMLMediaElement::AccumulateAudioTrackSilence() {
6827 MOZ_ASSERT(HasAudio());
6828 const double current
= CurrentTime();
6829 if (current
< mAudioTrackSilenceStartedTime
) {
6833 media::TimeUnit::FromSeconds(mAudioTrackSilenceStartedTime
);
6834 const auto end
= media::TimeUnit::FromSeconds(current
);
6835 mSilenceTimeRanges
+= media::TimeInterval(start
, end
);
6838 void HTMLMediaElement::ReportAudioTrackSilenceProportionTelemetry() {
6843 // Add last silence range to our ranges set.
6844 if (!mIsAudioTrackAudible
) {
6845 AccumulateAudioTrackSilence();
6848 RefPtr
<TimeRanges
> ranges
= Played();
6849 const uint32_t lengthPlayedRange
= ranges
->Length();
6850 const uint32_t lengthSilenceRange
= mSilenceTimeRanges
.Length();
6851 if (!lengthPlayedRange
|| !lengthSilenceRange
) {
6855 double playedTime
= 0.0, silenceTime
= 0.0;
6856 for (uint32_t idx
= 0; idx
< lengthPlayedRange
; idx
++) {
6857 playedTime
+= ranges
->End(idx
) - ranges
->Start(idx
);
6860 for (uint32_t idx
= 0; idx
< lengthSilenceRange
; idx
++) {
6861 silenceTime
+= mSilenceTimeRanges
.End(idx
).ToSeconds() -
6862 mSilenceTimeRanges
.Start(idx
).ToSeconds();
6865 double silenceProportion
= (silenceTime
/ playedTime
) * 100;
6866 // silenceProportion should be in the range [0, 100]
6867 silenceProportion
= std::min(100.0, std::max(silenceProportion
, 0.0));
6868 Telemetry::Accumulate(Telemetry::AUDIO_TRACK_SILENCE_PROPORTION
,
6872 void HTMLMediaElement::NotifyAudioPlaybackChanged(
6873 AudibleChangedReasons aReason
) {
6874 if (mAudioChannelWrapper
) {
6875 mAudioChannelWrapper
->NotifyAudioPlaybackChanged(aReason
);
6877 // only request wake lock for audible media.
6881 bool HTMLMediaElement::ShouldElementBePaused() {
6882 // Bfcached page or inactive document.
6890 void HTMLMediaElement::SetMediaInfo(const MediaInfo
& aInfo
) {
6891 const bool oldHasAudio
= mMediaInfo
.HasAudio();
6893 if (aInfo
.HasAudio() != oldHasAudio
) {
6894 UpdateAudioChannelPlayingState();
6895 NotifyAudioPlaybackChanged(
6896 AudioChannelService::AudibleChangedReasons::eDataAudibleChanged
);
6898 if (mAudioChannelWrapper
) {
6899 mAudioChannelWrapper
->AudioCaptureStreamChangeIfNeeded();
6904 void HTMLMediaElement::AudioCaptureStreamChange(bool aCapture
) {
6905 // No need to capture a silence media element.
6910 if (aCapture
&& !mCaptureStreamPort
) {
6911 nsCOMPtr
<nsPIDOMWindowInner
> window
= OwnerDoc()->GetInnerWindow();
6912 if (!OwnerDoc()->GetInnerWindow()) {
6916 uint64_t id
= window
->WindowID();
6917 MediaStreamGraph
* msg
= MediaStreamGraph::GetInstance(
6918 MediaStreamGraph::AUDIO_THREAD_DRIVER
, window
,
6919 MediaStreamGraph::REQUEST_DEFAULT_SAMPLE_RATE
);
6921 if (GetSrcMediaStream()) {
6922 mCaptureStreamPort
= msg
->ConnectToCaptureStream(id
, GetSrcMediaStream());
6924 RefPtr
<DOMMediaStream
> stream
=
6925 CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED
,
6926 StreamCaptureType::CAPTURE_AUDIO
, msg
);
6927 mCaptureStreamPort
=
6928 msg
->ConnectToCaptureStream(id
, stream
->GetPlaybackStream());
6930 } else if (!aCapture
&& mCaptureStreamPort
) {
6932 ProcessedMediaStream
* ps
=
6933 mCaptureStreamPort
->GetSource()->AsProcessedStream();
6936 for (uint32_t i
= 0; i
< mOutputStreams
.Length(); i
++) {
6937 if (mOutputStreams
[i
].mStream
->GetPlaybackStream() == ps
) {
6938 mDecoder
->RemoveOutputStream(mOutputStreams
[i
].mStream
);
6939 mOutputStreams
.RemoveElementAt(i
);
6944 mCaptureStreamPort
->Destroy();
6945 mCaptureStreamPort
= nullptr;
6949 void HTMLMediaElement::NotifyCueDisplayStatesChanged() {
6950 if (!mTextTrackManager
) {
6954 mTextTrackManager
->DispatchUpdateCueDisplay();
6957 void HTMLMediaElement::MarkAsContentSource(CallerAPI aAPI
) {
6958 const bool isVisible
= mVisibilityState
== Visibility::ApproximatelyVisible
;
6962 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 0);
6964 // 1 = ALL_INVISIBLE
6965 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 1);
6967 if (IsInComposedDoc()) {
6969 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
,
6972 // 1 = ALL_NOT_IN_TREE
6973 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
,
6979 case CallerAPI::DRAW_IMAGE
: {
6981 // 2 = drawImage_VISIBLE
6982 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 2);
6984 // 3 = drawImage_INVISIBLE
6985 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 3);
6987 if (IsInComposedDoc()) {
6988 // 2 = drawImage_IN_TREE
6989 Telemetry::Accumulate(
6990 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 2);
6992 // 3 = drawImage_NOT_IN_TREE
6993 Telemetry::Accumulate(
6994 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 3);
6999 case CallerAPI::CREATE_PATTERN
: {
7001 // 4 = createPattern_VISIBLE
7002 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 4);
7004 // 5 = createPattern_INVISIBLE
7005 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 5);
7007 if (IsInComposedDoc()) {
7008 // 4 = createPattern_IN_TREE
7009 Telemetry::Accumulate(
7010 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 4);
7012 // 5 = createPattern_NOT_IN_TREE
7013 Telemetry::Accumulate(
7014 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 5);
7019 case CallerAPI::CREATE_IMAGEBITMAP
: {
7021 // 6 = createImageBitmap_VISIBLE
7022 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 6);
7024 // 7 = createImageBitmap_INVISIBLE
7025 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 7);
7027 if (IsInComposedDoc()) {
7028 // 6 = createImageBitmap_IN_TREE
7029 Telemetry::Accumulate(
7030 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 6);
7032 // 7 = createImageBitmap_NOT_IN_TREE
7033 Telemetry::Accumulate(
7034 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 7);
7039 case CallerAPI::CAPTURE_STREAM
: {
7041 // 8 = captureStream_VISIBLE
7042 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 8);
7044 // 9 = captureStream_INVISIBLE
7045 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 9);
7047 if (IsInComposedDoc()) {
7048 // 8 = captureStream_IN_TREE
7049 Telemetry::Accumulate(
7050 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 8);
7052 // 9 = captureStream_NOT_IN_TREE
7053 Telemetry::Accumulate(
7054 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 9);
7061 LOG(LogLevel::Debug
,
7062 ("%p Log VIDEO_AS_CONTENT_SOURCE: visibility = %u, API: '%d' and 'All'",
7063 this, isVisible
, static_cast<int>(aAPI
)));
7066 LOG(LogLevel::Debug
,
7067 ("%p Log VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT: inTree = %u, API: "
7069 this, IsInComposedDoc(), static_cast<int>(aAPI
)));
7073 void HTMLMediaElement::UpdateCustomPolicyAfterPlayed() {
7074 if (mAudioChannelWrapper
) {
7075 mAudioChannelWrapper
->NotifyPlayStateChanged();
7079 AbstractThread
* HTMLMediaElement::AbstractMainThread() const {
7080 MOZ_ASSERT(mAbstractMainThread
);
7082 return mAbstractMainThread
;
7085 nsTArray
<RefPtr
<PlayPromise
>> HTMLMediaElement::TakePendingPlayPromises() {
7086 return std::move(mPendingPlayPromises
);
7089 void HTMLMediaElement::NotifyAboutPlaying() {
7090 // Stick to the DispatchAsyncEvent() call path for now because we want to
7091 // trigger some telemetry-related codes in the DispatchAsyncEvent() method.
7092 DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
7095 already_AddRefed
<PlayPromise
> HTMLMediaElement::CreatePlayPromise(
7096 ErrorResult
& aRv
) const {
7097 nsPIDOMWindowInner
* win
= OwnerDoc()->GetInnerWindow();
7100 aRv
.Throw(NS_ERROR_UNEXPECTED
);
7104 RefPtr
<PlayPromise
> promise
= PlayPromise::Create(win
->AsGlobal(), aRv
);
7105 LOG(LogLevel::Debug
, ("%p created PlayPromise %p", this, promise
.get()));
7107 return promise
.forget();
7110 already_AddRefed
<Promise
> HTMLMediaElement::CreateDOMPromise(
7111 ErrorResult
& aRv
) const {
7112 nsPIDOMWindowInner
* win
= OwnerDoc()->GetInnerWindow();
7115 aRv
.Throw(NS_ERROR_UNEXPECTED
);
7119 return Promise::Create(win
->AsGlobal(), aRv
);
7122 void HTMLMediaElement::AsyncResolvePendingPlayPromises() {
7123 if (mShuttingDown
) {
7127 nsCOMPtr
<nsIRunnable
> event
= new nsResolveOrRejectPendingPlayPromisesRunner(
7128 this, TakePendingPlayPromises());
7130 mMainThreadEventTarget
->Dispatch(event
.forget());
7133 void HTMLMediaElement::AsyncRejectPendingPlayPromises(nsresult aError
) {
7136 DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
7139 if (mShuttingDown
) {
7143 if (aError
== NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR
) {
7144 DispatchEventsWhenPlayWasNotAllowed();
7147 nsCOMPtr
<nsIRunnable
> event
= new nsResolveOrRejectPendingPlayPromisesRunner(
7148 this, TakePendingPlayPromises(), aError
);
7150 mMainThreadEventTarget
->Dispatch(event
.forget());
7153 void HTMLMediaElement::GetEMEInfo(dom::EMEDebugInfo
& aInfo
) {
7157 mMediaKeys
->GetKeySystem(aInfo
.mKeySystem
);
7158 mMediaKeys
->GetSessionsInfo(aInfo
.mSessionsInfo
);
7161 void HTMLMediaElement::NotifyDecoderActivityChanges() const {
7163 mDecoder
->NotifyOwnerActivityChanged(!IsHidden(), mVisibilityState
,
7168 Document
* HTMLMediaElement::GetDocument() const { return OwnerDoc(); }
7170 void HTMLMediaElement::ConstructMediaTracks(const MediaInfo
* aInfo
) {
7171 if (mMediaTracksConstructed
|| !aInfo
) {
7175 mMediaTracksConstructed
= true;
7177 AudioTrackList
* audioList
= AudioTracks();
7178 if (audioList
&& aInfo
->HasAudio()) {
7179 const TrackInfo
& info
= aInfo
->mAudio
;
7180 RefPtr
<AudioTrack
> track
= MediaTrackList::CreateAudioTrack(
7181 audioList
->GetOwnerGlobal(), info
.mId
, info
.mKind
, info
.mLabel
,
7182 info
.mLanguage
, info
.mEnabled
);
7184 audioList
->AddTrack(track
);
7187 VideoTrackList
* videoList
= VideoTracks();
7188 if (videoList
&& aInfo
->HasVideo()) {
7189 const TrackInfo
& info
= aInfo
->mVideo
;
7190 RefPtr
<VideoTrack
> track
= MediaTrackList::CreateVideoTrack(
7191 videoList
->GetOwnerGlobal(), info
.mId
, info
.mKind
, info
.mLabel
,
7194 videoList
->AddTrack(track
);
7195 track
->SetEnabledInternal(info
.mEnabled
, MediaTrack::FIRE_NO_EVENTS
);
7199 void HTMLMediaElement::RemoveMediaTracks() {
7200 if (mAudioTrackList
) {
7201 mAudioTrackList
->RemoveTracks();
7204 if (mVideoTrackList
) {
7205 mVideoTrackList
->RemoveTracks();
7208 mMediaTracksConstructed
= false;
7211 class MediaElementGMPCrashHelper
: public GMPCrashHelper
{
7213 explicit MediaElementGMPCrashHelper(HTMLMediaElement
* aElement
)
7214 : mElement(aElement
) {
7215 MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
7217 already_AddRefed
<nsPIDOMWindowInner
> GetPluginCrashedEventTarget() override
{
7218 MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
7222 return do_AddRef(mElement
->OwnerDoc()->GetInnerWindow());
7226 WeakPtr
<HTMLMediaElement
> mElement
;
7229 already_AddRefed
<GMPCrashHelper
> HTMLMediaElement::CreateGMPCrashHelper() {
7230 return MakeAndAddRef
<MediaElementGMPCrashHelper
>(this);
7233 void HTMLMediaElement::MarkAsTainted() {
7234 mHasSuspendTaint
= true;
7237 mDecoder
->SetSuspendTaint(true);
7241 bool HasDebuggerOrTabsPrivilege(JSContext
* aCx
, JSObject
* aObj
) {
7242 return nsContentUtils::CallerHasPermission(aCx
, nsGkAtoms::debugger
) ||
7243 nsContentUtils::CallerHasPermission(aCx
, nsGkAtoms::tabs
);
7246 void HTMLMediaElement::AsyncResolveSeekDOMPromiseIfExists() {
7247 MOZ_ASSERT(NS_IsMainThread());
7248 if (mSeekDOMPromise
) {
7249 RefPtr
<dom::Promise
> promise
= mSeekDOMPromise
.forget();
7250 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
7251 "dom::HTMLMediaElement::AsyncResolveSeekDOMPromiseIfExists",
7252 [promise
]() { promise
->MaybeResolveWithUndefined(); });
7253 mAbstractMainThread
->Dispatch(r
.forget());
7254 mSeekDOMPromise
= nullptr;
7258 void HTMLMediaElement::AsyncRejectSeekDOMPromiseIfExists() {
7259 MOZ_ASSERT(NS_IsMainThread());
7260 if (mSeekDOMPromise
) {
7261 RefPtr
<dom::Promise
> promise
= mSeekDOMPromise
.forget();
7262 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
7263 "dom::HTMLMediaElement::AsyncRejectSeekDOMPromiseIfExists",
7264 [promise
]() { promise
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
); });
7265 mAbstractMainThread
->Dispatch(r
.forget());
7266 mSeekDOMPromise
= nullptr;
7270 void HTMLMediaElement::ReportCanPlayTelemetry() {
7271 LOG(LogLevel::Debug
, ("%s", __func__
));
7273 RefPtr
<nsIThread
> thread
;
7274 nsresult rv
= NS_NewNamedThread("MediaTelemetry", getter_AddRefs(thread
));
7275 if (NS_WARN_IF(NS_FAILED(rv
))) {
7279 RefPtr
<AbstractThread
> abstractThread
= mAbstractMainThread
;
7282 NS_NewRunnableFunction(
7283 "dom::HTMLMediaElement::ReportCanPlayTelemetry",
7284 [thread
, abstractThread
]() {
7286 // Windows Media Foundation requires MSCOM to be inited.
7287 DebugOnly
<HRESULT
> hr
= CoInitializeEx(0, COINIT_MULTITHREADED
);
7288 MOZ_ASSERT(hr
== S_OK
);
7290 bool aac
= MP4Decoder::IsSupportedType(
7291 MediaContainerType(MEDIAMIMETYPE(AUDIO_MP4
)), nullptr);
7292 bool h264
= MP4Decoder::IsSupportedType(
7293 MediaContainerType(MEDIAMIMETYPE(VIDEO_MP4
)), nullptr);
7297 abstractThread
->Dispatch(NS_NewRunnableFunction(
7298 "dom::HTMLMediaElement::ReportCanPlayTelemetry",
7299 [thread
, aac
, h264
]() {
7300 LOG(LogLevel::Debug
,
7301 ("MediaTelemetry aac=%d h264=%d", aac
, h264
));
7302 Telemetry::Accumulate(
7303 Telemetry::HistogramID::VIDEO_CAN_CREATE_AAC_DECODER
,
7305 Telemetry::Accumulate(
7306 Telemetry::HistogramID::VIDEO_CAN_CREATE_H264_DECODER
,
7308 thread
->AsyncShutdown();
7311 NS_DISPATCH_NORMAL
);
7314 already_AddRefed
<Promise
> HTMLMediaElement::SetSinkId(const nsAString
& aSinkId
,
7316 nsCOMPtr
<nsPIDOMWindowInner
> win
= OwnerDoc()->GetInnerWindow();
7318 aRv
.Throw(NS_ERROR_UNEXPECTED
);
7322 RefPtr
<Promise
> promise
= Promise::Create(win
->AsGlobal(), aRv
);
7327 if (mSink
.first().Equals(aSinkId
)) {
7328 promise
->MaybeResolveWithUndefined();
7329 return promise
.forget();
7332 nsString
sinkId(aSinkId
);
7334 ->GetSinkDevice(win
, sinkId
)
7336 mAbstractMainThread
, __func__
,
7337 [self
= RefPtr
<HTMLMediaElement
>(this)](
7338 RefPtr
<AudioDeviceInfo
>&& aInfo
) {
7339 // Sink found switch output device.
7341 if (self
->mDecoder
) {
7342 RefPtr
<SinkInfoPromise
> p
= self
->mDecoder
->SetSink(aInfo
)->Then(
7343 self
->mAbstractMainThread
, __func__
,
7344 [aInfo
](const GenericPromise::ResolveOrRejectValue
& aValue
) {
7345 if (aValue
.IsResolve()) {
7346 return SinkInfoPromise::CreateAndResolve(aInfo
, __func__
);
7348 return SinkInfoPromise::CreateAndReject(
7349 aValue
.RejectValue(), __func__
);
7353 if (self
->GetSrcMediaStream()) {
7354 // Set Sink Id through MSG is not supported yet.
7355 return SinkInfoPromise::CreateAndReject(NS_ERROR_ABORT
, __func__
);
7357 // No media attached to the element save it for later.
7358 return SinkInfoPromise::CreateAndResolve(aInfo
, __func__
);
7361 // Promise is rejected, sink not found.
7362 return SinkInfoPromise::CreateAndReject(res
, __func__
);
7365 mAbstractMainThread
, __func__
,
7366 [promise
, self
= RefPtr
<HTMLMediaElement
>(this),
7367 sinkId
](const SinkInfoPromise::ResolveOrRejectValue
& aValue
) {
7368 if (aValue
.IsResolve()) {
7369 self
->mSink
= MakePair(sinkId
, aValue
.ResolveValue());
7370 promise
->MaybeResolveWithUndefined();
7372 switch (aValue
.RejectValue()) {
7373 case NS_ERROR_ABORT
:
7374 promise
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
);
7376 case NS_ERROR_NOT_AVAILABLE
: {
7377 ErrorResult notFoundError
;
7378 notFoundError
.ThrowDOMException(
7379 NS_ERROR_DOM_NOT_FOUND_ERR
,
7380 NS_LITERAL_CSTRING("The object can not be found here."));
7381 promise
->MaybeReject(notFoundError
);
7384 case NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR
:
7385 promise
->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR
);
7388 MOZ_ASSERT_UNREACHABLE("Invalid error.");
7394 return promise
.forget();
7397 void HTMLMediaElement::NotifyTextTrackModeChanged() {
7398 if (mPendingTextTrackChanged
) {
7401 mPendingTextTrackChanged
= true;
7402 mAbstractMainThread
->Dispatch(
7403 NS_NewRunnableFunction("HTMLMediaElement::NotifyTextTrackModeChanged",
7404 [this, self
= RefPtr
<HTMLMediaElement
>(this)]() {
7405 mPendingTextTrackChanged
= false;
7406 if (!mTextTrackManager
) {
7409 GetTextTracks()->CreateAndDispatchChangeEvent();
7410 // https://html.spec.whatwg.org/multipage/media.html#text-track-model:show-poster-flag
7412 mTextTrackManager
->TimeMarchesOn();
7418 } // namespace mozilla