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.
14 #include "mozilla/dom/HTMLMediaElement.h"
15 #include "AudioChannelService.h"
16 #include "AudioStreamTrack.h"
17 #include "AutoplayPolicy.h"
18 #include "ChannelMediaDecoder.h"
19 #include "DOMMediaStream.h"
20 #include "DecoderDoctorDiagnostics.h"
21 #include "DecoderDoctorLogger.h"
22 #include "DecoderTraits.h"
23 #include "FrameStatistics.h"
24 #include "GMPCrashHelper.h"
25 #ifdef MOZ_ANDROID_HLS_SUPPORT
26 #include "HLSDecoder.h"
28 #include "HTMLMediaElement.h"
29 #include "ImageContainer.h"
31 #include "MP4Decoder.h"
32 #include "MediaContainerType.h"
33 #include "MediaError.h"
34 #include "MediaMetadataManager.h"
35 #include "MediaResource.h"
36 #include "MediaSourceDecoder.h"
37 #include "MediaStreamError.h"
38 #include "MediaStreamGraph.h"
39 #include "MediaStreamListener.h"
40 #include "MediaTrackList.h"
41 #include "SVGObserverUtils.h"
42 #include "TimeRanges.h"
43 #include "VideoFrameContainer.h"
44 #include "VideoStreamTrack.h"
45 #include "base/basictypes.h"
47 #include "mozilla/ArrayUtils.h"
48 #include "mozilla/AsyncEventDispatcher.h"
49 #include "mozilla/EMEUtils.h"
50 #include "mozilla/EventDispatcher.h"
51 #include "mozilla/EventStateManager.h"
52 #include "mozilla/FloatingPoint.h"
53 #include "mozilla/MathAlgorithms.h"
54 #include "mozilla/NotNull.h"
55 #include "mozilla/Preferences.h"
56 #include "mozilla/Sprintf.h"
57 #include "mozilla/StaticPrefs.h"
58 #include "mozilla/Telemetry.h"
59 #include "mozilla/dom/AudioTrack.h"
60 #include "mozilla/dom/AudioTrackList.h"
61 #include "mozilla/AutoplayPermissionManager.h"
62 #include "mozilla/dom/BlobURLProtocolHandler.h"
63 #include "mozilla/dom/ElementInlines.h"
64 #include "mozilla/dom/HTMLAudioElement.h"
65 #include "mozilla/dom/HTMLInputElement.h"
66 #include "mozilla/dom/HTMLMediaElementBinding.h"
67 #include "mozilla/dom/HTMLSourceElement.h"
68 #include "mozilla/dom/HTMLVideoElement.h"
69 #include "mozilla/dom/MediaEncryptedEvent.h"
70 #include "mozilla/dom/MediaErrorBinding.h"
71 #include "mozilla/dom/MediaSource.h"
72 #include "mozilla/dom/PlayPromise.h"
73 #include "mozilla/dom/Promise.h"
74 #include "mozilla/dom/TextTrack.h"
75 #include "mozilla/dom/VideoPlaybackQuality.h"
76 #include "mozilla/dom/VideoTrack.h"
77 #include "mozilla/dom/VideoTrackList.h"
78 #include "mozilla/dom/WakeLock.h"
79 #include "mozilla/dom/power/PowerManagerService.h"
80 #include "nsAttrValueInlines.h"
81 #include "nsContentPolicyUtils.h"
82 #include "nsContentUtils.h"
83 #include "nsCycleCollectionParticipant.h"
84 #include "nsDisplayList.h"
85 #include "nsDocShell.h"
87 #include "nsGenericHTMLElement.h"
88 #include "nsGkAtoms.h"
89 #include "nsIAsyncVerifyRedirectCallback.h"
90 #include "nsIAutoplay.h"
91 #include "nsICachingChannel.h"
92 #include "nsICategoryManager.h"
93 #include "nsIClassOfService.h"
94 #include "nsIContentPolicy.h"
95 #include "nsIContentSecurityPolicy.h"
96 #include "nsIDocShell.h"
97 #include "nsIDocument.h"
99 #include "nsIObserverService.h"
100 #include "nsIPermissionManager.h"
101 #include "nsIPresShell.h"
102 #include "nsIRequest.h"
103 #include "nsIScriptError.h"
104 #include "nsIScriptSecurityManager.h"
105 #include "nsISupportsPrimitives.h"
106 #include "nsIThreadInternal.h"
107 #include "nsITimer.h"
108 #include "nsIXPConnect.h"
109 #include "nsJSUtils.h"
110 #include "nsLayoutUtils.h"
111 #include "nsMediaFragmentURIParser.h"
112 #include "nsMimeTypes.h"
113 #include "nsNetUtil.h"
114 #include "nsNodeInfoManager.h"
115 #include "nsPresContext.h"
116 #include "nsQueryObject.h"
119 #include "nsThreadUtils.h"
120 #include "nsURIHashKey.h"
121 #include "nsVideoFrame.h"
122 #include "xpcpublic.h"
127 mozilla::LazyLogModule
gMediaElementLog("nsMediaElement");
128 static mozilla::LazyLogModule
gMediaElementEventsLog("nsMediaElementEvents");
130 extern mozilla::LazyLogModule gAutoplayPermissionLog
;
131 #define AUTOPLAY_LOG(msg, ...) \
132 MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
134 #define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg)
135 #define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg)
137 using namespace mozilla::layers
;
138 using mozilla::net::nsMediaFragmentURIParser
;
139 using namespace mozilla::dom::HTMLMediaElement_Binding
;
144 // Number of milliseconds between progress events as defined by spec
145 static const uint32_t PROGRESS_MS
= 350;
147 // Number of milliseconds of no data before a stall event is fired as defined by
149 static const uint32_t STALL_MS
= 3000;
151 // Used by AudioChannel for suppresssing the volume to this ratio.
152 #define FADED_VOLUME_RATIO 0.25
154 // These constants are arbitrary
155 // Minimum playbackRate for a media
156 static const double MIN_PLAYBACKRATE
= 1.0 / 16;
157 // Maximum playbackRate for a media
158 static const double MAX_PLAYBACKRATE
= 16.0;
159 // These are the limits beyonds which SoundTouch does not perform too well and
160 // when speech is hard to understand anyway. Threshold above which audio is
162 static const double THRESHOLD_HIGH_PLAYBACKRATE_AUDIO
= 4.0;
163 // Threshold under which audio is muted
164 static const double THRESHOLD_LOW_PLAYBACKRATE_AUDIO
= 0.5;
166 // Media error values. These need to match the ones in MediaError.webidl.
167 static const unsigned short MEDIA_ERR_ABORTED
= 1;
168 static const unsigned short MEDIA_ERR_NETWORK
= 2;
169 static const unsigned short MEDIA_ERR_DECODE
= 3;
170 static const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED
= 4;
173 ResolvePromisesWithUndefined(const nsTArray
<RefPtr
<PlayPromise
>>& aPromises
)
175 for (auto& promise
: aPromises
) {
176 promise
->MaybeResolveWithUndefined();
181 RejectPromises(const nsTArray
<RefPtr
<PlayPromise
>>& aPromises
, nsresult aError
)
183 for (auto& promise
: aPromises
) {
184 promise
->MaybeReject(aError
);
188 // Under certain conditions there may be no-one holding references to
189 // a media element from script, DOM parent, etc, but the element may still
190 // fire meaningful events in the future so we can't destroy it yet:
191 // 1) If the element is delaying the load event (or would be, if it were
192 // in a document), then events up to loadeddata or error could be fired,
193 // so we need to stay alive.
194 // 2) If the element is not paused and playback has not ended, then
195 // we will (or might) play, sending timeupdate and ended events and possibly
196 // audio output, so we need to stay alive.
197 // 3) if the element is seeking then we will fire seeking events and possibly
198 // start playing afterward, so we need to stay alive.
199 // 4) If autoplay could start playback in this element (if we got enough data),
200 // then we need to stay alive.
201 // 5) if the element is currently loading, not suspended, and its source is
202 // not a MediaSource, then script might be waiting for progress events or a
203 // 'stalled' or 'suspend' event, so we need to stay alive.
204 // If we're already suspended then (all other conditions being met),
205 // it's OK to just disappear without firing any more events,
206 // since we have the freedom to remain suspended indefinitely. Note
207 // that we could use this 'suspended' loophole to garbage-collect a suspended
208 // element in case 4 even if it had 'autoplay' set, but we choose not to.
209 // If someone throws away all references to a loading 'autoplay' element
210 // sound should still eventually play.
211 // 6) If the source is a MediaSource, most loading events will not fire unless
212 // appendBuffer() is called on a SourceBuffer, in which case something is
213 // already referencing the SourceBuffer, which keeps the associated media
214 // element alive. Further, a MediaSource will never time out the resource
215 // fetch, and so should not keep the media element alive if it is
216 // unreferenced. A pending 'stalled' event keeps the media element alive.
218 // Media elements owned by inactive documents (i.e. documents not contained in
219 // any document viewer) should never hold a self-reference because none of the
220 // above conditions are allowed: the element will stop loading and playing
221 // and never resume loading or playing unless its owner document changes to
222 // an active document (which can only happen if there is an external reference
224 // Media elements with no owner doc should be able to hold a self-reference.
225 // Something native must have created the element and may expect it to
226 // stay alive to play.
228 // It's very important that any change in state which could change the value of
229 // needSelfReference in AddRemoveSelfReference be followed by a call to
230 // AddRemoveSelfReference before this element could die!
231 // It's especially important if needSelfReference would change to 'true',
232 // since if we neglect to add a self-reference, this element might be
233 // garbage collected while there are still event listeners that should
234 // receive events. If we neglect to remove the self-reference then the element
235 // just lives longer than it needs to.
237 class nsMediaEvent
: public Runnable
240 explicit nsMediaEvent(const char* aName
, HTMLMediaElement
* aElement
)
243 , mLoadID(mElement
->GetCurrentLoadID())
248 NS_IMETHOD
Run() override
= 0;
251 bool IsCancelled() { return mElement
->GetCurrentLoadID() != mLoadID
; }
253 RefPtr
<HTMLMediaElement
> mElement
;
257 class HTMLMediaElement::nsAsyncEventRunner
: public nsMediaEvent
263 nsAsyncEventRunner(const nsAString
& aName
, HTMLMediaElement
* aElement
)
264 : nsMediaEvent("HTMLMediaElement::nsAsyncEventRunner", aElement
)
269 NS_IMETHOD
Run() override
271 // Silently cancel if our load has been cancelled.
275 return mElement
->DispatchEvent(mName
);
280 * If no error is passed while constructing an instance, the instance will
281 * resolve the passed promises with undefined; otherwise, the instance will
282 * reject the passed promises with the passed error.
284 * The constructor appends the constructed instance into the passed media
285 * element's mPendingPlayPromisesRunners member and once the the runner is run
286 * (whether fulfilled or canceled), it removes itself from
287 * mPendingPlayPromisesRunners.
289 class HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner
290 : public nsMediaEvent
292 nsTArray
<RefPtr
<PlayPromise
>> mPromises
;
296 nsResolveOrRejectPendingPlayPromisesRunner(
297 HTMLMediaElement
* aElement
,
298 nsTArray
<RefPtr
<PlayPromise
>>&& aPromises
,
299 nsresult aError
= NS_OK
)
301 "HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner",
303 , mPromises(std::move(aPromises
))
306 mElement
->mPendingPlayPromisesRunners
.AppendElement(this);
309 void ResolveOrReject()
311 if (NS_SUCCEEDED(mError
)) {
312 ResolvePromisesWithUndefined(mPromises
);
314 RejectPromises(mPromises
, mError
);
318 NS_IMETHOD
Run() override
320 if (!IsCancelled()) {
324 mElement
->mPendingPlayPromisesRunners
.RemoveElement(this);
329 class HTMLMediaElement::nsNotifyAboutPlayingRunner
330 : public nsResolveOrRejectPendingPlayPromisesRunner
333 nsNotifyAboutPlayingRunner(
334 HTMLMediaElement
* aElement
,
335 nsTArray
<RefPtr
<PlayPromise
>>&& aPendingPlayPromises
)
336 : nsResolveOrRejectPendingPlayPromisesRunner(aElement
,
337 std::move(aPendingPlayPromises
))
341 NS_IMETHOD
Run() override
344 mElement
->mPendingPlayPromisesRunners
.RemoveElement(this);
348 mElement
->DispatchEvent(NS_LITERAL_STRING("playing"));
349 return nsResolveOrRejectPendingPlayPromisesRunner::Run();
353 class nsSourceErrorEventRunner
: public nsMediaEvent
356 nsCOMPtr
<nsIContent
> mSource
;
359 nsSourceErrorEventRunner(HTMLMediaElement
* aElement
, nsIContent
* aSource
)
360 : nsMediaEvent("dom::nsSourceErrorEventRunner", aElement
)
365 NS_IMETHOD
Run() override
367 // Silently cancel if our load has been cancelled.
370 LOG_EVENT(LogLevel::Debug
,
371 ("%p Dispatching simple event source error", mElement
.get()));
372 return nsContentUtils::DispatchTrustedEvent(
373 mElement
->OwnerDoc(), mSource
, NS_LITERAL_STRING("error"),
374 CanBubble::eNo
, Cancelable::eNo
);
379 * This listener observes the first video frame to arrive with a non-empty size,
380 * and calls HTMLMediaElement::UpdateInitialMediaSize() with that size.
382 class HTMLMediaElement::StreamSizeListener
383 : public DirectMediaStreamTrackListener
386 explicit StreamSizeListener(HTMLMediaElement
* aElement
)
388 , mMainThreadEventTarget(aElement
->MainThreadEventTarget())
389 , mInitialSizeFound(false)
391 MOZ_ASSERT(mElement
);
392 MOZ_ASSERT(mMainThreadEventTarget
);
395 void Forget() { mElement
= nullptr; }
397 void ReceivedSize(gfx::IntSize aSize
)
399 MOZ_ASSERT(NS_IsMainThread());
405 RefPtr
<HTMLMediaElement
> deathGrip
= mElement
;
406 deathGrip
->UpdateInitialMediaSize(aSize
);
409 void NotifyRealtimeTrackData(MediaStreamGraph
* aGraph
,
410 StreamTime aTrackOffset
,
411 const MediaSegment
& aMedia
) override
413 if (mInitialSizeFound
) {
417 if (aMedia
.GetType() != MediaSegment::VIDEO
) {
418 MOZ_ASSERT(false, "Should only lock on to a video track");
422 const VideoSegment
& video
= static_cast<const VideoSegment
&>(aMedia
);
423 for (VideoSegment::ConstChunkIterator
c(video
); !c
.IsEnded(); c
.Next()) {
424 if (c
->mFrame
.GetIntrinsicSize() != gfx::IntSize(0, 0)) {
425 mInitialSizeFound
= true;
426 // This is fine to dispatch straight to main thread (instead of via
427 // ...AfterStreamUpdate()) since it reflects state of the element,
428 // not the stream. Events reflecting stream or track state should be
429 // dispatched so their order is preserved.
430 mMainThreadEventTarget
->Dispatch(NewRunnableMethod
<gfx::IntSize
>(
431 "dom::HTMLMediaElement::StreamSizeListener::ReceivedSize",
433 &StreamSizeListener::ReceivedSize
,
434 c
->mFrame
.GetIntrinsicSize()));
441 // These fields may only be accessed on the main thread
442 HTMLMediaElement
* mElement
;
443 // We hold mElement->MainThreadEventTarget() here because the mElement could
444 // be reset in Forget().
445 nsCOMPtr
<nsISerialEventTarget
> mMainThreadEventTarget
;
447 // These fields may only be accessed on the MSG's appending thread.
448 // (this is a direct listener so we get called by whoever is producing
449 // this track's data)
450 bool mInitialSizeFound
;
454 * There is a reference cycle involving this class: MediaLoadListener
455 * holds a reference to the HTMLMediaElement, which holds a reference
456 * to an nsIChannel, which holds a reference to this listener.
457 * We break the reference cycle in OnStartRequest by clearing mElement.
459 class HTMLMediaElement::MediaLoadListener final
460 : public nsIStreamListener
461 , public nsIChannelEventSink
462 , public nsIInterfaceRequestor
464 , public nsIThreadRetargetableStreamListener
466 ~MediaLoadListener() {}
468 NS_DECL_THREADSAFE_ISUPPORTS
469 NS_DECL_NSIREQUESTOBSERVER
470 NS_DECL_NSISTREAMLISTENER
471 NS_DECL_NSICHANNELEVENTSINK
473 NS_DECL_NSIINTERFACEREQUESTOR
474 NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
477 explicit MediaLoadListener(HTMLMediaElement
* aElement
)
479 , mLoadID(aElement
->GetCurrentLoadID())
481 MOZ_ASSERT(mElement
, "Must pass an element to call back");
485 RefPtr
<HTMLMediaElement
> mElement
;
486 nsCOMPtr
<nsIStreamListener
> mNextListener
;
487 const uint32_t mLoadID
;
490 NS_IMPL_ISUPPORTS(HTMLMediaElement::MediaLoadListener
,
494 nsIInterfaceRequestor
,
496 nsIThreadRetargetableStreamListener
)
499 HTMLMediaElement::MediaLoadListener::Observe(nsISupports
* aSubject
,
501 const char16_t
* aData
)
503 nsContentUtils::UnregisterShutdownObserver(this);
505 // Clear mElement to break cycle so we don't leak on shutdown
511 HTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest
* aRequest
,
512 nsISupports
* aContext
)
514 nsContentUtils::UnregisterShutdownObserver(this);
517 // We've been notified by the shutdown observer, and are shutting down.
518 return NS_BINDING_ABORTED
;
521 // Media element playback is not currently supported when recording or
522 // replaying. See bug 1304146.
523 if (recordreplay::IsRecordingOrReplaying()) {
524 mElement
->ReportLoadError("Media elements not available when recording", nullptr, 0);
525 return NS_ERROR_NOT_AVAILABLE
;
528 // The element is only needed until we've had a chance to call
529 // InitializeDecoderForChannel. So make sure mElement is cleared here.
530 RefPtr
<HTMLMediaElement
> element
;
531 element
.swap(mElement
);
533 AbstractThread::AutoEnter
context(element
->AbstractMainThread());
535 if (mLoadID
!= element
->GetCurrentLoadID()) {
536 // The channel has been cancelled before we had a chance to create
537 // a decoder. Abort, don't dispatch an "error" event, as the new load
538 // may not be in an error state.
539 return NS_BINDING_ABORTED
;
542 // Don't continue to load if the request failed or has been canceled.
544 nsresult rv
= aRequest
->GetStatus(&status
);
545 NS_ENSURE_SUCCESS(rv
, rv
);
546 if (NS_FAILED(status
)) {
548 // Handle media not loading error because source was a tracking URL.
549 // We make a note of this media node by including it in a dedicated
550 // array of blocked tracking nodes under its parent document.
551 if (status
== NS_ERROR_TRACKING_URI
) {
552 nsIDocument
* ownerDoc
= element
->OwnerDoc();
554 ownerDoc
->AddBlockedTrackingNode(element
);
557 element
->NotifyLoadError(
558 nsPrintfCString("%u: %s", uint32_t(status
), "Request failed"));
563 nsCOMPtr
<nsIHttpChannel
> hc
= do_QueryInterface(aRequest
);
565 if (hc
&& NS_SUCCEEDED(hc
->GetRequestSucceeded(&succeeded
)) && !succeeded
) {
566 uint32_t responseStatus
= 0;
567 Unused
<< hc
->GetResponseStatus(&responseStatus
);
568 nsAutoCString statusText
;
569 Unused
<< hc
->GetResponseStatusText(statusText
);
570 element
->NotifyLoadError(
571 nsPrintfCString("%u: %s", responseStatus
, statusText
.get()));
574 code
.AppendInt(responseStatus
);
576 element
->GetCurrentSrc(src
);
577 const char16_t
* params
[] = { code
.get(), src
.get() };
578 element
->ReportLoadError("MediaLoadHttpError", params
, ArrayLength(params
));
579 return NS_BINDING_ABORTED
;
582 nsCOMPtr
<nsIChannel
> channel
= do_QueryInterface(aRequest
);
584 NS_SUCCEEDED(rv
= element
->InitializeDecoderForChannel(
585 channel
, getter_AddRefs(mNextListener
))) &&
587 rv
= mNextListener
->OnStartRequest(aRequest
, aContext
);
589 // If InitializeDecoderForChannel() returned an error, fire a network error.
590 if (NS_FAILED(rv
) && !mNextListener
) {
591 // Load failed, attempt to load the next candidate resource. If there
592 // are none, this will trigger a MEDIA_ERR_SRC_NOT_SUPPORTED error.
593 element
->NotifyLoadError(NS_LITERAL_CSTRING("Failed to init decoder"));
595 // If InitializeDecoderForChannel did not return a listener (but may
596 // have otherwise succeeded), we abort the connection since we aren't
597 // interested in keeping the channel alive ourselves.
598 rv
= NS_BINDING_ABORTED
;
605 HTMLMediaElement::MediaLoadListener::OnStopRequest(nsIRequest
* aRequest
,
606 nsISupports
* aContext
,
610 return mNextListener
->OnStopRequest(aRequest
, aContext
, aStatus
);
616 HTMLMediaElement::MediaLoadListener::OnDataAvailable(nsIRequest
* aRequest
,
617 nsISupports
* aContext
,
618 nsIInputStream
* aStream
,
622 if (!mNextListener
) {
623 NS_ERROR("Must have a chained listener; OnStartRequest should have "
624 "canceled this request");
625 return NS_BINDING_ABORTED
;
627 return mNextListener
->OnDataAvailable(
628 aRequest
, aContext
, aStream
, aOffset
, aCount
);
632 HTMLMediaElement::MediaLoadListener::AsyncOnChannelRedirect(
633 nsIChannel
* aOldChannel
,
634 nsIChannel
* aNewChannel
,
636 nsIAsyncVerifyRedirectCallback
* cb
)
638 // TODO is this really correct?? See bug #579329.
640 mElement
->OnChannelRedirect(aOldChannel
, aNewChannel
, aFlags
);
642 nsCOMPtr
<nsIChannelEventSink
> sink
= do_QueryInterface(mNextListener
);
644 return sink
->AsyncOnChannelRedirect(aOldChannel
, aNewChannel
, aFlags
, cb
);
646 cb
->OnRedirectVerifyCallback(NS_OK
);
651 HTMLMediaElement::MediaLoadListener::CheckListenerChain()
653 MOZ_ASSERT(mNextListener
);
654 nsCOMPtr
<nsIThreadRetargetableStreamListener
> retargetable
=
655 do_QueryInterface(mNextListener
);
657 return retargetable
->CheckListenerChain();
659 return NS_ERROR_NO_INTERFACE
;
663 HTMLMediaElement::MediaLoadListener::GetInterface(const nsIID
& aIID
,
666 return QueryInterface(aIID
, aResult
);
670 HTMLMediaElement::ReportLoadError(const char* aMsg
,
671 const char16_t
** aParams
,
672 uint32_t aParamCount
)
674 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag
,
675 NS_LITERAL_CSTRING("Media"),
677 nsContentUtils::eDOM_PROPERTIES
,
683 class HTMLMediaElement::AudioChannelAgentCallback final
684 : public nsIAudioChannelAgentCallback
687 NS_DECL_CYCLE_COLLECTING_ISUPPORTS
688 NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgentCallback
)
690 explicit AudioChannelAgentCallback(HTMLMediaElement
* aOwner
)
692 , mAudioChannelVolume(1.0)
693 , mPlayingThroughTheAudioChannel(false)
694 , mAudioCapturedByWindow(false)
695 , mSuspended(nsISuspendedTypes::NONE_SUSPENDED
)
696 , mIsOwnerAudible(IsOwnerAudible())
700 MaybeCreateAudioChannelAgent();
703 void UpdateAudioChannelPlayingState(bool aForcePlaying
= false)
705 MOZ_ASSERT(!mIsShutDown
);
706 bool playingThroughTheAudioChannel
=
707 aForcePlaying
|| IsPlayingThroughTheAudioChannel();
709 if (playingThroughTheAudioChannel
!= mPlayingThroughTheAudioChannel
) {
710 if (!MaybeCreateAudioChannelAgent()) {
714 mPlayingThroughTheAudioChannel
= playingThroughTheAudioChannel
;
715 NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel
);
719 bool ShouldResetSuspend() const
721 // The disposable-pause should be clear after media starts playing.
722 if (!mOwner
->Paused() &&
723 mSuspended
== nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE
) {
727 // If the blocked media is paused, we don't need to resume it. We reset the
728 // mSuspended in order to unregister the agent.
729 if (mOwner
->Paused() && mSuspended
== nsISuspendedTypes::SUSPENDED_BLOCK
) {
736 void NotifyPlayStateChanged()
738 MOZ_ASSERT(!mIsShutDown
);
739 if (ShouldResetSuspend()) {
740 SetSuspended(nsISuspendedTypes::NONE_SUSPENDED
);
741 NotifyAudioPlaybackChanged(
742 AudioChannelService::AudibleChangedReasons::ePauseStateChanged
);
744 UpdateAudioChannelPlayingState();
747 NS_IMETHODIMP
WindowVolumeChanged(float aVolume
, bool aMuted
) override
749 MOZ_ASSERT(mAudioChannelAgent
);
752 AudioChannelService::GetAudioChannelLog(),
754 ("HTMLMediaElement::AudioChannelAgentCallback, WindowVolumeChanged, "
755 "this = %p, aVolume = %f, aMuted = %s\n",
758 aMuted
? "true" : "false"));
760 if (mAudioChannelVolume
!= aVolume
) {
761 mAudioChannelVolume
= aVolume
;
762 mOwner
->SetVolumeInternal();
765 const uint32_t muted
= mOwner
->mMuted
;
766 if (aMuted
&& !mOwner
->ComputedMuted()) {
767 mOwner
->SetMutedInternal(muted
| MUTED_BY_AUDIO_CHANNEL
);
768 } else if (!aMuted
&& mOwner
->ComputedMuted()) {
769 mOwner
->SetMutedInternal(muted
& ~MUTED_BY_AUDIO_CHANNEL
);
775 NS_IMETHODIMP
WindowSuspendChanged(SuspendTypes aSuspend
) override
777 MOZ_ASSERT(mAudioChannelAgent
);
780 AudioChannelService::GetAudioChannelLog(),
782 ("HTMLMediaElement::AudioChannelAgentCallback, WindowSuspendChanged, "
783 "this = %p, aSuspend = %s\n",
785 SuspendTypeToStr(aSuspend
)));
788 case nsISuspendedTypes::NONE_SUSPENDED
:
791 case nsISuspendedTypes::SUSPENDED_PAUSE
:
792 case nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE
:
793 case nsISuspendedTypes::SUSPENDED_BLOCK
:
796 case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE
:
801 AudioChannelService::GetAudioChannelLog(),
803 ("HTMLMediaElement::AudioChannelAgentCallback, WindowSuspendChanged, "
804 "this = %p, Error : unknown suspended type!\n",
810 NS_IMETHODIMP
WindowAudioCaptureChanged(bool aCapture
) override
812 MOZ_ASSERT(mAudioChannelAgent
);
814 if (mAudioCapturedByWindow
!= aCapture
) {
815 mAudioCapturedByWindow
= aCapture
;
816 AudioCaptureStreamChangeIfNeeded();
821 void AudioCaptureStreamChangeIfNeeded()
823 MOZ_ASSERT(!mIsShutDown
);
824 if (!IsPlayingStarted()) {
828 if (!mOwner
->HasAudio()) {
832 mOwner
->AudioCaptureStreamChange(mAudioCapturedByWindow
);
835 void NotifyAudioPlaybackChanged(AudibleChangedReasons aReason
)
837 MOZ_ASSERT(!mIsShutDown
);
838 if (!IsPlayingStarted()) {
842 AudibleState newAudibleState
= IsOwnerAudible();
843 if (mIsOwnerAudible
== newAudibleState
) {
847 mIsOwnerAudible
= newAudibleState
;
848 mAudioChannelAgent
->NotifyStartedAudible(mIsOwnerAudible
, aReason
);
851 bool IsPlaybackBlocked()
853 MOZ_ASSERT(!mIsShutDown
);
854 // If the tab hasn't been activated yet, the media element in that tab can't
855 // be playback now until the tab goes to foreground first time or user
856 // clicks the unblocking tab icon.
857 if (!IsTabActivated()) {
858 // Even we haven't start playing yet, we still need to notify the audio
859 // channe system because we need to receive the resume notification later.
860 UpdateAudioChannelPlayingState(true /* force to start */);
869 MOZ_ASSERT(!mIsShutDown
);
870 if (mAudioChannelAgent
) {
871 mAudioChannelAgent
->NotifyStoppedPlaying();
872 mAudioChannelAgent
= nullptr;
877 float GetEffectiveVolume() const
879 MOZ_ASSERT(!mIsShutDown
);
880 return mOwner
->Volume() * mAudioChannelVolume
;
883 SuspendTypes
GetSuspendType() const
885 MOZ_ASSERT(!mIsShutDown
);
890 ~AudioChannelAgentCallback() { MOZ_ASSERT(mIsShutDown
); };
892 bool MaybeCreateAudioChannelAgent()
894 if (mAudioChannelAgent
) {
898 mAudioChannelAgent
= new AudioChannelAgent();
900 mAudioChannelAgent
->Init(mOwner
->OwnerDoc()->GetInnerWindow(), this);
901 if (NS_WARN_IF(NS_FAILED(rv
))) {
902 mAudioChannelAgent
= nullptr;
904 AudioChannelService::GetAudioChannelLog(),
906 ("HTMLMediaElement::AudioChannelAgentCallback, Fail to initialize "
907 "the audio channel agent, this = %p\n",
915 void NotifyAudioChannelAgent(bool aPlaying
)
917 MOZ_ASSERT(mAudioChannelAgent
);
920 AudioPlaybackConfig config
;
922 mAudioChannelAgent
->NotifyStartedPlaying(&config
, IsOwnerAudible());
923 if (NS_WARN_IF(NS_FAILED(rv
))) {
927 WindowVolumeChanged(config
.mVolume
, config
.mMuted
);
928 WindowSuspendChanged(config
.mSuspend
);
930 mAudioChannelAgent
->NotifyStoppedPlaying();
934 void SetSuspended(SuspendTypes aSuspend
)
936 if (mSuspended
== aSuspend
) {
940 MaybeNotifyMediaResumed(aSuspend
);
941 mSuspended
= aSuspend
;
943 AudioChannelService::GetAudioChannelLog(),
945 ("HTMLMediaElement::AudioChannelAgentCallback, SetAudioChannelSuspended, "
946 "this = %p, aSuspend = %s\n",
948 SuspendTypeToStr(aSuspend
)));
953 if (!IsSuspended()) {
955 AudioChannelService::GetAudioChannelLog(),
957 ("HTMLMediaElement::AudioChannelAgentCallback, ResumeFromAudioChannel, "
958 "this = %p, don't need to be resumed!\n",
963 SetSuspended(nsISuspendedTypes::NONE_SUSPENDED
);
964 IgnoredErrorResult rv
;
965 RefPtr
<Promise
> toBeIgnored
= mOwner
->Play(rv
);
966 MOZ_ASSERT_IF(toBeIgnored
&&
967 toBeIgnored
->State() == Promise::PromiseState::Rejected
,
970 NS_WARNING("Not able to resume from AudioChannel.");
973 NotifyAudioPlaybackChanged(
974 AudioChannelService::AudibleChangedReasons::ePauseStateChanged
);
977 void Suspend(SuspendTypes aSuspend
)
983 SetSuspended(aSuspend
);
984 if (aSuspend
== nsISuspendedTypes::SUSPENDED_PAUSE
||
985 aSuspend
== nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE
) {
986 IgnoredErrorResult rv
;
988 if (NS_WARN_IF(rv
.Failed())) {
992 NotifyAudioPlaybackChanged(
993 AudioChannelService::AudibleChangedReasons::ePauseStateChanged
);
998 SetSuspended(nsISuspendedTypes::NONE_SUSPENDED
);
1002 bool IsPlayingStarted()
1004 if (MaybeCreateAudioChannelAgent()) {
1005 return mAudioChannelAgent
->IsPlayingStarted();
1010 void MaybeNotifyMediaResumed(SuspendTypes aSuspend
)
1012 // In fennec, we should send the notification when media is resumed from the
1013 // pause-disposable which was called by media control.
1014 if (mSuspended
!= nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE
&&
1015 aSuspend
!= nsISuspendedTypes::NONE_SUSPENDED
) {
1019 if (!IsPlayingStarted()) {
1023 uint64_t windowID
= mAudioChannelAgent
->WindowID();
1024 mOwner
->MainThreadEventTarget()->Dispatch(NS_NewRunnableFunction(
1025 "dom::HTMLMediaElement::AudioChannelAgentCallback::"
1026 "MaybeNotifyMediaResumed",
1027 [windowID
]() -> void {
1028 nsCOMPtr
<nsIObserverService
> observerService
=
1029 services::GetObserverService();
1030 if (NS_WARN_IF(!observerService
)) {
1034 nsCOMPtr
<nsISupportsPRUint64
> wrapper
=
1035 do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID
);
1036 if (NS_WARN_IF(!wrapper
)) {
1040 wrapper
->SetData(windowID
);
1041 observerService
->NotifyObservers(
1042 wrapper
, "media-playback-resumed", u
"active");
1046 bool IsTabActivated()
1048 if (MaybeCreateAudioChannelAgent()) {
1049 return !mAudioChannelAgent
->ShouldBlockMedia();
1054 bool IsSuspended() const
1056 return (mSuspended
== nsISuspendedTypes::SUSPENDED_PAUSE
||
1057 mSuspended
== nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE
||
1058 mSuspended
== nsISuspendedTypes::SUSPENDED_BLOCK
);
1061 AudibleState
IsOwnerAudible() const
1063 // Muted or the volume should not be ~0
1064 if (mOwner
->mMuted
|| (std::fabs(mOwner
->Volume()) <= 1e-7)) {
1065 return mOwner
->HasAudio()
1066 ? AudioChannelService::AudibleState::eMaybeAudible
1067 : AudioChannelService::AudibleState::eNotAudible
;
1071 if (!mOwner
->HasAudio()) {
1072 return AudioChannelService::AudibleState::eNotAudible
;
1075 // Might be audible but not yet.
1076 if (mOwner
->HasAudio() && !mOwner
->mIsAudioTrackAudible
) {
1077 return AudioChannelService::AudibleState::eMaybeAudible
;
1080 // Suspended or paused media doesn't produce any sound.
1081 if (mSuspended
!= nsISuspendedTypes::NONE_SUSPENDED
|| mOwner
->mPaused
) {
1082 return AudioChannelService::AudibleState::eNotAudible
;
1085 return AudioChannelService::AudibleState::eAudible
;
1088 bool IsPlayingThroughTheAudioChannel() const
1090 // If we have an error, we are not playing.
1091 if (mOwner
->GetError()) {
1095 // We should consider any bfcached page or inactive document as non-playing.
1096 if (!mOwner
->IsActive()) {
1100 // It might be resumed from remote, we should keep the audio channel agent.
1101 if (IsSuspended()) {
1106 if (mOwner
->mPaused
) {
1111 if (!mOwner
->HasAudio()) {
1115 // A loop always is playing
1116 if (mOwner
->HasAttr(kNameSpaceID_None
, nsGkAtoms::loop
)) {
1120 // If we are actually playing...
1121 if (mOwner
->IsCurrentlyPlaying()) {
1125 // If we are playing an external stream.
1126 if (mOwner
->mSrcAttrStream
) {
1133 RefPtr
<AudioChannelAgent
> mAudioChannelAgent
;
1134 HTMLMediaElement
* mOwner
;
1136 // The audio channel volume
1137 float mAudioChannelVolume
;
1138 // Is this media element playing?
1139 bool mPlayingThroughTheAudioChannel
;
1140 // True if the sound is being captured by the window.
1141 bool mAudioCapturedByWindow
;
1142 // We have different kinds of suspended cases,
1143 // - SUSPENDED_PAUSE
1144 // It's used when we temporary lost platform audio focus. MediaElement can
1145 // only be resumed when we gain the audio focus again.
1146 // - SUSPENDED_PAUSE_DISPOSABLE
1147 // It's used when user press the pause button on the remote media-control.
1148 // MediaElement can be resumed by remote media-control or via play().
1149 // - SUSPENDED_BLOCK
1150 // It's used to reduce the power consumption, we won't play the auto-play
1151 // audio/video in the page we have never visited before. MediaElement would
1152 // be resumed when the page is active. See bug647429 for more details.
1153 // - SUSPENDED_STOP_DISPOSABLE
1154 // When we permanently lost platform audio focus, we should stop playing
1155 // and stop the audio channel agent. MediaElement can only be restarted by
1157 SuspendTypes mSuspended
;
1158 // Indicate whether media element is audible for users.
1159 AudibleState mIsOwnerAudible
;
1163 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::AudioChannelAgentCallback
)
1165 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
1166 HTMLMediaElement::AudioChannelAgentCallback
)
1167 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent
)
1168 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1170 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(
1171 HTMLMediaElement::AudioChannelAgentCallback
)
1172 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent
)
1173 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1175 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
1176 HTMLMediaElement::AudioChannelAgentCallback
)
1177 NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback
)
1178 NS_INTERFACE_MAP_END
1180 NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLMediaElement::AudioChannelAgentCallback
)
1181 NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLMediaElement::AudioChannelAgentCallback
)
1183 class HTMLMediaElement::ChannelLoader final
1186 NS_INLINE_DECL_REFCOUNTING(ChannelLoader
);
1188 void LoadInternal(HTMLMediaElement
* aElement
)
1194 // determine what security checks need to be performed in AsyncOpen2().
1195 nsSecurityFlags securityFlags
=
1196 aElement
->ShouldCheckAllowOrigin()
1197 ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
1198 : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS
;
1200 if (aElement
->GetCORSMode() == CORS_USE_CREDENTIALS
) {
1201 securityFlags
|= nsILoadInfo::SEC_COOKIES_INCLUDE
;
1205 aElement
->IsAnyOfHTMLElements(nsGkAtoms::audio
, nsGkAtoms::video
));
1206 nsContentPolicyType contentPolicyType
=
1207 aElement
->IsHTMLElement(nsGkAtoms::audio
)
1208 ? nsIContentPolicy::TYPE_INTERNAL_AUDIO
1209 : nsIContentPolicy::TYPE_INTERNAL_VIDEO
;
1211 // If aElement has 'triggeringprincipal' attribute, we will use the value as
1212 // triggeringPrincipal for the channel, otherwise it will default to use
1213 // aElement->NodePrincipal().
1214 // This function returns true when aElement has 'triggeringprincipal', so if
1215 // setAttrs is true we will override the origin attributes on the channel
1217 nsCOMPtr
<nsIPrincipal
> triggeringPrincipal
;
1218 bool setAttrs
= nsContentUtils::QueryTriggeringPrincipal(
1220 aElement
->mLoadingSrcTriggeringPrincipal
,
1221 getter_AddRefs(triggeringPrincipal
));
1223 nsCOMPtr
<nsILoadGroup
> loadGroup
= aElement
->GetDocumentLoadGroup();
1224 nsCOMPtr
<nsIChannel
> channel
;
1225 nsresult rv
= NS_NewChannelWithTriggeringPrincipal(
1226 getter_AddRefs(channel
),
1227 aElement
->mLoadingSrc
,
1228 static_cast<Element
*>(aElement
),
1229 triggeringPrincipal
,
1232 nullptr, // aPerformanceStorage
1234 nullptr, // aCallbacks
1235 nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY
|
1236 nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE
|
1237 nsIChannel::LOAD_CLASSIFY_URI
| nsIChannel::LOAD_CALL_CONTENT_SNIFFERS
);
1239 if (NS_FAILED(rv
)) {
1240 // Notify load error so the element will try next resource candidate.
1241 aElement
->NotifyLoadError(NS_LITERAL_CSTRING("Fail to create channel"));
1246 nsCOMPtr
<nsILoadInfo
> loadInfo
= channel
->GetLoadInfo();
1248 // The function simply returns NS_OK, so we ignore the return value.
1249 Unused
<< loadInfo
->SetOriginAttributes(
1250 triggeringPrincipal
->OriginAttributesRef());
1254 nsCOMPtr
<nsIClassOfService
> cos(do_QueryInterface(channel
));
1256 if (aElement
->mUseUrgentStartForChannel
) {
1257 cos
->AddClassFlags(nsIClassOfService::UrgentStart
);
1259 // Reset the flag to avoid loading again without initiated by user
1261 aElement
->mUseUrgentStartForChannel
= false;
1264 // Unconditionally disable throttling since we want the media to fluently
1265 // play even when we switch the tab to background.
1266 cos
->AddClassFlags(nsIClassOfService::DontThrottle
);
1269 // The listener holds a strong reference to us. This creates a
1270 // reference cycle, once we've set mChannel, which is manually broken
1271 // in the listener's OnStartRequest method after it is finished with
1272 // the element. The cycle will also be broken if we get a shutdown
1273 // notification before OnStartRequest fires. Necko guarantees that
1274 // OnStartRequest will eventually fire if we don't shut down first.
1275 RefPtr
<MediaLoadListener
> loadListener
= new MediaLoadListener(aElement
);
1277 channel
->SetNotificationCallbacks(loadListener
);
1279 nsCOMPtr
<nsIHttpChannel
> hc
= do_QueryInterface(channel
);
1281 // Use a byte range request from the start of the resource.
1282 // This enables us to detect if the stream supports byte range
1283 // requests, and therefore seeking, early.
1284 rv
= hc
->SetRequestHeader(
1285 NS_LITERAL_CSTRING("Range"), NS_LITERAL_CSTRING("bytes=0-"), false);
1286 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1287 aElement
->SetRequestHeaders(hc
);
1290 rv
= channel
->AsyncOpen2(loadListener
);
1291 if (NS_FAILED(rv
)) {
1292 // Notify load error so the element will try next resource candidate.
1293 aElement
->NotifyLoadError(NS_LITERAL_CSTRING("Failed to open channel"));
1297 // Else the channel must be open and starting to download. If it encounters
1298 // a non-catastrophic failure, it will set a new task to continue loading
1299 // another candidate. It's safe to set it as mChannel now.
1302 // loadListener will be unregistered either on shutdown or when
1303 // OnStartRequest for the channel we just opened fires.
1304 nsContentUtils::RegisterShutdownObserver(loadListener
);
1307 nsresult
Load(HTMLMediaElement
* aElement
)
1309 MOZ_ASSERT(aElement
);
1310 // Per bug 1235183 comment 8, we can't spin the event loop from stable
1311 // state. Defer NS_NewChannel() to a new regular runnable.
1312 return aElement
->MainThreadEventTarget()->Dispatch(
1313 NewRunnableMethod
<HTMLMediaElement
*>("ChannelLoader::LoadInternal",
1315 &ChannelLoader::LoadInternal
,
1323 mChannel
->Cancel(NS_BINDING_ABORTED
);
1330 MOZ_ASSERT(mChannel
);
1331 // Decoder successfully created, the decoder now owns the MediaResource
1332 // which owns the channel.
1336 nsresult
Redirect(nsIChannel
* aChannel
,
1337 nsIChannel
* aNewChannel
,
1340 NS_ASSERTION(aChannel
== mChannel
, "Channels should match!");
1341 mChannel
= aNewChannel
;
1343 // Handle forwarding of Range header so that the intial detection
1344 // of seeking support (via result code 206) works across redirects.
1345 nsCOMPtr
<nsIHttpChannel
> http
= do_QueryInterface(aChannel
);
1346 NS_ENSURE_STATE(http
);
1348 NS_NAMED_LITERAL_CSTRING(rangeHdr
, "Range");
1350 nsAutoCString rangeVal
;
1351 if (NS_SUCCEEDED(http
->GetRequestHeader(rangeHdr
, rangeVal
))) {
1352 NS_ENSURE_STATE(!rangeVal
.IsEmpty());
1354 http
= do_QueryInterface(aNewChannel
);
1355 NS_ENSURE_STATE(http
);
1357 nsresult rv
= http
->SetRequestHeader(rangeHdr
, rangeVal
, false);
1358 NS_ENSURE_SUCCESS(rv
, rv
);
1365 ~ChannelLoader() { MOZ_ASSERT(!mChannel
); }
1366 // Holds a reference to the first channel we open to the media resource.
1367 // Once the decoder is created, control over the channel passes to the
1368 // decoder, and we null out this reference. We must store this in case
1369 // we need to cancel the channel before control of it passes to the decoder.
1370 nsCOMPtr
<nsIChannel
> mChannel
;
1372 bool mCancelled
= false;
1375 class HTMLMediaElement::ErrorSink
1378 explicit ErrorSink(HTMLMediaElement
* aOwner
)
1384 void SetError(uint16_t aErrorCode
, const nsACString
& aErrorDetails
)
1386 // Since we have multiple paths calling into DecodeError, e.g.
1387 // MediaKeys::Terminated and EMEH264Decoder::Error. We should take the 1st
1388 // one only in order not to fire multiple 'error' events.
1393 if (!IsValidErrorCode(aErrorCode
)) {
1394 NS_ASSERTION(false, "Undefined MediaError codes!");
1399 mError
= new MediaError(mOwner
, aErrorCode
, aErrorDetails
);
1400 mOwner
->DispatchAsyncEvent(NS_LITERAL_STRING("error"));
1401 if (mOwner
->ReadyState() == HAVE_NOTHING
&&
1402 aErrorCode
== MEDIA_ERR_ABORTED
) {
1403 // https://html.spec.whatwg.org/multipage/embedded-content.html#media-data-processing-steps-list
1404 // "If the media data fetching process is aborted by the user"
1405 mOwner
->DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
1406 mOwner
->ChangeNetworkState(NETWORK_EMPTY
);
1407 mOwner
->DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
1408 if (mOwner
->mDecoder
) {
1409 mOwner
->ShutdownDecoder();
1411 } else if (aErrorCode
== MEDIA_ERR_SRC_NOT_SUPPORTED
) {
1412 mOwner
->ChangeNetworkState(NETWORK_NO_SOURCE
);
1414 mOwner
->ChangeNetworkState(NETWORK_IDLE
);
1423 RefPtr
<MediaError
> mError
;
1426 bool IsValidErrorCode(const uint16_t& aErrorCode
) const
1428 return (aErrorCode
== MEDIA_ERR_DECODE
|| aErrorCode
== MEDIA_ERR_NETWORK
||
1429 aErrorCode
== MEDIA_ERR_ABORTED
||
1430 aErrorCode
== MEDIA_ERR_SRC_NOT_SUPPORTED
);
1433 // Media elememt's life cycle would be longer than error sink, so we use the
1434 // raw pointer and this class would only be referenced by media element.
1435 HTMLMediaElement
* mOwner
;
1438 NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement
)
1440 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement
,
1441 nsGenericHTMLElement
)
1442 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource
)
1443 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcMediaSource
)
1444 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcStream
)
1445 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcAttrStream
)
1446 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourcePointer
)
1447 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadBlockedDoc
)
1448 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceLoadCandidate
)
1449 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelWrapper
)
1450 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mErrorSink
->mError
)
1451 for (uint32_t i
= 0; i
< tmp
->mOutputStreams
.Length(); ++i
) {
1452 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams
[i
].mStream
)
1453 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams
[i
].mPreCreatedTracks
)
1455 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed
);
1456 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager
)
1457 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList
)
1458 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList
)
1459 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys
)
1460 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingMediaKeys
)
1461 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack
)
1462 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayPromises
)
1463 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSeekDOMPromise
)
1464 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSetMediaKeysDOMPromise
)
1465 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1467 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement
,
1468 nsGenericHTMLElement
)
1469 tmp
->RemoveMutationObserver(tmp
);
1470 if (tmp
->mSrcStream
) {
1471 // Need to EndMediaStreamPlayback to clear mSrcStream and make sure
1472 // everything gets unhooked correctly.
1473 tmp
->EndSrcMediaStreamPlayback();
1475 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcAttrStream
)
1476 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource
)
1477 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcMediaSource
)
1478 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourcePointer
)
1479 NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoadBlockedDoc
)
1480 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate
)
1481 if (tmp
->mAudioChannelWrapper
) {
1482 tmp
->mAudioChannelWrapper
->Shutdown();
1484 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelWrapper
)
1485 NS_IMPL_CYCLE_COLLECTION_UNLINK(mErrorSink
->mError
)
1486 NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams
)
1487 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed
)
1488 NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager
)
1489 NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList
)
1490 NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList
)
1491 NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys
)
1492 NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingMediaKeys
)
1493 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack
)
1494 NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayPromises
)
1495 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSeekDOMPromise
)
1496 NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetMediaKeysDOMPromise
)
1497 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1499 NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLMediaElement
,
1500 nsGenericHTMLElement
)
1503 HTMLMediaElement::ContentRemoved(nsIContent
* aChild
,
1504 nsIContent
* aPreviousSibling
)
1506 if (aChild
== mSourcePointer
) {
1507 mSourcePointer
= aPreviousSibling
;
1511 already_AddRefed
<MediaSource
>
1512 HTMLMediaElement::GetMozMediaSourceObject() const
1514 RefPtr
<MediaSource
> source
= mMediaSource
;
1515 return source
.forget();
1519 HTMLMediaElement::GetMozDebugReaderData(nsAString
& aString
)
1521 if (mDecoder
&& !mSrcStream
) {
1522 nsAutoCString result
;
1523 mDecoder
->GetMozDebugReaderData(result
);
1524 CopyUTF8toUTF16(result
, aString
);
1528 already_AddRefed
<Promise
>
1529 HTMLMediaElement::MozRequestDebugInfo(ErrorResult
& aRv
)
1531 RefPtr
<Promise
> promise
= CreateDOMPromise(aRv
);
1532 if (NS_WARN_IF(aRv
.Failed())) {
1536 nsAutoString result
;
1537 GetMozDebugReaderData(result
);
1539 if (mVideoFrameContainer
) {
1540 result
.AppendPrintf(
1541 "Compositor dropped frame(including when element's invisible): %u\n",
1542 mVideoFrameContainer
->GetDroppedImageCount());
1547 GetEMEInfo(EMEInfo
);
1548 result
.AppendLiteral("EME Info: ");
1549 result
.Append(EMEInfo
);
1550 result
.AppendLiteral("\n");
1554 mDecoder
->RequestDebugInfo()->Then(
1555 mAbstractMainThread
,
1557 [promise
, result
](const nsACString
& aString
) {
1558 promise
->MaybeResolve(result
+ NS_ConvertUTF8toUTF16(aString
));
1560 [promise
, result
]() { promise
->MaybeResolve(result
); });
1562 promise
->MaybeResolve(result
);
1565 return promise
.forget();
1569 HTMLMediaElement::MozEnableDebugLog(const GlobalObject
&)
1571 DecoderDoctorLogger::EnableLogging();
1574 already_AddRefed
<Promise
>
1575 HTMLMediaElement::MozRequestDebugLog(ErrorResult
& aRv
)
1577 RefPtr
<Promise
> promise
= CreateDOMPromise(aRv
);
1578 if (NS_WARN_IF(aRv
.Failed())) {
1582 DecoderDoctorLogger::RetrieveMessages(this)->Then(
1583 mAbstractMainThread
,
1585 [promise
](const nsACString
& aString
) {
1586 promise
->MaybeResolve(NS_ConvertUTF8toUTF16(aString
));
1588 [promise
](nsresult rv
) { promise
->MaybeReject(rv
); });
1590 return promise
.forget();
1593 already_AddRefed
<Promise
>
1594 HTMLMediaElement::MozDumpDebugInfo()
1597 RefPtr
<Promise
> promise
= CreateDOMPromise(rv
);
1598 if (NS_WARN_IF(rv
.Failed())) {
1602 mDecoder
->DumpDebugInfo()->Then(mAbstractMainThread
,
1605 &Promise::MaybeResolveWithUndefined
);
1607 promise
->MaybeResolveWithUndefined();
1609 return promise
.forget();
1613 HTMLMediaElement::SetVisible(bool aVisible
)
1615 mForcedHidden
= !aVisible
;
1617 mDecoder
->SetForcedHidden(!aVisible
);
1621 already_AddRefed
<layers::Image
>
1622 HTMLMediaElement::GetCurrentImage()
1626 // TODO: In bug 1345404, handle case when video decoder is already suspended.
1627 ImageContainer
* container
= GetImageContainer();
1632 AutoLockImage
lockImage(container
);
1633 RefPtr
<layers::Image
> image
= lockImage
.GetImage(TimeStamp::Now());
1634 return image
.forget();
1638 HTMLMediaElement::HasSuspendTaint() const
1640 MOZ_ASSERT(!mDecoder
|| (mDecoder
->HasSuspendTaint() == mHasSuspendTaint
));
1641 return mHasSuspendTaint
;
1644 already_AddRefed
<DOMMediaStream
>
1645 HTMLMediaElement::GetSrcObject() const
1647 NS_ASSERTION(!mSrcAttrStream
|| mSrcAttrStream
->GetPlaybackStream(),
1648 "MediaStream should have been set up properly");
1649 RefPtr
<DOMMediaStream
> stream
= mSrcAttrStream
;
1650 return stream
.forget();
1654 HTMLMediaElement::SetSrcObject(DOMMediaStream
& aValue
)
1656 SetSrcObject(&aValue
);
1660 HTMLMediaElement::SetSrcObject(DOMMediaStream
* aValue
)
1662 mSrcAttrStream
= aValue
;
1663 UpdateAudioChannelPlayingState();
1668 HTMLMediaElement::Ended()
1670 return (mDecoder
&& mDecoder
->IsEnded()) ||
1671 (mSrcStream
&& !mSrcStream
->Active());
1675 HTMLMediaElement::GetCurrentSrc(nsAString
& aCurrentSrc
)
1678 GetCurrentSpec(src
);
1679 CopyUTF8toUTF16(src
, aCurrentSrc
);
1683 HTMLMediaElement::OnChannelRedirect(nsIChannel
* aChannel
,
1684 nsIChannel
* aNewChannel
,
1687 MOZ_ASSERT(mChannelLoader
);
1688 return mChannelLoader
->Redirect(aChannel
, aNewChannel
, aFlags
);
1692 HTMLMediaElement::ShutdownDecoder()
1694 RemoveMediaElementFromURITable();
1695 NS_ASSERTION(mDecoder
, "Must have decoder to shut down");
1697 mWaitingForKeyListener
.DisconnectIfExists();
1699 mMediaSource
->CompletePendingTransactions();
1701 for (OutputMediaStream
& out
: mOutputStreams
) {
1702 if (!out
.mCapturingDecoder
) {
1705 out
.mNextAvailableTrackID
= std::max
<TrackID
>(
1706 mDecoder
->NextAvailableTrackIDFor(out
.mStream
->GetInputStream()),
1707 out
.mNextAvailableTrackID
);
1709 mDecoder
->Shutdown();
1710 DDUNLINKCHILD(mDecoder
.get());
1712 ReportAudioTrackSilenceProportionTelemetry();
1716 HTMLMediaElement::AbortExistingLoads()
1718 // Abort any already-running instance of the resource selection algorithm.
1719 mLoadWaitStatus
= NOT_WAITING
;
1721 // Set a new load ID. This will cause events which were enqueued
1722 // with a different load ID to silently be cancelled.
1725 // Immediately reject or resolve the already-dispatched
1726 // nsResolveOrRejectPendingPlayPromisesRunners. These runners won't be
1727 // executed again later since the mCurrentLoadID had been changed.
1728 for (auto& runner
: mPendingPlayPromisesRunners
) {
1729 runner
->ResolveOrReject();
1731 mPendingPlayPromisesRunners
.Clear();
1733 if (mChannelLoader
) {
1734 mChannelLoader
->Cancel();
1735 mChannelLoader
= nullptr;
1738 bool fireTimeUpdate
= false;
1740 // We need to remove StreamSizeListener before VideoTracks get emptied.
1741 if (mMediaStreamSizeListener
) {
1742 mSelectedVideoStreamTrack
->RemoveDirectListener(mMediaStreamSizeListener
);
1743 mMediaStreamSizeListener
->Forget();
1744 mMediaStreamSizeListener
= nullptr;
1747 // When aborting the existing loads, empty the objects in audio track list and
1748 // video track list, no events (in particular, no removetrack events) are
1749 // fired as part of this. Ending MediaStream sends track ended notifications,
1750 // so we empty the track lists prior.
1751 AudioTracks()->EmptyTracks();
1752 VideoTracks()->EmptyTracks();
1755 fireTimeUpdate
= mDecoder
->GetCurrentTime() != 0.0;
1759 EndSrcMediaStreamPlayback();
1762 RemoveMediaElementFromURITable();
1763 mLoadingSrc
= nullptr;
1764 mLoadingSrcTriggeringPrincipal
= nullptr;
1765 DDLOG(DDLogCategory::Property
, "loading_src", "");
1766 DDUNLINKCHILD(mMediaSource
.get());
1767 mMediaSource
= nullptr;
1769 if (mNetworkState
== NETWORK_LOADING
|| mNetworkState
== NETWORK_IDLE
) {
1770 DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
1773 mErrorSink
->ResetError();
1774 mCurrentPlayRangeStart
= -1.0;
1775 mLoadedDataFired
= false;
1776 mAutoplaying
= true;
1777 mIsLoadingFromSourceChildren
= false;
1778 mSuspendedAfterFirstFrame
= false;
1779 mAllowSuspendAfterFirstFrame
= true;
1780 mHaveQueuedSelectResource
= false;
1781 mSuspendedForPreloadNone
= false;
1782 mDownloadSuspendedByCache
= false;
1783 mMediaInfo
= MediaInfo();
1784 mIsEncrypted
= false;
1785 mPendingEncryptedInitData
.Reset();
1786 mWaitingForKey
= NOT_WAITING_FOR_KEY
;
1787 mSourcePointer
= nullptr;
1788 mBlockedAsWithoutMetadata
= false;
1791 mAudioTrackSilenceStartedTime
= 0.0;
1793 if (mNetworkState
!= NETWORK_EMPTY
) {
1794 NS_ASSERTION(!mDecoder
&& !mSrcStream
,
1795 "How did someone setup a new stream/decoder already?");
1796 // ChangeNetworkState() will call UpdateAudioChannelPlayingState()
1797 // indirectly which depends on mPaused. So we need to update mPaused first.
1800 DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
1801 RejectPromises(TakePendingPlayPromises(), NS_ERROR_DOM_MEDIA_ABORT_ERR
);
1803 ChangeNetworkState(NETWORK_EMPTY
);
1804 ChangeReadyState(HAVE_NOTHING
);
1806 // TODO: Apply the rules for text track cue rendering Bug 865407
1807 if (mTextTrackManager
) {
1808 mTextTrackManager
->GetTextTracks()->SetCuesInactive();
1811 if (fireTimeUpdate
) {
1812 // Since we destroyed the decoder above, the current playback position
1813 // will now be reported as 0. The playback position was non-zero when
1814 // we destroyed the decoder, so fire a timeupdate event so that the
1815 // change will be reflected in the controls.
1816 FireTimeUpdate(false);
1818 DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
1819 UpdateAudioChannelPlayingState();
1822 // Disconnect requests for permission to play. We'll make a new request
1823 // if required should the new media resource try to play.
1824 mAutoplayPermissionRequest
.DisconnectIfExists();
1826 // We may have changed mPaused, mAutoplaying, and other
1827 // things which can affect AddRemoveSelfReference
1828 AddRemoveSelfReference();
1830 mIsRunningSelectResource
= false;
1832 if (mTextTrackManager
) {
1833 mTextTrackManager
->NotifyReset();
1836 mEventDeliveryPaused
= false;
1837 mPendingEvents
.Clear();
1839 AssertReadyStateIsNothing();
1843 HTMLMediaElement::NoSupportedMediaSourceError(const nsACString
& aErrorDetails
)
1848 mErrorSink
->SetError(MEDIA_ERR_SRC_NOT_SUPPORTED
, aErrorDetails
);
1849 ChangeDelayLoadStatus(false);
1850 UpdateAudioChannelPlayingState();
1851 RejectPromises(TakePendingPlayPromises(),
1852 NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR
);
1855 typedef void (HTMLMediaElement::*SyncSectionFn
)();
1857 // Runs a "synchronous section", a function that must run once the event loop
1858 // has reached a "stable state". See:
1859 // http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
1860 class nsSyncSection
: public nsMediaEvent
1863 nsCOMPtr
<nsIRunnable
> mRunnable
;
1866 nsSyncSection(HTMLMediaElement
* aElement
, nsIRunnable
* aRunnable
)
1867 : nsMediaEvent("dom::nsSyncSection", aElement
)
1868 , mRunnable(aRunnable
)
1872 NS_IMETHOD
Run() override
1874 // Silently cancel if our load has been cancelled.
1883 HTMLMediaElement::RunInStableState(nsIRunnable
* aRunnable
)
1885 if (mShuttingDown
) {
1889 nsCOMPtr
<nsIRunnable
> event
= new nsSyncSection(this, aRunnable
);
1890 nsContentUtils::RunInStableState(event
.forget());
1894 HTMLMediaElement::QueueLoadFromSourceTask()
1896 if (!mIsLoadingFromSourceChildren
|| mShuttingDown
) {
1901 // Reset readyState to HAVE_NOTHING since we're going to load a new decoder.
1903 ChangeReadyState(HAVE_NOTHING
);
1906 AssertReadyStateIsNothing();
1908 ChangeDelayLoadStatus(true);
1909 ChangeNetworkState(NETWORK_LOADING
);
1910 RefPtr
<Runnable
> r
=
1911 NewRunnableMethod("HTMLMediaElement::LoadFromSourceChildren",
1913 &HTMLMediaElement::LoadFromSourceChildren
);
1914 RunInStableState(r
);
1918 HTMLMediaElement::QueueSelectResourceTask()
1920 // Don't allow multiple async select resource calls to be queued.
1921 if (mHaveQueuedSelectResource
)
1923 mHaveQueuedSelectResource
= true;
1924 ChangeNetworkState(NETWORK_NO_SOURCE
);
1925 RefPtr
<Runnable
> r
=
1926 NewRunnableMethod("HTMLMediaElement::SelectResourceWrapper",
1928 &HTMLMediaElement::SelectResourceWrapper
);
1929 RunInStableState(r
);
1933 HasSourceChildren(nsIContent
* aElement
)
1935 for (nsIContent
* child
= aElement
->GetFirstChild(); child
;
1936 child
= child
->GetNextSibling()) {
1937 if (child
->IsHTMLElement(nsGkAtoms::source
)) {
1945 DocumentOrigin(nsIDocument
* aDoc
)
1948 return NS_LITERAL_CSTRING("null");
1950 nsCOMPtr
<nsIPrincipal
> principal
= aDoc
->NodePrincipal();
1952 return NS_LITERAL_CSTRING("null");
1955 if (NS_FAILED(principal
->GetOrigin(origin
))) {
1956 return NS_LITERAL_CSTRING("null");
1962 HTMLMediaElement::Load()
1964 LOG(LogLevel::Debug
,
1965 ("%p Load() hasSrcAttrStream=%d hasSrcAttr=%d hasSourceChildren=%d "
1966 "handlingInput=%d hasAutoplayAttr=%d IsAllowedToPlay=%d "
1967 "ownerDoc=%p (%s) ownerDocUserActivated=%d "
1968 "muted=%d volume=%f",
1971 HasAttr(kNameSpaceID_None
, nsGkAtoms::src
),
1972 HasSourceChildren(this),
1973 EventStateManager::IsHandlingUserInput(),
1974 HasAttr(kNameSpaceID_None
, nsGkAtoms::autoplay
),
1975 AutoplayPolicy::IsAllowedToPlay(*this),
1977 DocumentOrigin(OwnerDoc()).get(),
1978 OwnerDoc() ? OwnerDoc()->HasBeenUserGestureActivated() : 0,
1982 if (mIsRunningLoadMethod
) {
1986 mIsDoingExplicitLoad
= true;
1991 HTMLMediaElement::DoLoad()
1993 // Check if media is allowed for the docshell.
1994 nsCOMPtr
<nsIDocShell
> docShell
= OwnerDoc()->GetDocShell();
1995 if (docShell
&& !docShell
->GetAllowMedia()) {
1996 LOG(LogLevel::Debug
, ("%p Media not allowed", this));
2000 if (mIsRunningLoadMethod
) {
2004 if (EventStateManager::IsHandlingUserInput()) {
2005 // Detect if user has interacted with element so that play will not be
2006 // blocked when initiated by a script. This enables sites to capture user
2007 // intent to play by calling load() in the click handler of a "catalog
2008 // view" of a gallery of videos.
2010 // Mark the channel as urgent-start when autopaly so that it will play the
2011 // media from src after loading enough resource.
2012 if (HasAttr(kNameSpaceID_None
, nsGkAtoms::autoplay
)) {
2013 mUseUrgentStartForChannel
= true;
2017 SetPlayedOrSeeked(false);
2018 mIsRunningLoadMethod
= true;
2019 AbortExistingLoads();
2020 SetPlaybackRate(mDefaultPlaybackRate
, IgnoreErrors());
2021 QueueSelectResourceTask();
2023 mIsRunningLoadMethod
= false;
2027 HTMLMediaElement::ResetState()
2029 // There might be a pending MediaDecoder::PlaybackPositionChanged() which
2030 // will overwrite |mMediaInfo.mVideo.mDisplay| in UpdateMediaSize() to give
2031 // staled videoWidth and videoHeight. We have to call ForgetElement() here
2032 // such that the staled callbacks won't reach us.
2033 if (mVideoFrameContainer
) {
2034 mVideoFrameContainer
->ForgetElement();
2035 mVideoFrameContainer
= nullptr;
2040 HTMLMediaElement::SelectResourceWrapper()
2043 mIsRunningSelectResource
= false;
2044 mHaveQueuedSelectResource
= false;
2045 mIsDoingExplicitLoad
= false;
2049 HTMLMediaElement::SelectResource()
2051 if (!mSrcAttrStream
&& !HasAttr(kNameSpaceID_None
, nsGkAtoms::src
) &&
2052 !HasSourceChildren(this)) {
2053 // The media element has neither a src attribute nor any source
2054 // element children, abort the load.
2055 ChangeNetworkState(NETWORK_EMPTY
);
2056 ChangeDelayLoadStatus(false);
2060 ChangeDelayLoadStatus(true);
2062 ChangeNetworkState(NETWORK_LOADING
);
2063 DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
2065 // Delay setting mIsRunningSeletResource until after UpdatePreloadAction
2066 // so that we don't lose our state change by bailing out of the preload
2068 UpdatePreloadAction();
2069 mIsRunningSelectResource
= true;
2071 // If we have a 'src' attribute, use that exclusively.
2073 if (mSrcAttrStream
) {
2074 SetupSrcMediaStreamPlayback(mSrcAttrStream
);
2075 } else if (GetAttr(kNameSpaceID_None
, nsGkAtoms::src
, src
)) {
2076 nsCOMPtr
<nsIURI
> uri
;
2077 MediaResult rv
= NewURIFromString(src
, getter_AddRefs(uri
));
2078 if (NS_SUCCEEDED(rv
)) {
2081 ("%p Trying load from src=%s", this, NS_ConvertUTF16toUTF8(src
).get()));
2083 !mIsLoadingFromSourceChildren
,
2084 "Should think we're not loading from source children by default");
2086 RemoveMediaElementFromURITable();
2088 mLoadingSrcTriggeringPrincipal
= mSrcAttrTriggeringPrincipal
;
2089 DDLOG(DDLogCategory::Property
,
2091 nsCString(NS_ConvertUTF16toUTF8(src
)));
2092 mMediaSource
= mSrcMediaSource
;
2093 DDLINKCHILD("mediasource", mMediaSource
.get());
2094 UpdatePreloadAction();
2095 if (mPreloadAction
== HTMLMediaElement::PRELOAD_NONE
&& !mMediaSource
) {
2096 // preload:none media, suspend the load here before we make any
2097 // network requests.
2102 rv
= LoadResource();
2103 if (NS_SUCCEEDED(rv
)) {
2107 const char16_t
* params
[] = { src
.get() };
2108 ReportLoadError("MediaLoadInvalidURI", params
, ArrayLength(params
));
2109 rv
= MediaResult(rv
.Code(), "MediaLoadInvalidURI");
2111 // The media element has neither a src attribute nor a source element child:
2112 // set the networkState to NETWORK_EMPTY, and abort these steps; the
2113 // synchronous section ends.
2114 mMainThreadEventTarget
->Dispatch(NewRunnableMethod
<nsCString
>(
2115 "HTMLMediaElement::NoSupportedMediaSourceError",
2117 &HTMLMediaElement::NoSupportedMediaSourceError
,
2120 // Otherwise, the source elements will be used.
2121 mIsLoadingFromSourceChildren
= true;
2122 LoadFromSourceChildren();
2127 HTMLMediaElement::NotifyLoadError(const nsACString
& aErrorDetails
)
2129 if (!mIsLoadingFromSourceChildren
) {
2130 LOG(LogLevel::Debug
, ("NotifyLoadError(), no supported media error"));
2131 NoSupportedMediaSourceError(aErrorDetails
);
2132 } else if (mSourceLoadCandidate
) {
2133 DispatchAsyncSourceError(mSourceLoadCandidate
);
2134 QueueLoadFromSourceTask();
2136 NS_WARNING("Should know the source we were loading from!");
2141 HTMLMediaElement::NotifyMediaTrackEnabled(MediaTrack
* aTrack
)
2151 LOG(LogLevel::Debug
,
2152 ("MediaElement %p %sTrack with id %s enabled",
2154 aTrack
->AsAudioTrack() ? "Audio" : "Video",
2155 NS_ConvertUTF16toUTF8(id
).get()));
2158 MOZ_ASSERT((aTrack
->AsAudioTrack() && aTrack
->AsAudioTrack()->Enabled()) ||
2159 (aTrack
->AsVideoTrack() && aTrack
->AsVideoTrack()->Selected()));
2161 if (aTrack
->AsAudioTrack()) {
2162 SetMutedInternal(mMuted
& ~MUTED_BY_AUDIO_TRACK
);
2163 } else if (aTrack
->AsVideoTrack()) {
2168 mDisableVideo
= false;
2170 MOZ_ASSERT(false, "Unknown track type");
2174 if (aTrack
->AsVideoTrack()) {
2175 MOZ_ASSERT(!mSelectedVideoStreamTrack
);
2176 MOZ_ASSERT(!mMediaStreamSizeListener
);
2178 mSelectedVideoStreamTrack
= aTrack
->AsVideoTrack()->GetVideoStreamTrack();
2179 VideoFrameContainer
* container
= GetVideoFrameContainer();
2180 if (mSrcStreamIsPlaying
&& container
) {
2181 mSelectedVideoStreamTrack
->AddVideoOutput(container
);
2183 HTMLVideoElement
* self
= static_cast<HTMLVideoElement
*>(this);
2184 if (self
->VideoWidth() <= 1 && self
->VideoHeight() <= 1) {
2185 // MediaInfo uses dummy values of 1 for width and height to
2186 // mark video as valid. We need a new stream size listener
2187 // if size is 0x0 or 1x1.
2188 mMediaStreamSizeListener
= new StreamSizeListener(this);
2189 mSelectedVideoStreamTrack
->AddDirectListener(mMediaStreamSizeListener
);
2193 if (mReadyState
== HAVE_NOTHING
) {
2194 // No MediaStreamTracks are captured until we have metadata.
2197 for (OutputMediaStream
& ms
: mOutputStreams
) {
2198 if (aTrack
->AsVideoTrack() && ms
.mCapturingAudioOnly
) {
2199 // If the output stream is for audio only we ignore video tracks.
2202 AddCaptureMediaTrackToOutputStream(aTrack
, ms
);
2208 HTMLMediaElement::NotifyMediaTrackDisabled(MediaTrack
* aTrack
)
2218 LOG(LogLevel::Debug
,
2219 ("MediaElement %p %sTrack with id %s disabled",
2221 aTrack
->AsAudioTrack() ? "Audio" : "Video",
2222 NS_ConvertUTF16toUTF8(id
).get()));
2225 MOZ_ASSERT((!aTrack
->AsAudioTrack() || !aTrack
->AsAudioTrack()->Enabled()) &&
2226 (!aTrack
->AsVideoTrack() || !aTrack
->AsVideoTrack()->Selected()));
2228 if (aTrack
->AsAudioTrack()) {
2229 // If we don't have any alive track , we don't need to mute MediaElement.
2230 if (AudioTracks()->Length() > 0) {
2231 bool shouldMute
= true;
2232 for (uint32_t i
= 0; i
< AudioTracks()->Length(); ++i
) {
2233 if ((*AudioTracks())[i
]->Enabled()) {
2240 SetMutedInternal(mMuted
| MUTED_BY_AUDIO_TRACK
);
2243 } else if (aTrack
->AsVideoTrack()) {
2245 MOZ_ASSERT(mSelectedVideoStreamTrack
);
2246 if (mSelectedVideoStreamTrack
&& mMediaStreamSizeListener
) {
2247 mSelectedVideoStreamTrack
->RemoveDirectListener(
2248 mMediaStreamSizeListener
);
2249 mMediaStreamSizeListener
->Forget();
2250 mMediaStreamSizeListener
= nullptr;
2252 VideoFrameContainer
* container
= GetVideoFrameContainer();
2253 if (mSrcStreamIsPlaying
&& container
) {
2254 mSelectedVideoStreamTrack
->RemoveVideoOutput(container
);
2256 mSelectedVideoStreamTrack
= nullptr;
2260 if (mReadyState
== HAVE_NOTHING
) {
2261 // No MediaStreamTracks are captured until we have metadata, and code
2262 // below doesn't do anything for captured decoders.
2266 for (OutputMediaStream
& ms
: mOutputStreams
) {
2267 if (ms
.mCapturingDecoder
) {
2268 MOZ_ASSERT(!ms
.mCapturingMediaStream
);
2271 MOZ_ASSERT(ms
.mCapturingMediaStream
);
2272 for (int32_t i
= ms
.mTrackPorts
.Length() - 1; i
>= 0; --i
) {
2273 if (ms
.mTrackPorts
[i
].first() == aTrack
->GetId()) {
2274 // The source of this track just ended. Force-notify that it ended.
2275 // If we bounce it to the MediaStreamGraph it might not be picked up,
2276 // for instance if the MediaInputPort was destroyed in the same
2277 // iteration as it was added.
2278 MediaStreamTrack
* outputTrack
= ms
.mStream
->FindOwnedDOMTrack(
2279 ms
.mTrackPorts
[i
].second()->GetDestination(),
2280 ms
.mTrackPorts
[i
].second()->GetDestinationTrackId());
2281 MOZ_ASSERT(outputTrack
);
2283 mMainThreadEventTarget
->Dispatch(
2284 NewRunnableMethod("MediaStreamTrack::OverrideEnded",
2286 &MediaStreamTrack::OverrideEnded
));
2289 ms
.mTrackPorts
[i
].second()->Destroy();
2290 ms
.mTrackPorts
.RemoveElementAt(i
);
2295 for (auto pair
: ms
.mTrackPorts
) {
2296 MOZ_ASSERT(pair
.first() != aTrack
->GetId(),
2297 "The same MediaTrack was forwarded to the output stream more "
2298 "than once. This shouldn't happen.");
2305 HTMLMediaElement::NotifyMediaStreamTracksAvailable(DOMMediaStream
* aStream
)
2307 if (!mSrcStream
|| mSrcStream
!= aStream
) {
2311 LOG(LogLevel::Debug
, ("MediaElement %p MediaStream tracks available", this));
2313 mSrcStreamTracksAvailable
= true;
2315 bool videoHasChanged
= IsVideo() && HasVideo() != !VideoTracks()->IsEmpty();
2317 if (videoHasChanged
) {
2318 // We are a video element and HasVideo() changed so update the screen
2320 NotifyOwnerDocumentActivityChanged();
2323 UpdateReadyStateInternal();
2327 HTMLMediaElement::DealWithFailedElement(nsIContent
* aSourceElement
)
2329 if (mShuttingDown
) {
2333 DispatchAsyncSourceError(aSourceElement
);
2334 mMainThreadEventTarget
->Dispatch(
2335 NewRunnableMethod("HTMLMediaElement::QueueLoadFromSourceTask",
2337 &HTMLMediaElement::QueueLoadFromSourceTask
));
2341 HTMLMediaElement::NotifyOutputTrackStopped(DOMMediaStream
* aOwningStream
,
2342 TrackID aDestinationTrackID
)
2344 for (OutputMediaStream
& ms
: mOutputStreams
) {
2345 if (!ms
.mCapturingMediaStream
) {
2349 if (ms
.mStream
!= aOwningStream
) {
2353 for (int32_t i
= ms
.mTrackPorts
.Length() - 1; i
>= 0; --i
) {
2354 MediaInputPort
* port
= ms
.mTrackPorts
[i
].second();
2355 if (port
->GetDestinationTrackId() != aDestinationTrackID
) {
2360 ms
.mTrackPorts
.RemoveElementAt(i
);
2365 // An output track ended but its port is already gone.
2366 // It was probably cleared by the removal of the source MediaTrack.
2370 HTMLMediaElement::LoadFromSourceChildren()
2372 NS_ASSERTION(mDelayingLoadEvent
,
2373 "Should delay load event (if in document) during load");
2374 NS_ASSERTION(mIsLoadingFromSourceChildren
,
2375 "Must remember we're loading from source children");
2377 AddMutationObserverUnlessExists(this);
2380 Element
* child
= GetNextSource();
2382 // Exhausted candidates, wait for more candidates to be appended to
2383 // the media element.
2384 mLoadWaitStatus
= WAITING_FOR_SOURCE
;
2385 ChangeNetworkState(NETWORK_NO_SOURCE
);
2386 ChangeDelayLoadStatus(false);
2387 ReportLoadError("MediaLoadExhaustedCandidates");
2391 // Must have src attribute.
2393 if (!child
->GetAttr(kNameSpaceID_None
, nsGkAtoms::src
, src
)) {
2394 ReportLoadError("MediaLoadSourceMissingSrc");
2395 DealWithFailedElement(child
);
2399 // If we have a type attribute, it must be a supported type.
2401 if (child
->GetAttr(kNameSpaceID_None
, nsGkAtoms::type
, type
)) {
2402 DecoderDoctorDiagnostics diagnostics
;
2403 CanPlayStatus canPlay
= GetCanPlay(type
, &diagnostics
);
2404 diagnostics
.StoreFormatDiagnostics(
2405 OwnerDoc(), type
, canPlay
!= CANPLAY_NO
, __func__
);
2406 if (canPlay
== CANPLAY_NO
) {
2407 const char16_t
* params
[] = { type
.get(), src
.get() };
2409 "MediaLoadUnsupportedTypeAttribute", params
, ArrayLength(params
));
2410 DealWithFailedElement(child
);
2414 HTMLSourceElement
* childSrc
= HTMLSourceElement::FromNode(child
);
2415 LOG(LogLevel::Debug
,
2416 ("%p Trying load from <source>=%s type=%s",
2418 NS_ConvertUTF16toUTF8(src
).get(),
2419 NS_ConvertUTF16toUTF8(type
).get()));
2421 nsCOMPtr
<nsIURI
> uri
;
2422 NewURIFromString(src
, getter_AddRefs(uri
));
2424 const char16_t
* params
[] = { src
.get() };
2425 ReportLoadError("MediaLoadInvalidURI", params
, ArrayLength(params
));
2426 DealWithFailedElement(child
);
2430 RemoveMediaElementFromURITable();
2432 mLoadingSrcTriggeringPrincipal
= childSrc
->GetSrcTriggeringPrincipal();
2433 DDLOG(DDLogCategory::Property
,
2435 nsCString(NS_ConvertUTF16toUTF8(src
)));
2436 mMediaSource
= childSrc
->GetSrcMediaSource();
2437 DDLINKCHILD("mediasource", mMediaSource
.get());
2438 NS_ASSERTION(mNetworkState
== NETWORK_LOADING
,
2439 "Network state should be loading");
2441 if (mPreloadAction
== HTMLMediaElement::PRELOAD_NONE
&& !mMediaSource
) {
2442 // preload:none media, suspend the load here before we make any
2443 // network requests.
2448 if (NS_SUCCEEDED(LoadResource())) {
2452 // If we fail to load, loop back and try loading the next resource.
2453 DispatchAsyncSourceError(child
);
2455 MOZ_ASSERT_UNREACHABLE("Execution should not reach here!");
2459 HTMLMediaElement::SuspendLoad()
2461 mSuspendedForPreloadNone
= true;
2462 ChangeNetworkState(NETWORK_IDLE
);
2463 ChangeDelayLoadStatus(false);
2467 HTMLMediaElement::ResumeLoad(PreloadAction aAction
)
2469 NS_ASSERTION(mSuspendedForPreloadNone
,
2470 "Must be halted for preload:none to resume from preload:none "
2472 mSuspendedForPreloadNone
= false;
2473 mPreloadAction
= aAction
;
2474 ChangeDelayLoadStatus(true);
2475 ChangeNetworkState(NETWORK_LOADING
);
2476 if (!mIsLoadingFromSourceChildren
) {
2477 // We were loading from the element's src attribute.
2478 MediaResult rv
= LoadResource();
2479 if (NS_FAILED(rv
)) {
2480 NoSupportedMediaSourceError(rv
.Description());
2483 // We were loading from a child <source> element. Try to resume the
2484 // load of that child, and if that fails, try the next child.
2485 if (NS_FAILED(LoadResource())) {
2486 LoadFromSourceChildren();
2492 HTMLMediaElement::AllowedToPlay() const
2494 return AutoplayPolicy::IsAllowedToPlay(*this);
2498 HTMLMediaElement::UpdatePreloadAction()
2500 PreloadAction nextAction
= PRELOAD_UNDEFINED
;
2501 // If autoplay is set, or we're playing, we should always preload data,
2502 // as we'll need it to play.
2503 if ((AutoplayPolicy::IsAllowedToPlay(*this) &&
2504 HasAttr(kNameSpaceID_None
, nsGkAtoms::autoplay
)) ||
2506 nextAction
= HTMLMediaElement::PRELOAD_ENOUGH
;
2508 // Find the appropriate preload action by looking at the attribute.
2509 const nsAttrValue
* val
=
2510 mAttrs
.GetAttr(nsGkAtoms::preload
, kNameSpaceID_None
);
2511 // MSE doesn't work if preload is none, so it ignores the pref when src is
2513 uint32_t preloadDefault
=
2515 ? HTMLMediaElement::PRELOAD_ATTR_METADATA
2516 : Preferences::GetInt("media.preload.default",
2517 HTMLMediaElement::PRELOAD_ATTR_METADATA
);
2518 uint32_t preloadAuto
= Preferences::GetInt(
2519 "media.preload.auto", HTMLMediaElement::PRELOAD_ENOUGH
);
2521 // Attribute is not set. Use the preload action specified by the
2522 // media.preload.default pref, or just preload metadata if not present.
2523 nextAction
= static_cast<PreloadAction
>(preloadDefault
);
2524 } else if (val
->Type() == nsAttrValue::eEnum
) {
2525 PreloadAttrValue attr
=
2526 static_cast<PreloadAttrValue
>(val
->GetEnumValue());
2527 if (attr
== HTMLMediaElement::PRELOAD_ATTR_EMPTY
||
2528 attr
== HTMLMediaElement::PRELOAD_ATTR_AUTO
) {
2529 nextAction
= static_cast<PreloadAction
>(preloadAuto
);
2530 } else if (attr
== HTMLMediaElement::PRELOAD_ATTR_METADATA
) {
2531 nextAction
= HTMLMediaElement::PRELOAD_METADATA
;
2532 } else if (attr
== HTMLMediaElement::PRELOAD_ATTR_NONE
) {
2533 nextAction
= HTMLMediaElement::PRELOAD_NONE
;
2536 // Use the suggested "missing value default" of "metadata", or the value
2537 // specified by the media.preload.default, if present.
2538 nextAction
= static_cast<PreloadAction
>(preloadDefault
);
2542 if (nextAction
== HTMLMediaElement::PRELOAD_NONE
&& mIsDoingExplicitLoad
) {
2543 nextAction
= HTMLMediaElement::PRELOAD_METADATA
;
2546 mPreloadAction
= nextAction
;
2548 if (nextAction
== HTMLMediaElement::PRELOAD_ENOUGH
) {
2549 if (mSuspendedForPreloadNone
) {
2550 // Our load was previouly suspended due to the media having preload
2551 // value "none". The preload value has changed to preload:auto, so
2553 ResumeLoad(PRELOAD_ENOUGH
);
2555 // Preload as much of the video as we can, i.e. don't suspend after
2557 StopSuspendingAfterFirstFrame();
2560 } else if (nextAction
== HTMLMediaElement::PRELOAD_METADATA
) {
2561 // Ensure that the video can be suspended after first frame.
2562 mAllowSuspendAfterFirstFrame
= true;
2563 if (mSuspendedForPreloadNone
) {
2564 // Our load was previouly suspended due to the media having preload
2565 // value "none". The preload value has changed to preload:metadata, so
2566 // resume the load. We'll pause the load again after we've read the
2568 ResumeLoad(PRELOAD_METADATA
);
2574 HTMLMediaElement::LoadResource()
2576 AbstractThread::AutoEnter
context(AbstractMainThread());
2578 NS_ASSERTION(mDelayingLoadEvent
,
2579 "Should delay load event (if in document) during load");
2581 if (mChannelLoader
) {
2582 mChannelLoader
->Cancel();
2583 mChannelLoader
= nullptr;
2586 // Set the media element's CORS mode only when loading a resource
2587 mCORSMode
= AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin
));
2589 HTMLMediaElement
* other
= LookupMediaElementURITable(mLoadingSrc
);
2590 if (other
&& other
->mDecoder
) {
2592 // TODO: remove the cast by storing ChannelMediaDecoder in the URI table.
2593 nsresult rv
= InitializeDecoderAsClone(
2594 static_cast<ChannelMediaDecoder
*>(other
->mDecoder
.get()));
2595 if (NS_SUCCEEDED(rv
))
2600 MediaDecoderInit
decoderInit(
2602 mMuted
? 0.0 : mVolume
,
2605 mPreloadAction
== HTMLMediaElement::PRELOAD_METADATA
,
2607 HasAttr(kNameSpaceID_None
, nsGkAtoms::loop
),
2608 MediaContainerType(MEDIAMIMETYPE("application/x.mediasource")));
2610 RefPtr
<MediaSourceDecoder
> decoder
= new MediaSourceDecoder(decoderInit
);
2611 if (!mMediaSource
->Attach(decoder
)) {
2612 // TODO: Handle failure: run "If the media data cannot be fetched at
2613 // all, due to network errors, causing the user agent to give up
2614 // trying to fetch the resource" section of resource fetch algorithm.
2615 decoder
->Shutdown();
2616 return MediaResult(NS_ERROR_FAILURE
, "Failed to attach MediaSource");
2618 ChangeDelayLoadStatus(false);
2619 nsresult rv
= decoder
->Load(mMediaSource
->GetPrincipal());
2620 if (NS_FAILED(rv
)) {
2621 decoder
->Shutdown();
2622 LOG(LogLevel::Debug
,
2623 ("%p Failed to load for decoder %p", this, decoder
.get()));
2624 return MediaResult(rv
, "Fail to load decoder");
2626 rv
= FinishDecoderSetup(decoder
);
2627 return MediaResult(rv
, "Failed to set up decoder");
2630 AssertReadyStateIsNothing();
2632 RefPtr
<ChannelLoader
> loader
= new ChannelLoader
;
2633 nsresult rv
= loader
->Load(this);
2634 if (NS_SUCCEEDED(rv
)) {
2635 mChannelLoader
= loader
.forget();
2637 return MediaResult(rv
, "Failed to load channel");
2641 HTMLMediaElement::LoadWithChannel(nsIChannel
* aChannel
,
2642 nsIStreamListener
** aListener
)
2644 NS_ENSURE_ARG_POINTER(aChannel
);
2645 NS_ENSURE_ARG_POINTER(aListener
);
2647 *aListener
= nullptr;
2649 // Make sure we don't reenter during synchronous abort events.
2650 if (mIsRunningLoadMethod
)
2652 mIsRunningLoadMethod
= true;
2653 AbortExistingLoads();
2654 mIsRunningLoadMethod
= false;
2656 mLoadingSrcTriggeringPrincipal
= nullptr;
2657 nsresult rv
= aChannel
->GetOriginalURI(getter_AddRefs(mLoadingSrc
));
2658 NS_ENSURE_SUCCESS(rv
, rv
);
2660 ChangeDelayLoadStatus(true);
2661 rv
= InitializeDecoderForChannel(aChannel
, aListener
);
2662 if (NS_FAILED(rv
)) {
2663 ChangeDelayLoadStatus(false);
2667 SetPlaybackRate(mDefaultPlaybackRate
, IgnoreErrors());
2668 DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
2674 HTMLMediaElement::Seeking() const
2676 return mDecoder
&& mDecoder
->IsSeeking();
2680 HTMLMediaElement::CurrentTime() const
2682 if (MediaStream
* stream
= GetSrcMediaStream()) {
2683 if (mSrcStreamPausedCurrentTime
>= 0) {
2684 return mSrcStreamPausedCurrentTime
;
2686 return stream
->StreamTimeToSeconds(stream
->GetCurrentTime());
2689 if (mDefaultPlaybackStartPosition
== 0.0 && mDecoder
) {
2690 return mDecoder
->GetCurrentTime();
2693 return mDefaultPlaybackStartPosition
;
2697 HTMLMediaElement::FastSeek(double aTime
, ErrorResult
& aRv
)
2699 LOG(LogLevel::Debug
, ("%p FastSeek(%f) called by JS", this, aTime
));
2700 LOG(LogLevel::Debug
, ("Reporting telemetry VIDEO_FASTSEEK_USED"));
2701 Telemetry::Accumulate(Telemetry::VIDEO_FASTSEEK_USED
, 1);
2702 RefPtr
<Promise
> tobeDropped
= Seek(aTime
, SeekTarget::PrevSyncPoint
,
2706 already_AddRefed
<Promise
>
2707 HTMLMediaElement::SeekToNextFrame(ErrorResult
& aRv
)
2709 /* This will cause JIT code to be kept around longer, to help performance
2710 * when using SeekToNextFrame to iterate through every frame of a video.
2712 nsPIDOMWindowInner
* win
= OwnerDoc()->GetInnerWindow();
2715 if (JSObject
* obj
= win
->AsGlobal()->GetGlobalJSObject()) {
2716 js::NotifyAnimationActivity(obj
);
2720 return Seek(CurrentTime(), SeekTarget::NextFrame
, aRv
);
2724 HTMLMediaElement::SetCurrentTime(double aCurrentTime
, ErrorResult
& aRv
)
2726 LOG(LogLevel::Debug
,
2727 ("%p SetCurrentTime(%f) called by JS", this, aCurrentTime
));
2728 RefPtr
<Promise
> tobeDropped
= Seek(aCurrentTime
, SeekTarget::Accurate
,
2733 * Check if aValue is inside a range of aRanges, and if so returns true
2734 * and puts the range index in aIntervalIndex. If aValue is not
2735 * inside a range, returns false, and aIntervalIndex
2736 * is set to the index of the range which starts immediately after aValue
2737 * (and can be aRanges.Length() if aValue is after the last range).
2740 IsInRanges(TimeRanges
& aRanges
, double aValue
, uint32_t& aIntervalIndex
)
2742 uint32_t length
= aRanges
.Length();
2744 for (uint32_t i
= 0; i
< length
; i
++) {
2745 double start
= aRanges
.Start(i
);
2746 if (start
> aValue
) {
2750 double end
= aRanges
.End(i
);
2751 if (aValue
<= end
) {
2756 aIntervalIndex
= length
;
2760 already_AddRefed
<Promise
>
2761 HTMLMediaElement::Seek(double aTime
,
2762 SeekTarget::Type aSeekType
,
2765 // Note: Seek is called both by synchronous code that expects errors thrown in
2766 // aRv, as well as asynchronous code that expects a promise. Make sure all
2767 // synchronous errors are returned using aRv, not promise rejections.
2769 // aTime should be non-NaN.
2770 MOZ_ASSERT(!mozilla::IsNaN(aTime
));
2772 RefPtr
<Promise
> promise
= CreateDOMPromise(aRv
);
2773 if (NS_WARN_IF(aRv
.Failed())) {
2777 // Detect if user has interacted with element by seeking so that
2778 // play will not be blocked when initiated by a script.
2779 if (EventStateManager::IsHandlingUserInput()) {
2783 StopSuspendingAfterFirstFrame();
2786 // do nothing since media streams have an empty Seekable range.
2787 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2791 if (mPlayed
&& mCurrentPlayRangeStart
!= -1.0) {
2792 double rangeEndTime
= CurrentTime();
2793 LOG(LogLevel::Debug
,
2794 ("%p Adding \'played\' a range : [%f, %f]",
2796 mCurrentPlayRangeStart
,
2798 // Multiple seek without playing, or seek while playing.
2799 if (mCurrentPlayRangeStart
!= rangeEndTime
) {
2800 mPlayed
->Add(mCurrentPlayRangeStart
, rangeEndTime
);
2802 // Reset the current played range start time. We'll re-set it once
2803 // the seek completes.
2804 mCurrentPlayRangeStart
= -1.0;
2807 if (mReadyState
== HAVE_NOTHING
) {
2808 mDefaultPlaybackStartPosition
= aTime
;
2809 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2814 // mDecoder must always be set in order to reach this point.
2815 NS_ASSERTION(mDecoder
, "SetCurrentTime failed: no decoder");
2816 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2820 // Clamp the seek target to inside the seekable ranges.
2821 media::TimeIntervals seekableIntervals
= mDecoder
->GetSeekable();
2822 if (seekableIntervals
.IsInvalid()) {
2823 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2826 RefPtr
<TimeRanges
> seekable
=
2827 new TimeRanges(ToSupports(OwnerDoc()), seekableIntervals
);
2828 uint32_t length
= seekable
->Length();
2830 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
2834 // If the position we want to seek to is not in a seekable range, we seek
2835 // to the closest position in the seekable ranges instead. If two positions
2836 // are equally close, we seek to the closest position from the currentTime.
2837 // See seeking spec, point 7 :
2838 // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#seeking
2840 bool isInRange
= IsInRanges(*seekable
, aTime
, range
);
2843 // aTime is before the first range in |seekable|, the closest point we can
2844 // seek to is the start of the first range.
2845 aTime
= seekable
->Start(0);
2846 } else if (range
== length
) {
2847 // Seek target is after the end last range in seekable data.
2848 // Clamp the seek target to the end of the last seekable range.
2849 aTime
= seekable
->End(length
- 1);
2851 double leftBound
= seekable
->End(range
- 1);
2852 double rightBound
= seekable
->Start(range
);
2853 double distanceLeft
= Abs(leftBound
- aTime
);
2854 double distanceRight
= Abs(rightBound
- aTime
);
2855 if (distanceLeft
== distanceRight
) {
2856 double currentTime
= CurrentTime();
2857 distanceLeft
= Abs(leftBound
- currentTime
);
2858 distanceRight
= Abs(rightBound
- currentTime
);
2860 aTime
= (distanceLeft
< distanceRight
) ? leftBound
: rightBound
;
2864 // TODO: The spec requires us to update the current time to reflect the
2865 // actual seek target before beginning the synchronous section, but
2866 // that requires changing all MediaDecoderReaders to support telling
2867 // us the fastSeek target, and it's currently not possible to get
2868 // this information as we don't yet control the demuxer for all
2869 // MediaDecoderReaders.
2871 mPlayingBeforeSeek
= IsPotentiallyPlaying();
2873 // If the audio track is silent before seeking, we should end current silence
2874 // range and start a new range after seeking. Since seek() could be called
2875 // multiple times before seekEnd() executed, we should only calculate silence
2876 // range when first time seek() called. Calculating on other seek() calls
2877 // would cause a wrong result. In order to get correct time, this checking
2878 // should be called before decoder->seek().
2879 if (IsAudioTrackCurrentlySilent() &&
2880 !mHasAccumulatedSilenceRangeBeforeSeekEnd
) {
2881 AccumulateAudioTrackSilence();
2882 mHasAccumulatedSilenceRangeBeforeSeekEnd
= true;
2885 // The media backend is responsible for dispatching the timeupdate
2886 // event if it changes the playback position as a result of the seek.
2887 LOG(LogLevel::Debug
, ("%p SetCurrentTime(%f) starting seek", this, aTime
));
2888 mDecoder
->Seek(aTime
, aSeekType
);
2890 // We changed whether we're seeking so we need to AddRemoveSelfReference.
2891 AddRemoveSelfReference();
2893 // Keep the DOM promise.
2894 mSeekDOMPromise
= promise
;
2896 return promise
.forget();
2900 HTMLMediaElement::Duration() const
2903 return std::numeric_limits
<double>::infinity();
2907 return mDecoder
->GetDuration();
2910 return std::numeric_limits
<double>::quiet_NaN();
2913 already_AddRefed
<TimeRanges
>
2914 HTMLMediaElement::Seekable() const
2916 media::TimeIntervals seekable
=
2917 mDecoder
? mDecoder
->GetSeekable() : media::TimeIntervals();
2918 RefPtr
<TimeRanges
> ranges
= new TimeRanges(ToSupports(OwnerDoc()), seekable
);
2919 return ranges
.forget();
2922 already_AddRefed
<TimeRanges
>
2923 HTMLMediaElement::Played()
2925 RefPtr
<TimeRanges
> ranges
= new TimeRanges(ToSupports(OwnerDoc()));
2927 uint32_t timeRangeCount
= 0;
2929 timeRangeCount
= mPlayed
->Length();
2931 for (uint32_t i
= 0; i
< timeRangeCount
; i
++) {
2932 double begin
= mPlayed
->Start(i
);
2933 double end
= mPlayed
->End(i
);
2934 ranges
->Add(begin
, end
);
2937 if (mCurrentPlayRangeStart
!= -1.0) {
2938 double now
= CurrentTime();
2939 if (mCurrentPlayRangeStart
!= now
) {
2940 ranges
->Add(mCurrentPlayRangeStart
, now
);
2944 ranges
->Normalize();
2945 return ranges
.forget();
2949 HTMLMediaElement::Pause(ErrorResult
& aRv
)
2951 LOG(LogLevel::Debug
, ("%p Pause() called by JS", this));
2952 if (mNetworkState
== NETWORK_EMPTY
) {
2953 LOG(LogLevel::Debug
, ("Loading due to Pause()"));
2955 } else if (mDecoder
) {
2959 bool oldPaused
= mPaused
;
2961 mAutoplaying
= false;
2962 // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
2963 AddRemoveSelfReference();
2964 UpdateSrcMediaStreamPlaying();
2965 if (mAudioChannelWrapper
) {
2966 mAudioChannelWrapper
->NotifyPlayStateChanged();
2970 FireTimeUpdate(false);
2971 DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
2972 AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_ABORT_ERR
);
2977 HTMLMediaElement::SetVolume(double aVolume
, ErrorResult
& aRv
)
2979 LOG(LogLevel::Debug
, ("%p SetVolume(%f) called by JS", this, aVolume
));
2981 if (aVolume
< 0.0 || aVolume
> 1.0) {
2982 aRv
.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR
);
2986 if (aVolume
== mVolume
)
2991 // Here we want just to update the volume.
2992 SetVolumeInternal();
2994 DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
2996 // We allow inaudible autoplay. But changing our volume may make this
2997 // media audible. So pause if we are no longer supposed to be autoplaying.
2998 PauseIfShouldNotBePlaying();
3002 HTMLMediaElement::MozGetMetadata(JSContext
* cx
,
3003 JS::MutableHandle
<JSObject
*> aRetval
,
3006 if (mReadyState
< HAVE_METADATA
) {
3007 aRv
.Throw(NS_ERROR_DOM_INVALID_STATE_ERR
);
3011 JS::Rooted
<JSObject
*> tags(cx
, JS_NewPlainObject(cx
));
3013 aRv
.Throw(NS_ERROR_FAILURE
);
3017 for (auto iter
= mTags
->ConstIter(); !iter
.Done(); iter
.Next()) {
3019 CopyUTF8toUTF16(iter
.UserData(), wideValue
);
3020 JS::Rooted
<JSString
*> string(cx
,
3021 JS_NewUCStringCopyZ(cx
, wideValue
.Data()));
3022 if (!string
|| !JS_DefineProperty(
3023 cx
, tags
, iter
.Key().Data(), string
, JSPROP_ENUMERATE
)) {
3024 NS_WARNING("couldn't create metadata object!");
3025 aRv
.Throw(NS_ERROR_FAILURE
);
3035 HTMLMediaElement::SetMutedInternal(uint32_t aMuted
)
3037 uint32_t oldMuted
= mMuted
;
3040 if (!!aMuted
== !!oldMuted
) {
3044 SetVolumeInternal();
3048 HTMLMediaElement::PauseIfShouldNotBePlaying()
3053 if (!AutoplayPolicy::IsAllowedToPlay(*this)) {
3054 AUTOPLAY_LOG("pause because not allowed to play, element=%p", this);
3057 OwnerDoc()->SetDocTreeHadPlayRevoked();
3062 HTMLMediaElement::SetVolumeInternal()
3064 float effectiveVolume
= ComputedVolume();
3067 mDecoder
->SetVolume(effectiveVolume
);
3068 } else if (MediaStream
* stream
= GetSrcMediaStream()) {
3069 if (mSrcStreamIsPlaying
) {
3070 stream
->SetAudioOutputVolume(this, effectiveVolume
);
3074 NotifyAudioPlaybackChanged(
3075 AudioChannelService::AudibleChangedReasons::eVolumeChanged
);
3079 HTMLMediaElement::SetMuted(bool aMuted
)
3081 LOG(LogLevel::Debug
, ("%p SetMuted(%d) called by JS", this, aMuted
));
3082 if (aMuted
== Muted()) {
3087 SetMutedInternal(mMuted
| MUTED_BY_CONTENT
);
3089 SetMutedInternal(mMuted
& ~MUTED_BY_CONTENT
);
3092 DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
3094 // We allow inaudible autoplay. But changing our mute status may make this
3095 // media audible. So pause if we are no longer supposed to be autoplaying.
3096 PauseIfShouldNotBePlaying();
3099 class HTMLMediaElement::StreamCaptureTrackSource
3100 : public MediaStreamTrackSource
3101 , public MediaStreamTrackSource::Sink
3104 NS_DECL_ISUPPORTS_INHERITED
3105 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StreamCaptureTrackSource
,
3106 MediaStreamTrackSource
)
3108 StreamCaptureTrackSource(HTMLMediaElement
* aElement
,
3109 MediaStreamTrackSource
* aCapturedTrackSource
,
3110 DOMMediaStream
* aOwningStream
,
3111 TrackID aDestinationTrackID
)
3112 : MediaStreamTrackSource(aCapturedTrackSource
->GetPrincipal(), nsString())
3113 , mElement(aElement
)
3114 , mCapturedTrackSource(aCapturedTrackSource
)
3115 , mOwningStream(aOwningStream
)
3116 , mDestinationTrackID(aDestinationTrackID
)
3118 MOZ_ASSERT(mElement
);
3119 MOZ_ASSERT(mCapturedTrackSource
);
3120 MOZ_ASSERT(mOwningStream
);
3121 MOZ_ASSERT(IsTrackIDExplicit(mDestinationTrackID
));
3123 mCapturedTrackSource
->RegisterSink(this);
3126 void Destroy() override
3128 if (mCapturedTrackSource
) {
3129 mCapturedTrackSource
->UnregisterSink(this);
3130 mCapturedTrackSource
= nullptr;
3134 MediaSourceEnum
GetMediaSource() const override
3136 return MediaSourceEnum::Other
;
3139 CORSMode
GetCORSMode() const override
3141 if (!mCapturedTrackSource
) {
3142 // This could happen during shutdown.
3146 return mCapturedTrackSource
->GetCORSMode();
3149 void Stop() override
3151 if (mElement
&& mElement
->mSrcStream
) {
3152 // Only notify if we're still playing the source stream. GC might have
3153 // cleared it before the track sources.
3154 mElement
->NotifyOutputTrackStopped(mOwningStream
, mDestinationTrackID
);
3157 mOwningStream
= nullptr;
3163 * Do not keep the track source alive. The source lifetime is controlled by
3164 * its associated tracks.
3166 bool KeepsSourceAlive() const override
{ return false; }
3169 * Do not keep the track source on. It is controlled by its associated tracks.
3171 bool Enabled() const override
{ return false; }
3173 void Disable() override
{}
3175 void Enable() override
{}
3177 void PrincipalChanged() override
3179 if (!mCapturedTrackSource
) {
3180 // This could happen during shutdown.
3184 mPrincipal
= mCapturedTrackSource
->GetPrincipal();
3185 MediaStreamTrackSource::PrincipalChanged();
3188 void MutedChanged(bool aNewState
) override
3190 if (!mCapturedTrackSource
) {
3191 // This could happen during shutdown.
3195 MediaStreamTrackSource::MutedChanged(aNewState
);
3199 virtual ~StreamCaptureTrackSource() {}
3201 RefPtr
<HTMLMediaElement
> mElement
;
3202 RefPtr
<MediaStreamTrackSource
> mCapturedTrackSource
;
3203 RefPtr
<DOMMediaStream
> mOwningStream
;
3204 TrackID mDestinationTrackID
;
3207 NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::StreamCaptureTrackSource
,
3208 MediaStreamTrackSource
)
3209 NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::StreamCaptureTrackSource
,
3210 MediaStreamTrackSource
)
3211 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
3212 HTMLMediaElement::StreamCaptureTrackSource
)
3213 NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource
)
3214 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::StreamCaptureTrackSource
,
3215 MediaStreamTrackSource
,
3217 mCapturedTrackSource
,
3220 class HTMLMediaElement::DecoderCaptureTrackSource
3221 : public MediaStreamTrackSource
3222 , public DecoderPrincipalChangeObserver
3225 NS_DECL_ISUPPORTS_INHERITED
3226 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DecoderCaptureTrackSource
,
3227 MediaStreamTrackSource
)
3229 explicit DecoderCaptureTrackSource(HTMLMediaElement
* aElement
)
3230 : MediaStreamTrackSource(
3231 nsCOMPtr
<nsIPrincipal
>(aElement
->GetCurrentPrincipal()).get(),
3233 , mElement(aElement
)
3235 MOZ_ASSERT(mElement
);
3236 mElement
->AddDecoderPrincipalChangeObserver(this);
3239 void Destroy() override
3242 DebugOnly
<bool> res
=
3243 mElement
->RemoveDecoderPrincipalChangeObserver(this);
3245 "Removing decoder principal changed observer failed. "
3246 "Had it already been removed?");
3251 MediaSourceEnum
GetMediaSource() const override
3253 return MediaSourceEnum::Other
;
3256 CORSMode
GetCORSMode() const override
3259 MOZ_ASSERT(false, "Should always have an element if in use");
3263 return mElement
->GetCORSMode();
3266 void Stop() override
3268 // We don't notify the source that a track was stopped since it will keep
3269 // producing tracks until the element ends. The decoder also needs the
3270 // tracks it created to be live at the source since the decoder's clock is
3271 // based on MediaStreams during capture.
3274 void Disable() override
{}
3276 void Enable() override
{}
3278 void NotifyDecoderPrincipalChanged() override
3280 nsCOMPtr
<nsIPrincipal
> newPrincipal
= mElement
->GetCurrentPrincipal();
3281 if (nsContentUtils::CombineResourcePrincipals(&mPrincipal
, newPrincipal
)) {
3287 virtual ~DecoderCaptureTrackSource() {}
3289 RefPtr
<HTMLMediaElement
> mElement
;
3292 NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource
,
3293 MediaStreamTrackSource
)
3294 NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource
,
3295 MediaStreamTrackSource
)
3296 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
3297 HTMLMediaElement::DecoderCaptureTrackSource
)
3298 NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource
)
3299 NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource
,
3300 MediaStreamTrackSource
,
3303 class HTMLMediaElement::CaptureStreamTrackSourceGetter
3304 : public MediaStreamTrackSourceGetter
3307 NS_DECL_ISUPPORTS_INHERITED
3308 NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CaptureStreamTrackSourceGetter
,
3309 MediaStreamTrackSourceGetter
)
3311 explicit CaptureStreamTrackSourceGetter(HTMLMediaElement
* aElement
)
3312 : mElement(aElement
)
3316 already_AddRefed
<dom::MediaStreamTrackSource
> GetMediaStreamTrackSource(
3317 TrackID aInputTrackID
) override
3319 if (mElement
&& mElement
->mSrcStream
) {
3320 NS_ERROR("Captured media element playing a stream adds tracks explicitly "
3325 // We can return a new source each time here, even for different streams,
3326 // since the sources don't keep any internal state and all of them call
3327 // through to the same HTMLMediaElement.
3328 // If this changes (after implementing Stop()?) we'll have to ensure we
3329 // return the same source for all requests to the same TrackID, and only
3331 return do_AddRef(new DecoderCaptureTrackSource(mElement
));
3335 virtual ~CaptureStreamTrackSourceGetter() {}
3337 RefPtr
<HTMLMediaElement
> mElement
;
3340 NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter
,
3341 MediaStreamTrackSourceGetter
)
3342 NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter
,
3343 MediaStreamTrackSourceGetter
)
3344 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
3345 HTMLMediaElement::CaptureStreamTrackSourceGetter
)
3346 NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSourceGetter
)
3347 NS_IMPL_CYCLE_COLLECTION_INHERITED(
3348 HTMLMediaElement::CaptureStreamTrackSourceGetter
,
3349 MediaStreamTrackSourceGetter
,
3353 HTMLMediaElement::SetCapturedOutputStreamsEnabled(bool aEnabled
)
3355 for (OutputMediaStream
& ms
: mOutputStreams
) {
3356 if (ms
.mCapturingDecoder
) {
3357 MOZ_ASSERT(!ms
.mCapturingMediaStream
);
3360 for (auto pair
: ms
.mTrackPorts
) {
3361 MediaStream
* outputSource
= ms
.mStream
->GetInputStream();
3362 if (!outputSource
) {
3363 NS_ERROR("No output source stream");
3367 TrackID id
= pair
.second()->GetDestinationTrackId();
3368 outputSource
->SetTrackEnabled(id
,
3370 ? DisabledTrackMode::ENABLED
3371 : DisabledTrackMode::SILENCE_FREEZE
);
3373 LOG(LogLevel::Debug
,
3374 ("%s track %d for captured MediaStream %p",
3375 aEnabled
? "Enabled" : "Disabled",
3383 HTMLMediaElement::AddCaptureMediaTrackToOutputStream(
3385 OutputMediaStream
& aOutputStream
,
3386 bool aAsyncAddtrack
)
3388 if (aOutputStream
.mCapturingDecoder
) {
3389 MOZ_ASSERT(!aOutputStream
.mCapturingMediaStream
);
3392 aOutputStream
.mCapturingMediaStream
= true;
3394 if (aOutputStream
.mStream
== mSrcStream
) {
3395 // Cycle detected. This can happen since tracks are added async.
3396 // We avoid forwarding it to the output here or we'd get into an infloop.
3400 MediaStream
* outputSource
= aOutputStream
.mStream
->GetInputStream();
3401 if (!outputSource
) {
3402 NS_ERROR("No output source stream");
3406 ProcessedMediaStream
* processedOutputSource
=
3407 outputSource
->AsProcessedStream();
3408 if (!processedOutputSource
) {
3409 NS_ERROR("Input stream not a ProcessedMediaStream");
3414 MOZ_ASSERT(false, "Bad MediaTrack");
3418 MediaStreamTrack
* inputTrack
= mSrcStream
->GetTrackById(aTrack
->GetId());
3419 MOZ_ASSERT(inputTrack
);
3421 NS_ERROR("Input track not found in source stream");
3426 for (auto pair
: aOutputStream
.mTrackPorts
) {
3427 MOZ_ASSERT(pair
.first() != aTrack
->GetId(),
3428 "Captured track already captured to output stream");
3432 TrackID destinationTrackID
= aOutputStream
.mNextAvailableTrackID
++;
3433 RefPtr
<MediaStreamTrackSource
> source
= new StreamCaptureTrackSource(
3434 this, &inputTrack
->GetSource(), aOutputStream
.mStream
, destinationTrackID
);
3436 MediaSegment::Type type
= inputTrack
->AsAudioStreamTrack()
3437 ? MediaSegment::AUDIO
3438 : MediaSegment::VIDEO
;
3440 RefPtr
<MediaStreamTrack
> track
=
3441 aOutputStream
.mStream
->CreateDOMTrack(destinationTrackID
, type
, source
);
3443 if (aAsyncAddtrack
) {
3444 mMainThreadEventTarget
->Dispatch(
3445 NewRunnableMethod
<StoreRefPtrPassByPtr
<MediaStreamTrack
>>(
3446 "DOMMediaStream::AddTrackInternal",
3447 aOutputStream
.mStream
,
3448 &DOMMediaStream::AddTrackInternal
,
3451 aOutputStream
.mStream
->AddTrackInternal(track
);
3454 // Track is muted initially, so we don't leak data if it's added while paused
3455 // and an MSG iteration passes before the mute comes into effect.
3456 processedOutputSource
->SetTrackEnabled(destinationTrackID
,
3457 DisabledTrackMode::SILENCE_FREEZE
);
3458 RefPtr
<MediaInputPort
> port
= inputTrack
->ForwardTrackContentsTo(
3459 processedOutputSource
, destinationTrackID
);
3461 Pair
<nsString
, RefPtr
<MediaInputPort
>> p(aTrack
->GetId(), port
);
3462 aOutputStream
.mTrackPorts
.AppendElement(std::move(p
));
3464 if (mSrcStreamIsPlaying
) {
3465 processedOutputSource
->SetTrackEnabled(destinationTrackID
,
3466 DisabledTrackMode::ENABLED
);
3469 LOG(LogLevel::Debug
,
3470 ("Created %s track %p with id %d from track %p through MediaInputPort %p",
3471 inputTrack
->AsAudioStreamTrack() ? "audio" : "video",
3479 HTMLMediaElement::CanBeCaptured(StreamCaptureType aCaptureType
)
3481 // Don't bother capturing when the document has gone away
3482 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
3487 // Prevent capturing restricted video
3488 if (aCaptureType
== StreamCaptureType::CAPTURE_ALL_TRACKS
&&
3489 ContainsRestrictedContent()) {
3495 already_AddRefed
<DOMMediaStream
>
3496 HTMLMediaElement::CaptureStreamInternal(StreamCaptureBehavior aFinishBehavior
,
3497 StreamCaptureType aStreamCaptureType
,
3498 MediaStreamGraph
* aGraph
)
3500 MOZ_RELEASE_ASSERT(aGraph
);
3501 MOZ_ASSERT(CanBeCaptured(aStreamCaptureType
));
3503 MarkAsContentSource(CallerAPI::CAPTURE_STREAM
);
3506 // We don't support routing to a different graph.
3507 if (!mOutputStreams
.IsEmpty() &&
3508 aGraph
!= mOutputStreams
[0].mStream
->GetInputStream()->Graph()) {
3512 OutputMediaStream
* out
= mOutputStreams
.AppendElement();
3513 MediaStreamTrackSourceGetter
* getter
=
3514 new CaptureStreamTrackSourceGetter(this);
3515 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
3517 DOMMediaStream::CreateTrackUnionStreamAsInput(window
, aGraph
, getter
);
3518 out
->mStream
->SetInactiveOnFinish();
3519 out
->mFinishWhenEnded
=
3520 aFinishBehavior
== StreamCaptureBehavior::FINISH_WHEN_ENDED
;
3521 out
->mCapturingAudioOnly
=
3522 aStreamCaptureType
== StreamCaptureType::CAPTURE_AUDIO
;
3524 if (aStreamCaptureType
== StreamCaptureType::CAPTURE_AUDIO
) {
3526 // We don't support applying volume and mute to the captured stream, when
3527 // capturing a MediaStream.
3528 nsContentUtils::ReportToConsole(
3529 nsIScriptError::errorFlag
,
3530 NS_LITERAL_CSTRING("Media"),
3532 nsContentUtils::eDOM_PROPERTIES
,
3533 "MediaElementAudioCaptureOfMediaStreamError");
3537 // mAudioCaptured tells the user that the audio played by this media element
3538 // is being routed to the captureStreams *instead* of being played to
3540 mAudioCaptured
= true;
3544 out
->mCapturingDecoder
= true;
3545 mDecoder
->AddOutputStream(
3546 out
->mStream
->GetInputStream()->AsProcessedStream(),
3547 out
->mNextAvailableTrackID
,
3548 aFinishBehavior
== StreamCaptureBehavior::FINISH_WHEN_ENDED
);
3549 } else if (mSrcStream
) {
3550 out
->mCapturingMediaStream
= true;
3553 if (mReadyState
== HAVE_NOTHING
) {
3554 // Do not expose the tracks until we have metadata.
3555 RefPtr
<DOMMediaStream
> result
= out
->mStream
;
3556 return result
.forget();
3561 TrackID audioTrackId
= out
->mNextAvailableTrackID
++;
3562 RefPtr
<MediaStreamTrackSource
> trackSource
=
3563 getter
->GetMediaStreamTrackSource(audioTrackId
);
3564 RefPtr
<MediaStreamTrack
> track
= out
->mStream
->CreateDOMTrack(
3565 audioTrackId
, MediaSegment::AUDIO
, trackSource
);
3566 out
->mPreCreatedTracks
.AppendElement(track
);
3567 out
->mStream
->AddTrackInternal(track
);
3568 LOG(LogLevel::Debug
,
3569 ("Created audio track %d for captured decoder", audioTrackId
));
3571 if (IsVideo() && HasVideo() && !out
->mCapturingAudioOnly
) {
3572 TrackID videoTrackId
= out
->mNextAvailableTrackID
++;
3573 RefPtr
<MediaStreamTrackSource
> trackSource
=
3574 getter
->GetMediaStreamTrackSource(videoTrackId
);
3575 RefPtr
<MediaStreamTrack
> track
= out
->mStream
->CreateDOMTrack(
3576 videoTrackId
, MediaSegment::VIDEO
, trackSource
);
3577 out
->mPreCreatedTracks
.AppendElement(track
);
3578 out
->mStream
->AddTrackInternal(track
);
3579 LOG(LogLevel::Debug
,
3580 ("Created video track %d for captured decoder", videoTrackId
));
3585 for (size_t i
= 0; i
< AudioTracks()->Length(); ++i
) {
3586 AudioTrack
* t
= (*AudioTracks())[i
];
3588 AddCaptureMediaTrackToOutputStream(t
, *out
, false);
3591 if (IsVideo() && !out
->mCapturingAudioOnly
) {
3592 // Only add video tracks if we're a video element and the output stream
3594 for (size_t i
= 0; i
< VideoTracks()->Length(); ++i
) {
3595 VideoTrack
* t
= (*VideoTracks())[i
];
3596 if (t
->Selected()) {
3597 AddCaptureMediaTrackToOutputStream(t
, *out
, false);
3602 RefPtr
<DOMMediaStream
> result
= out
->mStream
;
3603 return result
.forget();
3606 already_AddRefed
<DOMMediaStream
>
3607 HTMLMediaElement::CaptureAudio(ErrorResult
& aRv
, MediaStreamGraph
* aGraph
)
3609 MOZ_RELEASE_ASSERT(aGraph
);
3611 if (!CanBeCaptured(StreamCaptureType::CAPTURE_AUDIO
)) {
3612 aRv
.Throw(NS_ERROR_FAILURE
);
3616 RefPtr
<DOMMediaStream
> stream
=
3617 CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED
,
3618 StreamCaptureType::CAPTURE_AUDIO
,
3621 aRv
.Throw(NS_ERROR_FAILURE
);
3625 return stream
.forget();
3628 already_AddRefed
<DOMMediaStream
>
3629 HTMLMediaElement::MozCaptureStream(ErrorResult
& aRv
)
3631 MediaStreamGraph::GraphDriverType graphDriverType
=
3632 HasAudio() ? MediaStreamGraph::AUDIO_THREAD_DRIVER
3633 : MediaStreamGraph::SYSTEM_THREAD_DRIVER
;
3635 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
3637 aRv
.Throw(NS_ERROR_FAILURE
);
3641 if (!CanBeCaptured(StreamCaptureType::CAPTURE_ALL_TRACKS
)) {
3642 aRv
.Throw(NS_ERROR_FAILURE
);
3646 MediaStreamGraph
* graph
= MediaStreamGraph::GetInstance(
3647 graphDriverType
, window
, MediaStreamGraph::REQUEST_DEFAULT_SAMPLE_RATE
);
3649 RefPtr
<DOMMediaStream
> stream
=
3650 CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED
,
3651 StreamCaptureType::CAPTURE_ALL_TRACKS
,
3654 aRv
.Throw(NS_ERROR_FAILURE
);
3658 return stream
.forget();
3661 already_AddRefed
<DOMMediaStream
>
3662 HTMLMediaElement::MozCaptureStreamUntilEnded(ErrorResult
& aRv
)
3664 MediaStreamGraph::GraphDriverType graphDriverType
=
3665 HasAudio() ? MediaStreamGraph::AUDIO_THREAD_DRIVER
3666 : MediaStreamGraph::SYSTEM_THREAD_DRIVER
;
3668 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
3670 aRv
.Throw(NS_ERROR_FAILURE
);
3674 if (!CanBeCaptured(StreamCaptureType::CAPTURE_ALL_TRACKS
)) {
3675 aRv
.Throw(NS_ERROR_FAILURE
);
3679 MediaStreamGraph
* graph
= MediaStreamGraph::GetInstance(
3680 graphDriverType
, window
, MediaStreamGraph::REQUEST_DEFAULT_SAMPLE_RATE
);
3682 RefPtr
<DOMMediaStream
> stream
=
3683 CaptureStreamInternal(StreamCaptureBehavior::FINISH_WHEN_ENDED
,
3684 StreamCaptureType::CAPTURE_ALL_TRACKS
,
3687 aRv
.Throw(NS_ERROR_FAILURE
);
3691 return stream
.forget();
3694 class MediaElementSetForURI
: public nsURIHashKey
3697 explicit MediaElementSetForURI(const nsIURI
* aKey
) : nsURIHashKey(aKey
) {}
3698 MediaElementSetForURI(MediaElementSetForURI
&& aOther
)
3699 : nsURIHashKey(std::move(aOther
))
3700 , mElements(std::move(aOther
.mElements
)) {}
3701 nsTArray
<HTMLMediaElement
*> mElements
;
3704 typedef nsTHashtable
<MediaElementSetForURI
> MediaElementURITable
;
3705 // Elements in this table must have non-null mDecoder and mLoadingSrc, and those
3706 // can't change while the element is in the table. The table is keyed by
3707 // the element's mLoadingSrc. Each entry has a list of all elements with the
3708 // same mLoadingSrc.
3709 static MediaElementURITable
* gElementTable
;
3713 URISafeEquals(nsIURI
* a1
, nsIURI
* a2
)
3716 // Consider two empty URIs *not* equal!
3720 nsresult rv
= a1
->Equals(a2
, &equal
);
3721 return NS_SUCCEEDED(rv
) && equal
;
3723 // Returns the number of times aElement appears in the media element table
3724 // for aURI. If this returns other than 0 or 1, there's a bug somewhere!
3726 MediaElementTableCount(HTMLMediaElement
* aElement
, nsIURI
* aURI
)
3728 if (!gElementTable
|| !aElement
) {
3731 uint32_t uriCount
= 0;
3732 uint32_t otherCount
= 0;
3733 for (auto it
= gElementTable
->ConstIter(); !it
.Done(); it
.Next()) {
3734 MediaElementSetForURI
* entry
= it
.Get();
3736 for (const auto& elem
: entry
->mElements
) {
3737 if (elem
== aElement
) {
3741 if (URISafeEquals(aURI
, entry
->GetKey())) {
3744 otherCount
+= count
;
3747 NS_ASSERTION(otherCount
== 0, "Should not have entries for unknown URIs");
3753 HTMLMediaElement::AddMediaElementToURITable()
3755 NS_ASSERTION(mDecoder
, "Call this only with decoder Load called");
3757 MediaElementTableCount(this, mLoadingSrc
) == 0,
3758 "Should not have entry for element in element table before addition");
3759 if (!gElementTable
) {
3760 gElementTable
= new MediaElementURITable();
3762 MediaElementSetForURI
* entry
= gElementTable
->PutEntry(mLoadingSrc
);
3763 entry
->mElements
.AppendElement(this);
3765 MediaElementTableCount(this, mLoadingSrc
) == 1,
3766 "Should have a single entry for element in element table after addition");
3770 HTMLMediaElement::RemoveMediaElementFromURITable()
3772 if (!mDecoder
|| !mLoadingSrc
|| !gElementTable
) {
3775 MediaElementSetForURI
* entry
= gElementTable
->GetEntry(mLoadingSrc
);
3779 entry
->mElements
.RemoveElement(this);
3780 if (entry
->mElements
.IsEmpty()) {
3781 gElementTable
->RemoveEntry(entry
);
3782 if (gElementTable
->Count() == 0) {
3783 delete gElementTable
;
3784 gElementTable
= nullptr;
3787 NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc
) == 0,
3788 "After remove, should no longer have an entry in element table");
3792 HTMLMediaElement::LookupMediaElementURITable(nsIURI
* aURI
)
3794 if (!gElementTable
) {
3797 MediaElementSetForURI
* entry
= gElementTable
->GetEntry(aURI
);
3801 for (uint32_t i
= 0; i
< entry
->mElements
.Length(); ++i
) {
3802 HTMLMediaElement
* elem
= entry
->mElements
[i
];
3804 // Look for elements that have the same principal and CORS mode.
3805 // Ditto for anything else that could cause us to send different headers.
3806 if (NS_SUCCEEDED(elem
->NodePrincipal()->Equals(NodePrincipal(), &equal
)) &&
3807 equal
&& elem
->mCORSMode
== mCORSMode
) {
3808 // See SetupDecoder() below. We only add a element to the table when
3809 // mDecoder is a ChannelMediaDecoder.
3810 auto decoder
= static_cast<ChannelMediaDecoder
*>(elem
->mDecoder
.get());
3811 NS_ASSERTION(decoder
, "Decoder gone");
3812 if (decoder
->CanClone()) {
3820 class HTMLMediaElement::ShutdownObserver
: public nsIObserver
3822 enum class Phase
: int8_t
3832 NS_IMETHOD
Observe(nsISupports
*, const char* aTopic
, const char16_t
*) override
3834 if (mPhase
!= Phase::Subscribed
) {
3835 // Bail out if we are not subscribed for this might be called even after
3836 // |nsContentUtils::UnregisterShutdownObserver(this)|.
3839 MOZ_DIAGNOSTIC_ASSERT(mWeak
);
3840 if (strcmp(aTopic
, NS_XPCOM_SHUTDOWN_OBSERVER_ID
) == 0) {
3841 mWeak
->NotifyShutdownEvent();
3845 void Subscribe(HTMLMediaElement
* aPtr
)
3847 MOZ_DIAGNOSTIC_ASSERT(mPhase
== Phase::Init
);
3848 MOZ_DIAGNOSTIC_ASSERT(!mWeak
);
3850 nsContentUtils::RegisterShutdownObserver(this);
3851 mPhase
= Phase::Subscribed
;
3855 MOZ_DIAGNOSTIC_ASSERT(mPhase
== Phase::Subscribed
);
3856 MOZ_DIAGNOSTIC_ASSERT(mWeak
);
3858 nsContentUtils::UnregisterShutdownObserver(this);
3859 mPhase
= Phase::Unsubscribed
;
3861 void AddRefMediaElement() { mWeak
->AddRef(); }
3862 void ReleaseMediaElement() { mWeak
->Release(); }
3865 virtual ~ShutdownObserver()
3867 MOZ_DIAGNOSTIC_ASSERT(mPhase
== Phase::Unsubscribed
);
3868 MOZ_DIAGNOSTIC_ASSERT(!mWeak
);
3870 // Guaranteed to be valid by HTMLMediaElement.
3871 HTMLMediaElement
* mWeak
= nullptr;
3872 Phase mPhase
= Phase::Init
;
3875 NS_IMPL_ISUPPORTS(HTMLMediaElement::ShutdownObserver
, nsIObserver
)
3877 HTMLMediaElement::HTMLMediaElement(
3878 already_AddRefed
<mozilla::dom::NodeInfo
>&& aNodeInfo
)
3879 : nsGenericHTMLElement(std::move(aNodeInfo
))
3880 , mWatchManager(this, OwnerDoc()->AbstractMainThreadFor(TaskCategory::Other
))
3881 , mMainThreadEventTarget(OwnerDoc()->EventTargetFor(TaskCategory::Other
))
3882 , mAbstractMainThread(OwnerDoc()->AbstractMainThreadFor(TaskCategory::Other
))
3883 , mShutdownObserver(new ShutdownObserver
)
3884 , mPlayed(new TimeRanges(ToSupports(OwnerDoc())))
3885 , mPaused(true, "HTMLMediaElement::mPaused")
3886 , mErrorSink(new ErrorSink(this))
3887 , mAudioChannelWrapper(new AudioChannelAgentCallback(this))
3889 MOZ_ASSERT(mMainThreadEventTarget
);
3890 MOZ_ASSERT(mAbstractMainThread
);
3892 DecoderDoctorLogger::LogConstruction(this);
3894 mWatchManager
.Watch(mPaused
, &HTMLMediaElement::UpdateWakeLock
);
3898 double defaultVolume
= Preferences::GetFloat("media.default_volume", 1.0);
3899 SetVolume(defaultVolume
, rv
);
3901 RegisterActivityObserver();
3902 NotifyOwnerDocumentActivityChanged();
3904 mShutdownObserver
->Subscribe(this);
3907 HTMLMediaElement::~HTMLMediaElement()
3911 "How can we be destroyed if we're still holding a self reference?");
3913 mShutdownObserver
->Unsubscribe();
3915 if (mVideoFrameContainer
) {
3916 mVideoFrameContainer
->ForgetElement();
3918 UnregisterActivityObserver();
3920 mSetCDMRequest
.DisconnectIfExists();
3921 mAutoplayPermissionRequest
.DisconnectIfExists();
3925 if (mProgressTimer
) {
3928 if (mVideoDecodeSuspendTimer
) {
3929 mVideoDecodeSuspendTimer
->Cancel();
3930 mVideoDecodeSuspendTimer
= nullptr;
3933 EndSrcMediaStreamPlayback();
3936 if (mCaptureStreamPort
) {
3937 mCaptureStreamPort
->Destroy();
3938 mCaptureStreamPort
= nullptr;
3941 NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc
) == 0,
3942 "Destroyed media element should no longer be in element table");
3944 if (mChannelLoader
) {
3945 mChannelLoader
->Cancel();
3948 if (mAudioChannelWrapper
) {
3949 mAudioChannelWrapper
->Shutdown();
3950 mAudioChannelWrapper
= nullptr;
3955 DecoderDoctorLogger::LogDestruction(this);
3959 HTMLMediaElement::StopSuspendingAfterFirstFrame()
3961 mAllowSuspendAfterFirstFrame
= false;
3962 if (!mSuspendedAfterFirstFrame
)
3964 mSuspendedAfterFirstFrame
= false;
3971 HTMLMediaElement::SetPlayedOrSeeked(bool aValue
)
3973 if (aValue
== mHasPlayedOrSeeked
) {
3977 mHasPlayedOrSeeked
= aValue
;
3979 // Force a reflow so that the poster frame hides or shows immediately.
3980 nsIFrame
* frame
= GetPrimaryFrame();
3984 frame
->PresShell()->FrameNeedsReflow(
3985 frame
, nsIPresShell::eTreeChange
, NS_FRAME_IS_DIRTY
);
3989 HTMLMediaElement::NotifyXPCOMShutdown()
3995 HTMLMediaElement::AudioChannelAgentDelayingPlayback()
3997 return mAudioChannelWrapper
&& mAudioChannelWrapper
->IsPlaybackBlocked();
4001 HTMLMediaElement::UpdateHadAudibleAutoplayState()
4003 // If we're audible, and autoplaying...
4004 if ((Volume() > 0.0 && !Muted()) &&
4005 (!OwnerDoc()->HasBeenUserGestureActivated() || Autoplay())) {
4006 OwnerDoc()->SetDocTreeHadAudibleMedia();
4007 if (AutoplayPolicy::WouldBeAllowedToPlayIfAutoplayDisabled(*this)) {
4008 ScalarAdd(Telemetry::ScalarID::MEDIA_AUTOPLAY_WOULD_BE_ALLOWED_COUNT
, 1);
4009 if (mReadyState
>= HAVE_METADATA
&& !HasAudio()) {
4010 ScalarAdd(Telemetry::ScalarID::MEDIA_ALLOWED_AUTOPLAY_NO_AUDIO_TRACK_COUNT
, 1);
4013 if (mReadyState
< HAVE_METADATA
) {
4014 mBlockedAsWithoutMetadata
= true;
4015 ScalarAdd(Telemetry::ScalarID::MEDIA_BLOCKED_NO_METADATA
, 1);
4017 ScalarAdd(Telemetry::ScalarID::MEDIA_AUTOPLAY_WOULD_NOT_BE_ALLOWED_COUNT
, 1);
4022 already_AddRefed
<Promise
>
4023 HTMLMediaElement::Play(ErrorResult
& aRv
)
4025 LOG(LogLevel::Debug
,
4026 ("%p Play() called by JS readyState=%d", this, mReadyState
));
4029 // When the play() method on a media element is invoked, the user agent must
4030 // run the following steps.
4032 RefPtr
<PlayPromise
> promise
= CreatePlayPromise(aRv
);
4033 if (NS_WARN_IF(aRv
.Failed())) {
4037 // 4.8.12.8 - Step 1:
4038 // If the media element is not allowed to play, return a promise rejected
4039 // with a "NotAllowedError" DOMException and abort these steps.
4040 // NOTE: we may require requesting permission from the user, so we do the
4041 // "not allowed" check below.
4043 // 4.8.12.8 - Step 2:
4044 // If the media element's error attribute is not null and its code
4045 // attribute has the value MEDIA_ERR_SRC_NOT_SUPPORTED, return a promise
4046 // rejected with a "NotSupportedError" DOMException and abort these steps.
4047 if (GetError() && GetError()->Code() == MEDIA_ERR_SRC_NOT_SUPPORTED
) {
4048 LOG(LogLevel::Debug
,
4049 ("%p Play() promise rejected because source not supported.", this));
4050 promise
->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR
);
4051 return promise
.forget();
4054 // 4.8.12.8 - Step 3:
4055 // Let promise be a new promise and append promise to the list of pending
4057 // Note: Promise appended to list of pending promises as needed below.
4059 if (AudioChannelAgentDelayingPlayback()) {
4060 // The audio channel agent may delay starting playback of a media resource
4061 // until the tab the media element is in has been in the foreground.
4062 // Save a reference to the promise, and return it. The AudioChannelAgent
4063 // will call Play() again if the tab is brought to the foreground, or the
4064 // audio tab indicator is clicked, which will resolve the promise if we end
4066 LOG(LogLevel::Debug
, ("%p Play() call delayed by AudioChannelAgent", this));
4068 mPendingPlayPromises
.AppendElement(promise
);
4069 return promise
.forget();
4072 if (AudioChannelAgentBlockedPlay()) {
4073 LOG(LogLevel::Debug
, ("%p play blocked by AudioChannelAgent.", this));
4074 promise
->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR
);
4075 DispatchEventsWhenPlayWasNotAllowed();
4076 return promise
.forget();
4079 UpdateHadAudibleAutoplayState();
4081 const bool handlingUserInput
= EventStateManager::IsHandlingUserInput();
4082 if (AutoplayPolicy::IsAllowedToPlay(*this)) {
4083 mPendingPlayPromises
.AppendElement(promise
);
4084 PlayInternal(handlingUserInput
);
4085 UpdateCustomPolicyAfterPlayed();
4087 // Prompt the user for permission to play.
4088 mPendingPlayPromises
.AppendElement(promise
);
4089 EnsureAutoplayRequested(handlingUserInput
);
4091 return promise
.forget();
4095 HTMLMediaElement::EnsureAutoplayRequested(bool aHandlingUserInput
)
4097 if (mAutoplayPermissionRequest
.Exists()) {
4098 // Autoplay has already been requested in a previous play() call.
4099 // Await for the previous request to be approved or denied. This
4100 // play request's promise will be fulfilled with all other pending
4101 // promises when the permission prompt is resolved.
4102 AUTOPLAY_LOG("%p EnsureAutoplayRequested() existing request, bailing.", this);
4106 RefPtr
<AutoplayPermissionManager
> request
=
4107 AutoplayPolicy::RequestFor(*OwnerDoc());
4109 AsyncRejectPendingPlayPromises(NS_ERROR_DOM_INVALID_STATE_ERR
);
4112 RefPtr
<HTMLMediaElement
> self
= this;
4113 request
->RequestWithPrompt()
4114 ->Then(mAbstractMainThread
,
4116 [ self
, handlingUserInput
= aHandlingUserInput
, request
](
4118 self
->mAutoplayPermissionRequest
.Complete();
4119 AUTOPLAY_LOG("%p Autoplay request approved request=%p",
4122 self
->PlayInternal(handlingUserInput
);
4123 self
->UpdateCustomPolicyAfterPlayed();
4125 [self
, request
](nsresult aError
) {
4126 self
->mAutoplayPermissionRequest
.Complete();
4127 AUTOPLAY_LOG("%p Autoplay request denied request=%p",
4130 LOG(LogLevel::Debug
, ("%s rejecting play promises", __func__
));
4131 self
->AsyncRejectPendingPlayPromises(
4132 NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR
);
4133 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag
,
4134 NS_LITERAL_CSTRING("Media"),
4136 nsContentUtils::eDOM_PROPERTIES
,
4137 "BlockAutoplayError");
4139 ->Track(mAutoplayPermissionRequest
);
4143 HTMLMediaElement::DispatchEventsWhenPlayWasNotAllowed()
4145 if (StaticPrefs::MediaBlockEventEnabled()) {
4146 DispatchAsyncEvent(NS_LITERAL_STRING("blocked"));
4148 #if defined(MOZ_WIDGET_ANDROID)
4149 RefPtr
<AsyncEventDispatcher
> asyncDispatcher
=
4150 new AsyncEventDispatcher(this,
4151 NS_LITERAL_STRING("MozAutoplayMediaBlocked"),
4153 ChromeOnlyDispatch::eYes
);
4154 asyncDispatcher
->PostDOMEvent();
4159 HTMLMediaElement::PlayInternal(bool aHandlingUserInput
)
4161 if (mPreloadAction
== HTMLMediaElement::PRELOAD_NONE
) {
4162 // The media load algorithm will be initiated by a user interaction.
4163 // We want to boost the channel priority for better responsiveness.
4164 // Note this must be done before UpdatePreloadAction() which will
4165 // update |mPreloadAction|.
4166 mUseUrgentStartForChannel
= true;
4169 StopSuspendingAfterFirstFrame();
4170 SetPlayedOrSeeked(true);
4172 // 4.8.12.8 - Step 4:
4173 // If the media element's networkState attribute has the value NETWORK_EMPTY,
4174 // invoke the media element's resource selection algorithm.
4176 if (mSuspendedForPreloadNone
) {
4177 ResumeLoad(PRELOAD_ENOUGH
);
4180 // 4.8.12.8 - Step 5:
4181 // If the playback has ended and the direction of playback is forwards,
4182 // seek to the earliest possible position of the media resource.
4184 // Even if we just did Load() or ResumeLoad(), we could already have a decoder
4185 // here if we managed to clone an existing decoder.
4187 if (mDecoder
->IsEnded()) {
4190 if (!mPausedForInactiveDocumentOrChannel
) {
4195 if (mCurrentPlayRangeStart
== -1.0) {
4196 mCurrentPlayRangeStart
= CurrentTime();
4199 const bool oldPaused
= mPaused
;
4201 mAutoplaying
= false;
4203 // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
4204 // and our preload status.
4205 AddRemoveSelfReference();
4206 UpdatePreloadAction();
4207 UpdateSrcMediaStreamPlaying();
4209 // Once play() has been called in a user generated event handler,
4210 // it is allowed to autoplay. Note: we can reach here when not in
4211 // a user generated event handler if our readyState has not yet
4212 // reached HAVE_METADATA.
4213 mIsBlessed
|= aHandlingUserInput
;
4215 // TODO: If the playback has ended, then the user agent must set
4216 // seek to the effective start.
4218 // 4.8.12.8 - Step 6:
4219 // If the media element's paused attribute is true, run the following steps:
4221 // 6.1. Change the value of paused to false. (Already done.)
4222 // This step is uplifted because the "block-media-playback" feature needs
4223 // the mPaused to be false before UpdateAudioChannelPlayingState() being
4226 // 6.2. If the show poster flag is true, set the element's show poster flag
4227 // to false and run the time marches on steps.
4229 // 6.3. Queue a task to fire a simple event named play at the element.
4230 DispatchAsyncEvent(NS_LITERAL_STRING("play"));
4232 // 6.4. If the media element's readyState attribute has the value
4233 // HAVE_NOTHING, HAVE_METADATA, or HAVE_CURRENT_DATA, queue a task to
4234 // fire a simple event named waiting at the element.
4235 // Otherwise, the media element's readyState attribute has the value
4236 // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA: notify about playing for the
4238 switch (mReadyState
) {
4240 DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
4243 case HAVE_CURRENT_DATA
:
4244 FireTimeUpdate(false);
4245 DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
4247 case HAVE_FUTURE_DATA
:
4248 case HAVE_ENOUGH_DATA
:
4249 FireTimeUpdate(false);
4250 NotifyAboutPlaying();
4253 } else if (mReadyState
>= HAVE_FUTURE_DATA
) {
4254 // 7. Otherwise, if the media element's readyState attribute has the value
4255 // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, take pending play promises and
4256 // queue a task to resolve pending play promises with the result.
4257 AsyncResolvePendingPlayPromises();
4260 // 8. Set the media element's autoplaying flag to false. (Already done.)
4262 // 9. Return promise.
4263 // (Done in caller.)
4267 HTMLMediaElement::MaybeDoLoad()
4269 if (mNetworkState
== NETWORK_EMPTY
) {
4275 HTMLMediaElement::UpdateWakeLock()
4277 MOZ_ASSERT(NS_IsMainThread());
4278 // Ensure we have a wake lock if we're playing audibly. This ensures the
4279 // device doesn't sleep while playing.
4280 bool playing
= !mPaused
;
4282 Volume() > 0.0 && !mMuted
&& mIsAudioTrackAudible
;
4283 // WakeLock when playing audible media.
4284 if (playing
&& isAudible
) {
4285 CreateAudioWakeLockIfNeeded();
4287 ReleaseAudioWakeLockIfExists();
4292 HTMLMediaElement::CreateAudioWakeLockIfNeeded()
4295 RefPtr
<power::PowerManagerService
> pmService
=
4296 power::PowerManagerService::GetInstance();
4297 NS_ENSURE_TRUE_VOID(pmService
);
4300 mWakeLock
= pmService
->NewWakeLock(NS_LITERAL_STRING("audio-playing"),
4301 OwnerDoc()->GetInnerWindow(),
4307 HTMLMediaElement::ReleaseAudioWakeLockIfExists()
4311 mWakeLock
->Unlock(rv
);
4312 rv
.SuppressException();
4313 mWakeLock
= nullptr;
4318 HTMLMediaElement::WakeLockRelease()
4320 ReleaseAudioWakeLockIfExists();
4323 HTMLMediaElement::OutputMediaStream::OutputMediaStream()
4324 : mNextAvailableTrackID(1)
4325 , mFinishWhenEnded(false)
4326 , mCapturingAudioOnly(false)
4327 , mCapturingDecoder(false)
4328 , mCapturingMediaStream(false)
4332 HTMLMediaElement::OutputMediaStream::~OutputMediaStream()
4334 for (auto pair
: mTrackPorts
) {
4335 pair
.second()->Destroy();
4340 HTMLMediaElement::GetEventTargetParent(EventChainPreVisitor
& aVisitor
)
4342 if (!this->Controls() || !aVisitor
.mEvent
->mFlags
.mIsTrusted
) {
4343 nsGenericHTMLElement::GetEventTargetParent(aVisitor
);
4347 HTMLInputElement
* el
= nullptr;
4348 nsCOMPtr
<nsINode
> node
;
4350 // We will need to trap pointer, touch, and mouse events within the media
4351 // element, allowing media control exclusive consumption on these events,
4352 // and preventing the content from handling them.
4353 switch (aVisitor
.mEvent
->mMessage
) {
4357 // Always prevent touchmove captured in video element from being handled by
4358 // content, since we always do that for touchstart.
4362 case eMouseDoubleClick
:
4365 aVisitor
.mCanHandle
= false;
4368 // The *move events however are only comsumed when the range input is being
4372 node
= do_QueryInterface(aVisitor
.mEvent
->mOriginalTarget
);
4373 if (node
->IsInNativeAnonymousSubtree() ||
4374 node
->IsInUAWidget()) {
4375 if (node
->IsHTMLElement(nsGkAtoms::input
)) {
4376 // The node is a <input type="range">
4377 el
= static_cast<HTMLInputElement
*>(node
.get());
4378 } else if (node
->GetParentNode() &&
4379 node
->GetParentNode()->IsHTMLElement(nsGkAtoms::input
)) {
4380 // The node is a child of <input type="range">
4381 el
= static_cast<HTMLInputElement
*>(node
->GetParentNode());
4384 if (el
&& el
->IsDraggingRange()) {
4385 aVisitor
.mCanHandle
= false;
4388 nsGenericHTMLElement::GetEventTargetParent(aVisitor
);
4392 nsGenericHTMLElement::GetEventTargetParent(aVisitor
);
4398 HTMLMediaElement::ParseAttribute(int32_t aNamespaceID
,
4400 const nsAString
& aValue
,
4401 nsIPrincipal
* aMaybeScriptedPrincipal
,
4402 nsAttrValue
& aResult
)
4404 // Mappings from 'preload' attribute strings to an enumeration.
4405 static const nsAttrValue::EnumTable kPreloadTable
[] = {
4406 { "", HTMLMediaElement::PRELOAD_ATTR_EMPTY
},
4407 { "none", HTMLMediaElement::PRELOAD_ATTR_NONE
},
4408 { "metadata", HTMLMediaElement::PRELOAD_ATTR_METADATA
},
4409 { "auto", HTMLMediaElement::PRELOAD_ATTR_AUTO
},
4413 if (aNamespaceID
== kNameSpaceID_None
) {
4414 if (ParseImageAttribute(aAttribute
, aValue
, aResult
)) {
4417 if (aAttribute
== nsGkAtoms::crossorigin
) {
4418 ParseCORSValue(aValue
, aResult
);
4421 if (aAttribute
== nsGkAtoms::preload
) {
4422 return aResult
.ParseEnumValue(aValue
, kPreloadTable
, false);
4426 return nsGenericHTMLElement::ParseAttribute(
4427 aNamespaceID
, aAttribute
, aValue
, aMaybeScriptedPrincipal
, aResult
);
4431 HTMLMediaElement::DoneCreatingElement()
4433 if (HasAttr(kNameSpaceID_None
, nsGkAtoms::muted
)) {
4434 mMuted
|= MUTED_BY_CONTENT
;
4439 HTMLMediaElement::IsHTMLFocusable(bool aWithMouse
,
4443 if (nsGenericHTMLElement::IsHTMLFocusable(
4444 aWithMouse
, aIsFocusable
, aTabIndex
)) {
4448 *aIsFocusable
= true;
4453 HTMLMediaElement::TabIndexDefault()
4459 HTMLMediaElement::AfterSetAttr(int32_t aNameSpaceID
,
4461 const nsAttrValue
* aValue
,
4462 const nsAttrValue
* aOldValue
,
4463 nsIPrincipal
* aMaybeScriptedPrincipal
,
4466 if (aNameSpaceID
== kNameSpaceID_None
) {
4467 if (aName
== nsGkAtoms::src
) {
4468 mSrcMediaSource
= nullptr;
4469 mSrcAttrTriggeringPrincipal
= nsContentUtils::GetAttrTriggeringPrincipal(
4471 aValue
? aValue
->GetStringValue() : EmptyString(),
4472 aMaybeScriptedPrincipal
);
4474 nsString srcStr
= aValue
->GetStringValue();
4475 nsCOMPtr
<nsIURI
> uri
;
4476 NewURIFromString(srcStr
, getter_AddRefs(uri
));
4477 if (uri
&& IsMediaSourceURI(uri
)) {
4479 NS_GetSourceForMediaSourceURI(uri
, getter_AddRefs(mSrcMediaSource
));
4480 if (NS_FAILED(rv
)) {
4482 GetCurrentSrc(spec
);
4483 const char16_t
* params
[] = { spec
.get() };
4484 ReportLoadError("MediaLoadInvalidURI", params
, ArrayLength(params
));
4488 } else if (aName
== nsGkAtoms::autoplay
) {
4491 StopSuspendingAfterFirstFrame();
4492 CheckAutoplayDataReady();
4494 // This attribute can affect AddRemoveSelfReference
4495 AddRemoveSelfReference();
4496 UpdatePreloadAction();
4498 } else if (aName
== nsGkAtoms::preload
) {
4499 UpdatePreloadAction();
4500 } else if (aName
== nsGkAtoms::loop
) {
4502 mDecoder
->SetLooping(!!aValue
);
4504 } else if (nsContentUtils::IsUAWidgetEnabled() &&
4505 aName
== nsGkAtoms::controls
&&
4506 IsInComposedDoc()) {
4507 AsyncEventDispatcher
* dispatcher
=
4508 new AsyncEventDispatcher(this,
4509 NS_LITERAL_STRING("UAWidgetAttributeChanged"),
4511 ChromeOnlyDispatch::eYes
);
4512 // This has to happen at this tick so that UA Widget could respond
4513 // before returning to content script.
4514 dispatcher
->RunDOMEventWhenSafe();
4518 // Since AfterMaybeChangeAttr may call DoLoad, make sure that it is called
4519 // *after* any possible changes to mSrcMediaSource.
4521 AfterMaybeChangeAttr(aNameSpaceID
, aName
, aNotify
);
4524 return nsGenericHTMLElement::AfterSetAttr(
4525 aNameSpaceID
, aName
, aValue
, aOldValue
, aMaybeScriptedPrincipal
, aNotify
);
4529 HTMLMediaElement::OnAttrSetButNotChanged(int32_t aNamespaceID
,
4531 const nsAttrValueOrString
& aValue
,
4534 AfterMaybeChangeAttr(aNamespaceID
, aName
, aNotify
);
4536 return nsGenericHTMLElement::OnAttrSetButNotChanged(
4537 aNamespaceID
, aName
, aValue
, aNotify
);
4541 HTMLMediaElement::AfterMaybeChangeAttr(int32_t aNamespaceID
,
4545 if (aNamespaceID
== kNameSpaceID_None
) {
4546 if (aName
== nsGkAtoms::src
) {
4553 HTMLMediaElement::BindToTree(nsIDocument
* aDocument
,
4554 nsIContent
* aParent
,
4555 nsIContent
* aBindingParent
)
4557 nsresult rv
= nsGenericHTMLElement::BindToTree(
4558 aDocument
, aParent
, aBindingParent
);
4560 if (nsContentUtils::IsUAWidgetEnabled() && IsInComposedDoc()) {
4561 // Construct Shadow Root so web content can be hidden in the DOM.
4562 AttachAndSetUAShadowRoot();
4564 AsyncEventDispatcher
* dispatcher
=
4565 new AsyncEventDispatcher(this,
4566 NS_LITERAL_STRING("UAWidgetBindToTree"),
4568 ChromeOnlyDispatch::eYes
);
4569 dispatcher
->RunDOMEventWhenSafe();
4571 // We don't want to call into JS if the website never asks for native
4573 // If controls attribute is set later, controls is constructed lazily
4574 // with the UAWidgetAttributeChanged event.
4575 // This only applies to Desktop because on Fennec we would need to show
4576 // an UI if the video is blocked.
4578 AsyncEventDispatcher
* dispatcher
=
4579 new AsyncEventDispatcher(this,
4580 NS_LITERAL_STRING("UAWidgetBindToTree"),
4582 ChromeOnlyDispatch::eYes
);
4583 dispatcher
->RunDOMEventWhenSafe();
4588 mUnboundFromTree
= false;
4591 // The preload action depends on the value of the autoplay attribute.
4592 // It's value may have changed, so update it.
4593 UpdatePreloadAction();
4596 NotifyDecoderActivityChanges();
4603 HTMLMediaElement::VideoDecodeSuspendTimerCallback(nsITimer
* aTimer
,
4606 MOZ_ASSERT(NS_IsMainThread());
4607 auto element
= static_cast<HTMLMediaElement
*>(aClosure
);
4608 element
->mVideoDecodeSuspendTime
.Start();
4609 element
->mVideoDecodeSuspendTimer
= nullptr;
4613 HTMLMediaElement::HiddenVideoStart()
4615 MOZ_ASSERT(NS_IsMainThread());
4616 mHiddenPlayTime
.Start();
4617 if (mVideoDecodeSuspendTimer
) {
4618 // Already started, just keep it running.
4621 NS_NewTimerWithFuncCallback(
4622 getter_AddRefs(mVideoDecodeSuspendTimer
),
4623 VideoDecodeSuspendTimerCallback
,
4625 StaticPrefs::MediaSuspendBkgndVideoDelayMs(),
4626 nsITimer::TYPE_ONE_SHOT
,
4627 "HTMLMediaElement::VideoDecodeSuspendTimerCallback",
4628 mMainThreadEventTarget
);
4632 HTMLMediaElement::HiddenVideoStop()
4634 MOZ_ASSERT(NS_IsMainThread());
4635 mHiddenPlayTime
.Pause();
4636 mVideoDecodeSuspendTime
.Pause();
4637 if (!mVideoDecodeSuspendTimer
) {
4640 mVideoDecodeSuspendTimer
->Cancel();
4641 mVideoDecodeSuspendTimer
= nullptr;
4645 HTMLMediaElement::ReportTelemetry()
4647 // Report telemetry for videos when a page is unloaded. We
4648 // want to know data on what state the video is at when
4649 // the user has exited.
4659 UnloadedState state
= OTHER
;
4662 } else if (Ended()) {
4664 } else if (Paused()) {
4667 // For buffering we check if the current playback position is at the end
4668 // of a buffered range, within a margin of error. We also consider to be
4669 // buffering if the last frame status was buffering and the ready state is
4670 // HAVE_CURRENT_DATA to account for times where we are in a buffering state
4671 // regardless of what actual data we have buffered.
4672 bool stalled
= false;
4673 RefPtr
<TimeRanges
> ranges
= Buffered();
4674 const double errorMargin
= 0.05;
4675 double t
= CurrentTime();
4676 TimeRanges::index_type index
= ranges
->Find(t
, errorMargin
);
4678 index
!= TimeRanges::NoIndex
&& (ranges
->End(index
) - t
) < errorMargin
;
4679 stalled
|= mDecoder
&&
4680 NextFrameStatus() ==
4681 MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING
&&
4682 mReadyState
== HAVE_CURRENT_DATA
;
4688 Telemetry::Accumulate(Telemetry::VIDEO_UNLOAD_STATE
, state
);
4689 LOG(LogLevel::Debug
, ("%p VIDEO_UNLOAD_STATE = %d", this, state
));
4691 FrameStatisticsData data
;
4693 if (HTMLVideoElement
* vid
= HTMLVideoElement::FromNodeOrNull(this)) {
4694 FrameStatistics
* stats
= vid
->GetFrameStatistics();
4696 data
= stats
->GetFrameStatisticsData();
4697 if (data
.mParsedFrames
) {
4698 MOZ_ASSERT(data
.mDroppedFrames
<= data
.mParsedFrames
);
4699 // Dropped frames <= total frames, so 'percentage' cannot be higher than
4700 // 100 and therefore can fit in a uint32_t (that Telemetry takes).
4701 uint32_t percentage
= 100 * data
.mDroppedFrames
/ data
.mParsedFrames
;
4702 LOG(LogLevel::Debug
,
4703 ("Reporting telemetry DROPPED_FRAMES_IN_VIDEO_PLAYBACK"));
4704 Telemetry::Accumulate(Telemetry::VIDEO_DROPPED_FRAMES_PROPORTION
,
4710 if (mMediaInfo
.HasVideo() && mMediaInfo
.mVideo
.mImage
.height
> 0) {
4711 // We have a valid video.
4712 double playTime
= mPlayTime
.Total();
4713 double hiddenPlayTime
= mHiddenPlayTime
.Total();
4714 double videoDecodeSuspendTime
= mVideoDecodeSuspendTime
.Total();
4716 Telemetry::Accumulate(Telemetry::VIDEO_PLAY_TIME_MS
,
4717 SECONDS_TO_MS(playTime
));
4718 LOG(LogLevel::Debug
, ("%p VIDEO_PLAY_TIME_MS = %f", this, playTime
));
4720 Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_MS
,
4721 SECONDS_TO_MS(hiddenPlayTime
));
4722 LOG(LogLevel::Debug
,
4723 ("%p VIDEO_HIDDEN_PLAY_TIME_MS = %f", this, hiddenPlayTime
));
4725 if (playTime
> 0.0) {
4726 // We have actually played something -> Report some valid-video telemetry.
4728 // Keyed by audio+video or video alone, and by a resolution range.
4729 nsCString
key(mMediaInfo
.HasAudio() ? "AV," : "V,");
4734 } sResolutions
[] = { { 240, "0<h<=240" }, { 480, "240<h<=480" },
4735 { 576, "480<h<=576" }, { 720, "576<h<=720" },
4736 { 1080, "720<h<=1080" }, { 2160, "1080<h<=2160" } };
4737 const char* resolution
= "h>2160";
4738 int32_t height
= mMediaInfo
.mVideo
.mImage
.height
;
4739 for (const auto& res
: sResolutions
) {
4740 if (height
<= res
.mH
) {
4741 resolution
= res
.mRes
;
4745 key
.AppendASCII(resolution
);
4747 uint32_t hiddenPercentage
=
4748 uint32_t(hiddenPlayTime
/ playTime
* 100.0 + 0.5);
4749 Telemetry::Accumulate(
4750 Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE
, key
, hiddenPercentage
);
4751 // Also accumulate all percentages in an "All" key.
4752 Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE
,
4753 NS_LITERAL_CSTRING("All"),
4755 LOG(LogLevel::Debug
,
4756 ("%p VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE = %u, keys: '%s' and 'All'",
4761 uint32_t videoDecodeSuspendPercentage
=
4762 uint32_t(videoDecodeSuspendTime
/ playTime
* 100.0 + 0.5);
4763 Telemetry::Accumulate(Telemetry::VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE
,
4765 videoDecodeSuspendPercentage
);
4766 Telemetry::Accumulate(Telemetry::VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE
,
4767 NS_LITERAL_CSTRING("All"),
4768 videoDecodeSuspendPercentage
);
4769 LOG(LogLevel::Debug
,
4770 ("%p VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE = %u, keys: '%s' and "
4773 videoDecodeSuspendPercentage
,
4776 if (data
.mInterKeyframeCount
!= 0) {
4777 uint32_t average_ms
= uint32_t(
4778 std::min
<uint64_t>(double(data
.mInterKeyframeSum_us
) /
4779 double(data
.mInterKeyframeCount
) / 1000.0 +
4782 Telemetry::Accumulate(
4783 Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS
, key
, average_ms
);
4784 Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS
,
4785 NS_LITERAL_CSTRING("All"),
4787 LOG(LogLevel::Debug
,
4788 ("%p VIDEO_INTER_KEYFRAME_AVERAGE_MS = %u, keys: '%s' and 'All'",
4793 uint32_t max_ms
= uint32_t(std::min
<uint64_t>(
4794 (data
.mInterKeyFrameMax_us
+ 500) / 1000, UINT32_MAX
));
4795 Telemetry::Accumulate(
4796 Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS
, key
, max_ms
);
4797 Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS
,
4798 NS_LITERAL_CSTRING("All"),
4800 LOG(LogLevel::Debug
,
4801 ("%p VIDEO_INTER_KEYFRAME_MAX_MS = %u, keys: '%s' and 'All'",
4806 // Here, we have played *some* of the video, but didn't get more than 1
4807 // keyframe. Report '0' if we have played for longer than the video-
4808 // decode-suspend delay (showing recovery would be difficult).
4809 uint32_t suspendDelay_ms
= StaticPrefs::MediaSuspendBkgndVideoDelayMs();
4810 if (uint32_t(playTime
* 1000.0) > suspendDelay_ms
) {
4811 Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS
, key
, 0);
4812 Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS
,
4813 NS_LITERAL_CSTRING("All"),
4815 LOG(LogLevel::Debug
,
4816 ("%p VIDEO_INTER_KEYFRAME_MAX_MS = 0 (only 1 keyframe), keys: "
4827 HTMLMediaElement::UnbindFromTree(bool aDeep
, bool aNullParent
)
4829 mUnboundFromTree
= true;
4830 mVisibilityState
= Visibility::UNTRACKED
;
4832 nsGenericHTMLElement::UnbindFromTree(aDeep
, aNullParent
);
4834 MOZ_ASSERT(IsHidden());
4835 NotifyDecoderActivityChanges();
4837 AsyncEventDispatcher
* dispatcher
=
4838 new AsyncEventDispatcher(this,
4839 NS_LITERAL_STRING("UAWidgetUnbindFromTree"),
4841 ChromeOnlyDispatch::eYes
);
4842 dispatcher
->RunDOMEventWhenSafe();
4844 RefPtr
<HTMLMediaElement
> self(this);
4845 nsCOMPtr
<nsIRunnable
> task
=
4846 NS_NewRunnableFunction("dom::HTMLMediaElement::UnbindFromTree", [self
]() {
4847 if (self
->mUnboundFromTree
) {
4851 RunInStableState(task
);
4856 HTMLMediaElement::GetCanPlay(const nsAString
& aType
,
4857 DecoderDoctorDiagnostics
* aDiagnostics
)
4859 Maybe
<MediaContainerType
> containerType
= MakeMediaContainerType(aType
);
4860 if (!containerType
) {
4863 CanPlayStatus status
=
4864 DecoderTraits::CanHandleContainerType(*containerType
, aDiagnostics
);
4865 if (status
== CANPLAY_YES
&&
4866 (*containerType
).ExtendedType().Codecs().IsEmpty()) {
4867 // Per spec: 'Generally, a user agent should never return "probably" for a
4868 // type that allows the `codecs` parameter if that parameter is not
4869 // present.' As all our currently-supported types allow for `codecs`, we can
4870 // do this check here.
4871 // TODO: Instead, missing `codecs` should be checked in each decoder's
4872 // `IsSupportedType` call from `CanHandleCodecsType()`.
4874 return CANPLAY_MAYBE
;
4880 HTMLMediaElement::CanPlayType(const nsAString
& aType
, nsAString
& aResult
)
4882 DecoderDoctorDiagnostics diagnostics
;
4883 CanPlayStatus canPlay
= GetCanPlay(aType
, &diagnostics
);
4884 diagnostics
.StoreFormatDiagnostics(
4885 OwnerDoc(), aType
, canPlay
!= CANPLAY_NO
, __func__
);
4891 aResult
.AssignLiteral("probably");
4894 aResult
.AssignLiteral("maybe");
4897 MOZ_ASSERT_UNREACHABLE("Unexpected case.");
4901 LOG(LogLevel::Debug
,
4902 ("%p CanPlayType(%s) = \"%s\"",
4904 NS_ConvertUTF16toUTF8(aType
).get(),
4905 NS_ConvertUTF16toUTF8(aResult
).get()));
4909 HTMLMediaElement::AssertReadyStateIsNothing()
4911 #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4912 if (mReadyState
!= HAVE_NOTHING
) {
4915 "readyState=%d networkState=%d mLoadWaitStatus=%d "
4916 "mSourceLoadCandidate=%d "
4917 "mIsLoadingFromSourceChildren=%d mPreloadAction=%d "
4918 "mSuspendedForPreloadNone=%d error=%d",
4921 int(mLoadWaitStatus
),
4922 !!mSourceLoadCandidate
,
4923 mIsLoadingFromSourceChildren
,
4924 int(mPreloadAction
),
4925 mSuspendedForPreloadNone
,
4926 GetError() ? GetError()->Code() : 0);
4927 MOZ_CRASH_UNSAFE_PRINTF("ReadyState should be HAVE_NOTHING! %s", buf
);
4933 HTMLMediaElement::AttachAndSetUAShadowRoot()
4935 if (GetShadowRoot()) {
4936 MOZ_ASSERT(GetShadowRoot()->IsUAWidget());
4940 // Add a closed shadow root to host video controls
4941 RefPtr
<ShadowRoot
> shadowRoot
=
4942 AttachShadowWithoutNameChecks(ShadowRootMode::Closed
);
4943 shadowRoot
->SetIsUAWidget();
4947 HTMLMediaElement::InitializeDecoderAsClone(ChannelMediaDecoder
* aOriginal
)
4949 NS_ASSERTION(mLoadingSrc
, "mLoadingSrc must already be set");
4950 NS_ASSERTION(mDecoder
== nullptr, "Shouldn't have a decoder");
4951 AssertReadyStateIsNothing();
4953 MediaDecoderInit
decoderInit(this,
4954 mMuted
? 0.0 : mVolume
,
4958 HTMLMediaElement::PRELOAD_METADATA
,
4960 HasAttr(kNameSpaceID_None
, nsGkAtoms::loop
),
4961 aOriginal
->ContainerType());
4963 RefPtr
<ChannelMediaDecoder
> decoder
= aOriginal
->Clone(decoderInit
);
4965 return NS_ERROR_FAILURE
;
4967 LOG(LogLevel::Debug
,
4968 ("%p Cloned decoder %p from %p", this, decoder
.get(), aOriginal
));
4970 return FinishDecoderSetup(decoder
);
4973 template<typename DecoderType
, typename
... LoadArgs
>
4975 HTMLMediaElement::SetupDecoder(DecoderType
* aDecoder
, LoadArgs
&&... aArgs
)
4977 LOG(LogLevel::Debug
,
4978 ("%p Created decoder %p for type %s",
4981 aDecoder
->ContainerType().OriginalString().Data()));
4983 nsresult rv
= aDecoder
->Load(std::forward
<LoadArgs
>(aArgs
)...);
4984 if (NS_FAILED(rv
)) {
4985 aDecoder
->Shutdown();
4986 LOG(LogLevel::Debug
, ("%p Failed to load for decoder %p", this, aDecoder
));
4990 rv
= FinishDecoderSetup(aDecoder
);
4991 // Only ChannelMediaDecoder supports resource cloning.
4992 if (IsSame
<DecoderType
, ChannelMediaDecoder
>::value
&& NS_SUCCEEDED(rv
)) {
4993 AddMediaElementToURITable();
4995 MediaElementTableCount(this, mLoadingSrc
) == 1,
4996 "Media element should have single table entry if decode initialized");
5003 HTMLMediaElement::InitializeDecoderForChannel(nsIChannel
* aChannel
,
5004 nsIStreamListener
** aListener
)
5006 NS_ASSERTION(mLoadingSrc
, "mLoadingSrc must already be set");
5007 AssertReadyStateIsNothing();
5009 DecoderDoctorDiagnostics diagnostics
;
5011 nsAutoCString mimeType
;
5012 aChannel
->GetContentType(mimeType
);
5013 NS_ASSERTION(!mimeType
.IsEmpty(), "We should have the Content-Type.");
5014 NS_ConvertUTF8toUTF16
mimeUTF16(mimeType
);
5016 RefPtr
<HTMLMediaElement
> self
= this;
5017 auto reportCanPlay
= [&, self
](bool aCanPlay
) {
5018 diagnostics
.StoreFormatDiagnostics(
5019 self
->OwnerDoc(), mimeUTF16
, aCanPlay
, __func__
);
5022 self
->GetCurrentSrc(src
);
5023 const char16_t
* params
[] = { mimeUTF16
.get(), src
.get() };
5024 self
->ReportLoadError(
5025 "MediaLoadUnsupportedMimeType", params
, ArrayLength(params
));
5029 auto onExit
= MakeScopeExit([self
] {
5030 if (self
->mChannelLoader
) {
5031 self
->mChannelLoader
->Done();
5032 self
->mChannelLoader
= nullptr;
5036 Maybe
<MediaContainerType
> containerType
= MakeMediaContainerType(mimeType
);
5037 if (!containerType
) {
5038 reportCanPlay(false);
5039 return NS_ERROR_FAILURE
;
5042 MediaDecoderInit
decoderInit(this,
5043 mMuted
? 0.0 : mVolume
,
5047 HTMLMediaElement::PRELOAD_METADATA
,
5049 HasAttr(kNameSpaceID_None
, nsGkAtoms::loop
),
5052 #ifdef MOZ_ANDROID_HLS_SUPPORT
5053 if (HLSDecoder::IsSupportedType(*containerType
)) {
5054 RefPtr
<HLSDecoder
> decoder
= new HLSDecoder(decoderInit
);
5055 reportCanPlay(true);
5056 return SetupDecoder(decoder
.get(), aChannel
);
5060 RefPtr
<ChannelMediaDecoder
> decoder
=
5061 ChannelMediaDecoder::Create(decoderInit
, &diagnostics
);
5063 reportCanPlay(false);
5064 return NS_ERROR_FAILURE
;
5067 reportCanPlay(true);
5068 bool isPrivateBrowsing
= NodePrincipal()->GetPrivateBrowsingId() > 0;
5069 return SetupDecoder(decoder
.get(), aChannel
, isPrivateBrowsing
, aListener
);
5073 HTMLMediaElement::FinishDecoderSetup(MediaDecoder
* aDecoder
)
5075 ChangeNetworkState(NETWORK_LOADING
);
5077 // Set mDecoder now so if methods like GetCurrentSrc get called between
5078 // here and Load(), they work.
5079 SetDecoder(aDecoder
);
5081 // Notify the decoder of the initial activity status.
5082 NotifyDecoderActivityChanges();
5084 // Update decoder principal before we start decoding, since it
5085 // can affect how we feed data to MediaStreams
5086 NotifyDecoderPrincipalChanged();
5088 for (OutputMediaStream
& ms
: mOutputStreams
) {
5089 if (ms
.mCapturingMediaStream
) {
5090 MOZ_ASSERT(!ms
.mCapturingDecoder
);
5094 ms
.mCapturingDecoder
= true;
5095 aDecoder
->AddOutputStream(ms
.mStream
->GetInputStream()->AsProcessedStream(),
5096 ms
.mNextAvailableTrackID
,
5097 ms
.mFinishWhenEnded
);
5101 if (mMediaKeys
->GetCDMProxy()) {
5102 mDecoder
->SetCDMProxy(mMediaKeys
->GetCDMProxy());
5104 // CDM must have crashed.
5106 return NS_ERROR_FAILURE
;
5110 if (mChannelLoader
) {
5111 mChannelLoader
->Done();
5112 mChannelLoader
= nullptr;
5115 // We may want to suspend the new stream now.
5116 // This will also do an AddRemoveSelfReference.
5117 NotifyOwnerDocumentActivityChanged();
5119 if (mPausedForInactiveDocumentOrChannel
) {
5120 mDecoder
->Suspend();
5124 SetPlayedOrSeeked(true);
5125 if (!mPausedForInactiveDocumentOrChannel
) {
5133 class HTMLMediaElement::StreamListener
: public MediaStreamListener
5136 StreamListener(HTMLMediaElement
* aElement
, const char* aName
)
5137 : mElement(aElement
)
5138 , mHaveCurrentData(false)
5141 , mPendingNotifyOutput(false)
5147 HTMLMediaElement
* element
= mElement
;
5149 element
->UpdateReadyStateInternal();
5155 MediaDecoderOwner::NextFrameStatus
NextFrameStatus()
5157 if (!mElement
|| !mHaveCurrentData
|| mFinished
) {
5158 return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE
;
5160 return MediaDecoderOwner::NEXT_FRAME_AVAILABLE
;
5163 void DoNotifyOutput()
5166 MutexAutoLock
lock(mMutex
);
5167 mPendingNotifyOutput
= false;
5169 if (mElement
&& mHaveCurrentData
) {
5170 RefPtr
<HTMLMediaElement
> kungFuDeathGrip
= mElement
;
5171 kungFuDeathGrip
->FireTimeUpdate(true);
5174 void DoNotifyHaveCurrentData()
5176 mHaveCurrentData
= true;
5178 RefPtr
<HTMLMediaElement
> kungFuDeathGrip
= mElement
;
5179 kungFuDeathGrip
->FirstFrameLoaded();
5180 kungFuDeathGrip
->UpdateReadyStateInternal();
5185 // These notifications run on the media graph thread so we need to
5186 // dispatch events to the main thread.
5187 virtual void NotifyHasCurrentData(MediaStreamGraph
* aGraph
) override
5189 MutexAutoLock
lock(mMutex
);
5190 aGraph
->DispatchToMainThreadAfterStreamStateUpdate(NewRunnableMethod(
5191 "dom::HTMLMediaElement::StreamListener::DoNotifyHaveCurrentData",
5193 &StreamListener::DoNotifyHaveCurrentData
));
5195 virtual void NotifyOutput(MediaStreamGraph
* aGraph
,
5196 GraphTime aCurrentTime
) override
5198 MutexAutoLock
lock(mMutex
);
5199 if (mPendingNotifyOutput
)
5201 mPendingNotifyOutput
= true;
5202 aGraph
->DispatchToMainThreadAfterStreamStateUpdate(
5203 NewRunnableMethod("dom::HTMLMediaElement::StreamListener::DoNotifyOutput",
5205 &StreamListener::DoNotifyOutput
));
5209 // These fields may only be accessed on the main thread
5210 HTMLMediaElement
* mElement
;
5211 bool mHaveCurrentData
;
5214 // mMutex protects the fields below; they can be accessed on any thread
5216 bool mPendingNotifyOutput
;
5219 class HTMLMediaElement::MediaStreamTracksAvailableCallback
5220 : public OnTracksAvailableCallback
5223 explicit MediaStreamTracksAvailableCallback(HTMLMediaElement
* aElement
)
5224 : OnTracksAvailableCallback()
5225 , mElement(aElement
)
5228 virtual void NotifyTracksAvailable(DOMMediaStream
* aStream
) override
5230 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
5235 mElement
->NotifyMediaStreamTracksAvailable(aStream
);
5239 WeakPtr
<HTMLMediaElement
> mElement
;
5242 class HTMLMediaElement::MediaStreamTrackListener
5243 : public DOMMediaStream::TrackListener
5246 explicit MediaStreamTrackListener(HTMLMediaElement
* aElement
)
5247 : mElement(aElement
)
5251 void NotifyTrackAdded(const RefPtr
<MediaStreamTrack
>& aTrack
) override
5253 mElement
->NotifyMediaStreamTrackAdded(aTrack
);
5256 void NotifyTrackRemoved(const RefPtr
<MediaStreamTrack
>& aTrack
) override
5258 mElement
->NotifyMediaStreamTrackRemoved(aTrack
);
5261 void NotifyActive() override
5263 LOG(LogLevel::Debug
,
5264 ("%p, mSrcStream %p became active",
5266 mElement
->mSrcStream
.get()));
5267 mElement
->CheckAutoplayDataReady();
5270 void NotifyInactive() override
5272 LOG(LogLevel::Debug
,
5273 ("%p, mSrcStream %p became inactive",
5275 mElement
->mSrcStream
.get()));
5276 MOZ_ASSERT(!mElement
->mSrcStream
->Active());
5277 if (mElement
->mMediaStreamListener
) {
5278 mElement
->mMediaStreamListener
->Forget();
5280 mElement
->PlaybackEnded();
5284 HTMLMediaElement
* const mElement
;
5288 HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags
)
5293 // We might be in cycle collection with mSrcStream->GetPlaybackStream()
5294 // already returning null due to unlinking.
5296 MediaStream
* stream
= GetSrcMediaStream();
5297 bool shouldPlay
= !(aFlags
& REMOVING_SRC_STREAM
) && !mPaused
&&
5298 !mPausedForInactiveDocumentOrChannel
&& stream
;
5299 if (shouldPlay
== mSrcStreamIsPlaying
) {
5302 mSrcStreamIsPlaying
= shouldPlay
;
5304 LOG(LogLevel::Debug
,
5305 ("MediaElement %p %s playback of DOMMediaStream %p",
5307 shouldPlay
? "Setting up" : "Removing",
5311 mSrcStreamPausedCurrentTime
= -1;
5313 mMediaStreamListener
=
5314 new StreamListener(this, "HTMLMediaElement::mMediaStreamListener");
5315 stream
->AddListener(mMediaStreamListener
);
5317 stream
->AddAudioOutput(this);
5318 SetVolumeInternal();
5320 VideoFrameContainer
* container
= GetVideoFrameContainer();
5321 if (mSelectedVideoStreamTrack
&& container
) {
5322 mSelectedVideoStreamTrack
->AddVideoOutput(container
);
5325 SetCapturedOutputStreamsEnabled(true); // Unmute
5326 // If the input is a media stream, we don't check its data and always regard
5327 // it as audible when it's playing.
5328 SetAudibleState(true);
5331 mSrcStreamPausedCurrentTime
= CurrentTime();
5333 stream
->RemoveListener(mMediaStreamListener
);
5335 stream
->RemoveAudioOutput(this);
5336 VideoFrameContainer
* container
= GetVideoFrameContainer();
5337 if (mSelectedVideoStreamTrack
&& container
) {
5338 mSelectedVideoStreamTrack
->RemoveVideoOutput(container
);
5341 SetCapturedOutputStreamsEnabled(false); // Mute
5343 // If stream is null, then DOMMediaStream::Destroy must have been
5344 // called and that will remove all listeners/outputs.
5346 mMediaStreamListener
->Forget();
5347 mMediaStreamListener
= nullptr;
5352 HTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream
* aStream
)
5354 NS_ASSERTION(!mSrcStream
&& !mMediaStreamListener
&&
5355 !mMediaStreamSizeListener
,
5356 "Should have been ended already");
5358 mSrcStream
= aStream
;
5360 nsPIDOMWindowInner
* window
= OwnerDoc()->GetInnerWindow();
5365 UpdateSrcMediaStreamPlaying();
5367 // If we pause this media element, track changes in the underlying stream
5368 // will continue to fire events at this element and alter its track list.
5369 // That's simpler than delaying the events, but probably confusing...
5370 nsTArray
<RefPtr
<MediaStreamTrack
>> tracks
;
5371 mSrcStream
->GetTracks(tracks
);
5372 for (const RefPtr
<MediaStreamTrack
>& track
: tracks
) {
5373 NotifyMediaStreamTrackAdded(track
);
5376 mSrcStream
->OnTracksAvailable(new MediaStreamTracksAvailableCallback(this));
5377 mMediaStreamTrackListener
= new MediaStreamTrackListener(this);
5378 mSrcStream
->RegisterTrackListener(mMediaStreamTrackListener
);
5380 mSrcStream
->AddPrincipalChangeObserver(this);
5381 mSrcStreamVideoPrincipal
= mSrcStream
->GetVideoPrincipal();
5383 ChangeNetworkState(NETWORK_IDLE
);
5384 ChangeDelayLoadStatus(false);
5385 CheckAutoplayDataReady();
5387 // FirstFrameLoaded() will be called when the stream has current data.
5391 HTMLMediaElement::EndSrcMediaStreamPlayback()
5393 MOZ_ASSERT(mSrcStream
);
5395 UpdateSrcMediaStreamPlaying(REMOVING_SRC_STREAM
);
5397 if (mMediaStreamSizeListener
) {
5398 MOZ_ASSERT(mSelectedVideoStreamTrack
);
5399 if (mSelectedVideoStreamTrack
) {
5400 mSelectedVideoStreamTrack
->RemoveDirectListener(mMediaStreamSizeListener
);
5402 mMediaStreamSizeListener
->Forget();
5404 mSelectedVideoStreamTrack
= nullptr;
5405 mMediaStreamSizeListener
= nullptr;
5407 mSrcStream
->UnregisterTrackListener(mMediaStreamTrackListener
);
5408 mMediaStreamTrackListener
= nullptr;
5409 mSrcStreamTracksAvailable
= false;
5411 mSrcStream
->RemovePrincipalChangeObserver(this);
5412 mSrcStreamVideoPrincipal
= nullptr;
5414 for (OutputMediaStream
& ms
: mOutputStreams
) {
5415 for (auto pair
: ms
.mTrackPorts
) {
5416 pair
.second()->Destroy();
5418 ms
.mTrackPorts
.Clear();
5421 mSrcStream
= nullptr;
5424 static already_AddRefed
<AudioTrack
>
5425 CreateAudioTrack(AudioStreamTrack
* aStreamTrack
, nsIGlobalObject
* aOwnerGlobal
)
5429 aStreamTrack
->GetId(id
);
5430 aStreamTrack
->GetLabel(label
, CallerType::System
);
5432 return MediaTrackList::CreateAudioTrack(
5433 aOwnerGlobal
, id
, NS_LITERAL_STRING("main"), label
, EmptyString(), true);
5436 static already_AddRefed
<VideoTrack
>
5437 CreateVideoTrack(VideoStreamTrack
* aStreamTrack
, nsIGlobalObject
* aOwnerGlobal
)
5441 aStreamTrack
->GetId(id
);
5442 aStreamTrack
->GetLabel(label
, CallerType::System
);
5444 return MediaTrackList::CreateVideoTrack(aOwnerGlobal
,
5446 NS_LITERAL_STRING("main"),
5453 HTMLMediaElement::NotifyMediaStreamTrackAdded(
5454 const RefPtr
<MediaStreamTrack
>& aTrack
)
5458 if (aTrack
->Ended()) {
5466 LOG(LogLevel::Debug
,
5467 ("%p, Adding %sTrack with id %s",
5469 aTrack
->AsAudioStreamTrack() ? "Audio" : "Video",
5470 NS_ConvertUTF16toUTF8(id
).get()));
5473 if (AudioStreamTrack
* t
= aTrack
->AsAudioStreamTrack()) {
5474 RefPtr
<AudioTrack
> audioTrack
=
5475 CreateAudioTrack(t
, AudioTracks()->GetOwnerGlobal());
5476 AudioTracks()->AddTrack(audioTrack
);
5477 } else if (VideoStreamTrack
* t
= aTrack
->AsVideoStreamTrack()) {
5478 // TODO: Fix this per the spec on bug 1273443.
5482 RefPtr
<VideoTrack
> videoTrack
=
5483 CreateVideoTrack(t
, VideoTracks()->GetOwnerGlobal());
5484 VideoTracks()->AddTrack(videoTrack
);
5485 // New MediaStreamTrack added, set the new added video track as selected
5486 // video track when there is no selected track.
5487 if (VideoTracks()->SelectedIndex() == -1) {
5488 MOZ_ASSERT(!mSelectedVideoStreamTrack
);
5489 videoTrack
->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS
);
5493 UpdateReadyStateInternal();
5497 HTMLMediaElement::NotifyMediaStreamTrackRemoved(
5498 const RefPtr
<MediaStreamTrack
>& aTrack
)
5505 LOG(LogLevel::Debug
,
5506 ("%p, Removing %sTrack with id %s",
5508 aTrack
->AsAudioStreamTrack() ? "Audio" : "Video",
5509 NS_ConvertUTF16toUTF8(id
).get()));
5511 if (MediaTrack
* t
= AudioTracks()->GetTrackById(id
)) {
5512 AudioTracks()->RemoveTrack(t
);
5513 } else if (MediaTrack
* t
= VideoTracks()->GetTrackById(id
)) {
5514 VideoTracks()->RemoveTrack(t
);
5516 NS_ASSERTION(aTrack
->AsVideoStreamTrack() && !IsVideo(),
5517 "MediaStreamTrack ended but did not exist in track lists. "
5518 "This is only allowed if a video element ends and we are an "
5525 HTMLMediaElement::ProcessMediaFragmentURI()
5527 nsMediaFragmentURIParser
parser(mLoadingSrc
);
5529 if (mDecoder
&& parser
.HasEndTime()) {
5530 mFragmentEnd
= parser
.GetEndTime();
5533 if (parser
.HasStartTime()) {
5534 SetCurrentTime(parser
.GetStartTime());
5535 mFragmentStart
= parser
.GetStartTime();
5540 HTMLMediaElement::MetadataLoaded(const MediaInfo
* aInfo
,
5541 UniquePtr
<const MetadataTags
> aTags
)
5543 MOZ_ASSERT(NS_IsMainThread());
5545 SetMediaInfo(*aInfo
);
5548 aInfo
->IsEncrypted() || mPendingEncryptedInitData
.IsEncrypted();
5549 mTags
= std::move(aTags
);
5550 mLoadedDataFired
= false;
5551 ChangeReadyState(HAVE_METADATA
);
5553 DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
5554 if (IsVideo() && HasVideo()) {
5555 DispatchAsyncEvent(NS_LITERAL_STRING("resize"));
5557 NS_ASSERTION(!HasVideo() || (mMediaInfo
.mVideo
.mDisplay
.width
> 0 &&
5558 mMediaInfo
.mVideo
.mDisplay
.height
> 0),
5559 "Video resolution must be known on 'loadedmetadata'");
5560 DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
5562 if (mBlockedAsWithoutMetadata
&& !HasAudio()) {
5563 mBlockedAsWithoutMetadata
= false;
5564 ScalarAdd(Telemetry::ScalarID::MEDIA_BLOCKED_NO_METADATA_ENDUP_NO_AUDIO_TRACK
, 1);
5567 if (mDecoder
&& mDecoder
->IsTransportSeekable() &&
5568 mDecoder
->IsMediaSeekable()) {
5569 ProcessMediaFragmentURI();
5570 mDecoder
->SetFragmentEndTime(mFragmentEnd
);
5573 // We only support playback of encrypted content via MSE by default.
5574 if (!mMediaSource
&& Preferences::GetBool("media.eme.mse-only", true)) {
5576 MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR
,
5577 "Encrypted content not supported outside of MSE"));
5581 // Dispatch a distinct 'encrypted' event for each initData we have.
5582 for (const auto& initData
: mPendingEncryptedInitData
.mInitDatas
) {
5583 DispatchEncrypted(initData
.mInitData
, initData
.mType
);
5585 mPendingEncryptedInitData
.Reset();
5588 if (IsVideo() && aInfo
->HasVideo()) {
5589 // We are a video element playing video so update the screen wakelock
5590 NotifyOwnerDocumentActivityChanged();
5593 if (mDefaultPlaybackStartPosition
!= 0.0) {
5594 SetCurrentTime(mDefaultPlaybackStartPosition
);
5595 mDefaultPlaybackStartPosition
= 0.0;
5598 UpdateReadyStateInternal();
5603 for (OutputMediaStream
& ms
: mOutputStreams
) {
5604 for (size_t i
= 0; i
< AudioTracks()->Length(); ++i
) {
5605 AudioTrack
* t
= (*AudioTracks())[i
];
5607 AddCaptureMediaTrackToOutputStream(t
, ms
);
5610 if (IsVideo() && !ms
.mCapturingAudioOnly
) {
5611 // Only add video tracks if we're a video element and the output stream
5613 for (size_t i
= 0; i
< VideoTracks()->Length(); ++i
) {
5614 VideoTrack
* t
= (*VideoTracks())[i
];
5615 if (t
->Selected()) {
5616 AddCaptureMediaTrackToOutputStream(t
, ms
);
5624 HTMLMediaElement::FirstFrameLoaded()
5626 LOG(LogLevel::Debug
,
5627 ("%p, FirstFrameLoaded() mFirstFrameLoaded=%d mWaitingForKey=%d",
5632 NS_ASSERTION(!mSuspendedAfterFirstFrame
, "Should not have already suspended");
5634 if (!mFirstFrameLoaded
) {
5635 mFirstFrameLoaded
= true;
5636 UpdateReadyStateInternal();
5639 ChangeDelayLoadStatus(false);
5641 if (mDecoder
&& mAllowSuspendAfterFirstFrame
&& mPaused
&&
5642 !HasAttr(kNameSpaceID_None
, nsGkAtoms::autoplay
) &&
5643 mPreloadAction
== HTMLMediaElement::PRELOAD_METADATA
) {
5644 mSuspendedAfterFirstFrame
= true;
5645 mDecoder
->Suspend();
5650 HTMLMediaElement::NetworkError(const MediaResult
& aError
)
5652 if (mReadyState
== HAVE_NOTHING
) {
5653 NoSupportedMediaSourceError(aError
.Description());
5655 Error(MEDIA_ERR_NETWORK
);
5660 HTMLMediaElement::DecodeError(const MediaResult
& aError
)
5664 const char16_t
* params
[] = { src
.get() };
5665 ReportLoadError("MediaLoadDecodeError", params
, ArrayLength(params
));
5667 DecoderDoctorDiagnostics diagnostics
;
5668 diagnostics
.StoreDecodeError(OwnerDoc(), aError
, src
, __func__
);
5670 AudioTracks()->EmptyTracks();
5671 VideoTracks()->EmptyTracks();
5672 if (mIsLoadingFromSourceChildren
) {
5673 mErrorSink
->ResetError();
5674 if (mSourceLoadCandidate
) {
5675 DispatchAsyncSourceError(mSourceLoadCandidate
);
5676 QueueLoadFromSourceTask();
5678 NS_WARNING("Should know the source we were loading from!");
5680 } else if (mReadyState
== HAVE_NOTHING
) {
5681 NoSupportedMediaSourceError(aError
.Description());
5683 Error(MEDIA_ERR_DECODE
, aError
.Description());
5688 HTMLMediaElement::DecodeWarning(const MediaResult
& aError
)
5692 DecoderDoctorDiagnostics diagnostics
;
5693 diagnostics
.StoreDecodeWarning(OwnerDoc(), aError
, src
, __func__
);
5697 HTMLMediaElement::HasError() const
5703 HTMLMediaElement::LoadAborted()
5705 Error(MEDIA_ERR_ABORTED
);
5709 HTMLMediaElement::Error(uint16_t aErrorCode
, const nsACString
& aErrorDetails
)
5711 mErrorSink
->SetError(aErrorCode
, aErrorDetails
);
5712 ChangeDelayLoadStatus(false);
5713 UpdateAudioChannelPlayingState();
5717 HTMLMediaElement::PlaybackEnded()
5719 // We changed state which can affect AddRemoveSelfReference
5720 AddRemoveSelfReference();
5722 NS_ASSERTION(!mDecoder
|| mDecoder
->IsEnded(),
5723 "Decoder fired ended, but not in ended state");
5725 // Discard all output streams that have finished now.
5726 for (int32_t i
= mOutputStreams
.Length() - 1; i
>= 0; --i
) {
5727 if (mOutputStreams
[i
].mFinishWhenEnded
) {
5728 LOG(LogLevel::Debug
,
5729 ("Playback ended. Removing output stream %p",
5730 mOutputStreams
[i
].mStream
.get()));
5731 mOutputStreams
.RemoveElementAt(i
);
5736 LOG(LogLevel::Debug
,
5737 ("%p, got duration by reaching the end of the resource", this));
5738 DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
5741 if (HasAttr(kNameSpaceID_None
, nsGkAtoms::loop
)) {
5746 FireTimeUpdate(false);
5753 // A MediaStream that goes from inactive to active shall be eligible for
5754 // autoplay again according to the mediacapture-main spec.
5755 mAutoplaying
= true;
5758 DispatchAsyncEvent(NS_LITERAL_STRING("ended"));
5762 HTMLMediaElement::SeekStarted()
5764 DispatchAsyncEvent(NS_LITERAL_STRING("seeking"));
5768 HTMLMediaElement::SeekCompleted()
5770 mPlayingBeforeSeek
= false;
5771 SetPlayedOrSeeked(true);
5772 if (mTextTrackManager
) {
5773 mTextTrackManager
->DidSeek();
5775 FireTimeUpdate(false);
5776 DispatchAsyncEvent(NS_LITERAL_STRING("seeked"));
5777 // We changed whether we're seeking so we need to AddRemoveSelfReference
5778 AddRemoveSelfReference();
5779 if (mCurrentPlayRangeStart
== -1.0) {
5780 mCurrentPlayRangeStart
= CurrentTime();
5783 // After seeking completed, if the audio track is silent, start another new
5785 mHasAccumulatedSilenceRangeBeforeSeekEnd
= false;
5786 if (IsAudioTrackCurrentlySilent()) {
5787 UpdateAudioTrackSilenceRange(mIsAudioTrackAudible
);
5792 HTMLMediaElement::NotifySuspendedByCache(bool aSuspendedByCache
)
5794 mDownloadSuspendedByCache
= aSuspendedByCache
;
5795 UpdateReadyStateInternal();
5799 HTMLMediaElement::DownloadSuspended()
5801 if (mNetworkState
== NETWORK_LOADING
) {
5802 DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
5804 ChangeNetworkState(NETWORK_IDLE
);
5808 HTMLMediaElement::DownloadResumed()
5810 ChangeNetworkState(NETWORK_LOADING
);
5814 HTMLMediaElement::CheckProgress(bool aHaveNewProgress
)
5816 MOZ_ASSERT(NS_IsMainThread());
5817 MOZ_ASSERT(mNetworkState
== NETWORK_LOADING
);
5819 TimeStamp now
= TimeStamp::NowLoRes();
5821 if (aHaveNewProgress
) {
5825 // If this is the first progress, or PROGRESS_MS has passed since the last
5826 // progress event fired and more data has arrived since then, fire a
5828 NS_ASSERTION((mProgressTime
.IsNull() && !aHaveNewProgress
) ||
5829 !mDataTime
.IsNull(),
5830 "null TimeStamp mDataTime should not be used in comparison");
5831 if (mProgressTime
.IsNull()
5833 : (now
- mProgressTime
>= TimeDuration::FromMilliseconds(PROGRESS_MS
) &&
5834 mDataTime
> mProgressTime
)) {
5835 DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
5836 // Resolution() ensures that future data will have now > mProgressTime,
5837 // and so will trigger another event. mDataTime is not reset because it
5838 // is still required to detect stalled; it is similarly offset by
5839 // resolution to indicate the new data has not yet arrived.
5840 mProgressTime
= now
- TimeDuration::Resolution();
5841 if (mDataTime
> mProgressTime
) {
5842 mDataTime
= mProgressTime
;
5844 if (!mProgressTimer
) {
5845 NS_ASSERTION(aHaveNewProgress
,
5846 "timer dispatched when there was no timer");
5847 // Were stalled. Restart timer.
5848 StartProgressTimer();
5849 if (!mLoadedDataFired
) {
5850 ChangeDelayLoadStatus(true);
5853 // Download statistics may have been updated, force a recheck of the
5855 UpdateReadyStateInternal();
5858 if (now
- mDataTime
>= TimeDuration::FromMilliseconds(STALL_MS
)) {
5859 if (!mMediaSource
) {
5860 DispatchAsyncEvent(NS_LITERAL_STRING("stalled"));
5862 ChangeDelayLoadStatus(false);
5865 NS_ASSERTION(mProgressTimer
, "detected stalled without timer");
5866 // Stop timer events, which prevents repeated stalled events until there
5867 // is more progress.
5871 AddRemoveSelfReference();
5876 HTMLMediaElement::ProgressTimerCallback(nsITimer
* aTimer
, void* aClosure
)
5878 auto decoder
= static_cast<HTMLMediaElement
*>(aClosure
);
5879 decoder
->CheckProgress(false);
5883 HTMLMediaElement::StartProgressTimer()
5885 MOZ_ASSERT(NS_IsMainThread());
5886 MOZ_ASSERT(mNetworkState
== NETWORK_LOADING
);
5887 NS_ASSERTION(!mProgressTimer
, "Already started progress timer.");
5889 NS_NewTimerWithFuncCallback(getter_AddRefs(mProgressTimer
),
5890 ProgressTimerCallback
,
5893 nsITimer::TYPE_REPEATING_SLACK
,
5894 "HTMLMediaElement::ProgressTimerCallback",
5895 mMainThreadEventTarget
);
5899 HTMLMediaElement::StartProgress()
5901 // Record the time now for detecting stalled.
5902 mDataTime
= TimeStamp::NowLoRes();
5903 // Reset mProgressTime so that mDataTime is not indicating bytes received
5904 // after the last progress event.
5905 mProgressTime
= TimeStamp();
5906 StartProgressTimer();
5910 HTMLMediaElement::StopProgress()
5912 MOZ_ASSERT(NS_IsMainThread());
5913 if (!mProgressTimer
) {
5917 mProgressTimer
->Cancel();
5918 mProgressTimer
= nullptr;
5922 HTMLMediaElement::DownloadProgressed()
5924 if (mNetworkState
!= NETWORK_LOADING
) {
5927 CheckProgress(true);
5931 HTMLMediaElement::ShouldCheckAllowOrigin()
5933 return mCORSMode
!= CORS_NONE
;
5937 HTMLMediaElement::IsCORSSameOrigin()
5940 RefPtr
<nsIPrincipal
> principal
= GetCurrentPrincipal();
5941 return (NS_SUCCEEDED(NodePrincipal()->Subsumes(principal
, &subsumes
)) &&
5943 ShouldCheckAllowOrigin();
5947 HTMLMediaElement::UpdateReadyStateInternal()
5949 if (!mDecoder
&& !mSrcStream
) {
5950 // Not initialized - bail out.
5951 LOG(LogLevel::Debug
,
5952 ("MediaElement %p UpdateReadyStateInternal() "
5958 if (mDecoder
&& mReadyState
< HAVE_METADATA
) {
5959 // aNextFrame might have a next frame because the decoder can advance
5960 // on its own thread before MetadataLoaded gets a chance to run.
5961 // The arrival of more data can't change us out of this readyState.
5962 LOG(LogLevel::Debug
,
5963 ("MediaElement %p UpdateReadyStateInternal() "
5964 "Decoder ready state < HAVE_METADATA",
5969 if (mSrcStream
&& mReadyState
< HAVE_METADATA
) {
5970 if (!mSrcStreamTracksAvailable
) {
5971 LOG(LogLevel::Debug
,
5972 ("MediaElement %p UpdateReadyStateInternal() "
5973 "MediaStreamTracks not available yet",
5978 bool hasAudioTracks
= !AudioTracks()->IsEmpty();
5979 bool hasVideoTracks
= !VideoTracks()->IsEmpty();
5980 if (!hasAudioTracks
&& !hasVideoTracks
) {
5981 LOG(LogLevel::Debug
,
5982 ("MediaElement %p UpdateReadyStateInternal() "
5983 "Stream with no tracks",
5988 if (IsVideo() && hasVideoTracks
&& !HasVideo()) {
5989 LOG(LogLevel::Debug
,
5990 ("MediaElement %p UpdateReadyStateInternal() "
5991 "Stream waiting for video",
5996 LOG(LogLevel::Debug
,
5997 ("MediaElement %p UpdateReadyStateInternal() Stream has "
5998 "metadata; audioTracks=%d, videoTracks=%d, "
6001 AudioTracks()->Length(),
6002 VideoTracks()->Length(),
6005 // We are playing a stream that has video and a video frame is now set.
6006 // This means we have all metadata needed to change ready state.
6007 MediaInfo mediaInfo
= mMediaInfo
;
6008 if (hasAudioTracks
) {
6009 mediaInfo
.EnableAudio();
6011 if (hasVideoTracks
) {
6012 mediaInfo
.EnableVideo();
6014 MetadataLoaded(&mediaInfo
, nullptr);
6018 // readyState has changed, assuming it's following the pending mediasource
6019 // operations. Notify the Mediasource that the operations have completed.
6020 mMediaSource
->CompletePendingTransactions();
6023 enum NextFrameStatus nextFrameStatus
= NextFrameStatus();
6024 if (mWaitingForKey
== NOT_WAITING_FOR_KEY
) {
6025 if (nextFrameStatus
== NEXT_FRAME_UNAVAILABLE
&& mDecoder
&&
6026 !mDecoder
->IsEnded()) {
6027 nextFrameStatus
= mDecoder
->NextFrameBufferedStatus();
6029 } else if (mWaitingForKey
== WAITING_FOR_KEY
) {
6030 if (nextFrameStatus
== NEXT_FRAME_UNAVAILABLE
||
6031 nextFrameStatus
== NEXT_FRAME_UNAVAILABLE_BUFFERING
) {
6032 // http://w3c.github.io/encrypted-media/#wait-for-key
6033 // Continuing 7.3.4 Queue a "waitingforkey" Event
6034 // 4. Queue a task to fire a simple event named waitingforkey
6035 // at the media element.
6036 // 5. Set the readyState of media element to HAVE_METADATA.
6037 // NOTE: We'll change to HAVE_CURRENT_DATA or HAVE_METADATA
6038 // depending on whether we've loaded the first frame or not
6040 // 6. Suspend playback.
6041 // Note: Playback will already be stalled, as the next frame is
6043 mWaitingForKey
= WAITING_FOR_KEY_DISPATCHED
;
6044 DispatchAsyncEvent(NS_LITERAL_STRING("waitingforkey"));
6047 MOZ_ASSERT(mWaitingForKey
== WAITING_FOR_KEY_DISPATCHED
);
6048 if (nextFrameStatus
== NEXT_FRAME_AVAILABLE
) {
6049 // We have new frames after dispatching "waitingforkey".
6050 // This means we've got the key and can reset mWaitingForKey now.
6051 mWaitingForKey
= NOT_WAITING_FOR_KEY
;
6055 if (nextFrameStatus
== MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING
) {
6056 LOG(LogLevel::Debug
,
6057 ("MediaElement %p UpdateReadyStateInternal() "
6058 "NEXT_FRAME_UNAVAILABLE_SEEKING; Forcing HAVE_METADATA",
6060 ChangeReadyState(HAVE_METADATA
);
6064 if (IsVideo() && HasVideo() && !IsPlaybackEnded() && GetImageContainer() &&
6065 !GetImageContainer()->HasCurrentImage()) {
6066 // Don't advance if we are playing video, but don't have a video frame.
6067 // Also, if video became available after advancing to HAVE_CURRENT_DATA
6068 // while we are still playing, we need to revert to HAVE_METADATA until
6069 // a video frame is available.
6070 LOG(LogLevel::Debug
,
6071 ("MediaElement %p UpdateReadyStateInternal() "
6072 "Playing video but no video frame; Forcing HAVE_METADATA",
6074 ChangeReadyState(HAVE_METADATA
);
6078 if (!mFirstFrameLoaded
) {
6079 // We haven't yet loaded the first frame, making us unable to determine
6080 // if we have enough valid data at the present stage.
6084 if (nextFrameStatus
== NEXT_FRAME_UNAVAILABLE_BUFFERING
) {
6085 // Force HAVE_CURRENT_DATA when buffering.
6086 ChangeReadyState(HAVE_CURRENT_DATA
);
6090 // TextTracks must be loaded for the HAVE_ENOUGH_DATA and
6091 // HAVE_FUTURE_DATA.
6092 // So force HAVE_CURRENT_DATA if text tracks not loaded.
6093 if (mTextTrackManager
&& !mTextTrackManager
->IsLoaded()) {
6094 ChangeReadyState(HAVE_CURRENT_DATA
);
6098 if (mDownloadSuspendedByCache
&& mDecoder
&& !mDecoder
->IsEnded()) {
6099 // The decoder has signaled that the download has been suspended by the
6100 // media cache. So move readyState into HAVE_ENOUGH_DATA, in case there's
6101 // script waiting for a "canplaythrough" event; without this forced
6102 // transition, we will never fire the "canplaythrough" event if the
6103 // media cache is too small, and scripts are bound to fail. Don't force
6104 // this transition if the decoder is in ended state; the readyState
6105 // should remain at HAVE_CURRENT_DATA in this case.
6106 // Note that this state transition includes the case where we finished
6107 // downloaded the whole data stream.
6108 LOG(LogLevel::Debug
,
6109 ("MediaElement %p UpdateReadyStateInternal() "
6110 "Decoder download suspended by cache",
6112 ChangeReadyState(HAVE_ENOUGH_DATA
);
6116 if (nextFrameStatus
!= MediaDecoderOwner::NEXT_FRAME_AVAILABLE
) {
6117 LOG(LogLevel::Debug
,
6118 ("MediaElement %p UpdateReadyStateInternal() "
6119 "Next frame not available",
6121 ChangeReadyState(HAVE_CURRENT_DATA
);
6126 LOG(LogLevel::Debug
,
6127 ("MediaElement %p UpdateReadyStateInternal() "
6128 "Stream HAVE_ENOUGH_DATA",
6130 ChangeReadyState(HAVE_ENOUGH_DATA
);
6134 // Now see if we should set HAVE_ENOUGH_DATA.
6135 // If it's something we don't know the size of, then we can't
6136 // make a real estimate, so we go straight to HAVE_ENOUGH_DATA once
6137 // we've downloaded enough data that our download rate is considered
6138 // reliable. We have to move to HAVE_ENOUGH_DATA at some point or
6139 // autoplay elements for live streams will never play. Otherwise we
6140 // move to HAVE_ENOUGH_DATA if we can play through the entire media
6141 // without stopping to buffer.
6142 if (mDecoder
->CanPlayThrough()) {
6143 LOG(LogLevel::Debug
,
6144 ("MediaElement %p UpdateReadyStateInternal() "
6145 "Decoder can play through",
6147 ChangeReadyState(HAVE_ENOUGH_DATA
);
6150 LOG(LogLevel::Debug
,
6151 ("MediaElement %p UpdateReadyStateInternal() "
6152 "Default; Decoder has future data",
6154 ChangeReadyState(HAVE_FUTURE_DATA
);
6157 static const char* const gReadyStateToString
[] = { "HAVE_NOTHING",
6159 "HAVE_CURRENT_DATA",
6161 "HAVE_ENOUGH_DATA" };
6164 HTMLMediaElement::ChangeReadyState(nsMediaReadyState aState
)
6166 if (mReadyState
== aState
) {
6170 nsMediaReadyState oldState
= mReadyState
;
6171 mReadyState
= aState
;
6172 LOG(LogLevel::Debug
,
6173 ("%p Ready state changed to %s", this, gReadyStateToString
[aState
]));
6175 DDLOG(DDLogCategory::Property
, "ready_state", gReadyStateToString
[aState
]);
6177 if (mNetworkState
== NETWORK_EMPTY
) {
6181 UpdateAudioChannelPlayingState();
6183 // Handle raising of "waiting" event during seek (see 4.8.10.9)
6185 // 4.8.12.7 Ready states:
6186 // "If the previous ready state was HAVE_FUTURE_DATA or more, and the new
6187 // ready state is HAVE_CURRENT_DATA or less
6188 // If the media element was potentially playing before its readyState
6189 // attribute changed to a value lower than HAVE_FUTURE_DATA, and the element
6190 // has not ended playback, and playback has not stopped due to errors,
6191 // paused for user interaction, or paused for in-band content, the user agent
6192 // must queue a task to fire a simple event named timeupdate at the element,
6193 // and queue a task to fire a simple event named waiting at the element."
6194 if (mPlayingBeforeSeek
&& mReadyState
< HAVE_FUTURE_DATA
) {
6195 DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
6196 } else if (oldState
>= HAVE_FUTURE_DATA
&& mReadyState
< HAVE_FUTURE_DATA
&&
6197 !Paused() && !Ended() && !mErrorSink
->mError
) {
6198 FireTimeUpdate(false);
6199 DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
6202 if (oldState
< HAVE_CURRENT_DATA
&& mReadyState
>= HAVE_CURRENT_DATA
&&
6203 !mLoadedDataFired
) {
6204 DispatchAsyncEvent(NS_LITERAL_STRING("loadeddata"));
6205 mLoadedDataFired
= true;
6208 if (oldState
< HAVE_FUTURE_DATA
&& mReadyState
>= HAVE_FUTURE_DATA
) {
6209 DispatchAsyncEvent(NS_LITERAL_STRING("canplay"));
6211 if (mDecoder
&& !mPausedForInactiveDocumentOrChannel
) {
6212 MOZ_ASSERT(AutoplayPolicy::IsAllowedToPlay(*this));
6215 NotifyAboutPlaying();
6219 CheckAutoplayDataReady();
6221 if (oldState
< HAVE_ENOUGH_DATA
&& mReadyState
>= HAVE_ENOUGH_DATA
) {
6222 DispatchAsyncEvent(NS_LITERAL_STRING("canplaythrough"));
6226 static const char* const gNetworkStateToString
[] = { "EMPTY",
6232 HTMLMediaElement::ChangeNetworkState(nsMediaNetworkState aState
)
6234 if (mNetworkState
== aState
) {
6238 nsMediaNetworkState oldState
= mNetworkState
;
6239 mNetworkState
= aState
;
6240 LOG(LogLevel::Debug
,
6241 ("%p Network state changed to %s", this, gNetworkStateToString
[aState
]));
6243 DDLogCategory::Property
, "network_state", gNetworkStateToString
[aState
]);
6245 if (oldState
== NETWORK_LOADING
) {
6246 // Stop progress notification when exiting NETWORK_LOADING.
6250 if (mNetworkState
== NETWORK_LOADING
) {
6251 // Start progress notification when entering NETWORK_LOADING.
6253 } else if (mNetworkState
== NETWORK_IDLE
&& !mErrorSink
->mError
) {
6254 // Fire 'suspend' event when entering NETWORK_IDLE and no error presented.
6255 DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
6258 // Changing mNetworkState affects AddRemoveSelfReference().
6259 AddRemoveSelfReference();
6263 HTMLMediaElement::CanActivateAutoplay()
6265 // For stream inputs, we activate autoplay on HAVE_NOTHING because
6266 // this element itself might be blocking the stream from making progress by
6267 // being paused. We only check that it has data by checking its active state.
6268 // We also activate autoplay when playing a media source since the data
6269 // download is controlled by the script and there is no way to evaluate
6270 // MediaDecoder::CanPlayThrough().
6272 if (!HasAttr(kNameSpaceID_None
, nsGkAtoms::autoplay
)) {
6276 if (!mAutoplaying
) {
6288 if (mPausedForInactiveDocumentOrChannel
) {
6292 // Static document is used for print preview and printing, should not be
6294 if (OwnerDoc()->IsStaticDocument()) {
6298 if (mAudioChannelWrapper
) {
6299 // Note: SUSPENDED_PAUSE and SUSPENDED_BLOCK will be merged into one single
6301 if (mAudioChannelWrapper
->GetSuspendType() ==
6302 nsISuspendedTypes::SUSPENDED_PAUSE
||
6303 mAudioChannelWrapper
->GetSuspendType() ==
6304 nsISuspendedTypes::SUSPENDED_BLOCK
||
6305 mAudioChannelWrapper
->IsPlaybackBlocked()) {
6310 bool hasData
= (mDecoder
&& mReadyState
>= HAVE_ENOUGH_DATA
) ||
6311 (mSrcStream
&& mSrcStream
->Active());
6317 HTMLMediaElement::CheckAutoplayDataReady()
6319 if (!CanActivateAutoplay()) {
6323 UpdateHadAudibleAutoplayState();
6324 if (!AutoplayPolicy::IsAllowedToPlay(*this)) {
6325 EnsureAutoplayRequested(false);
6330 // We changed mPaused which can affect AddRemoveSelfReference
6331 AddRemoveSelfReference();
6332 UpdateSrcMediaStreamPlaying();
6333 UpdateAudioChannelPlayingState();
6336 SetPlayedOrSeeked(true);
6337 if (mCurrentPlayRangeStart
== -1.0) {
6338 mCurrentPlayRangeStart
= CurrentTime();
6340 MOZ_ASSERT(!mPausedForInactiveDocumentOrChannel
);
6342 } else if (mSrcStream
) {
6343 SetPlayedOrSeeked(true);
6346 // For blocked media, the event would be pending until it is resumed.
6347 DispatchAsyncEvent(NS_LITERAL_STRING("play"));
6349 DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
6353 HTMLMediaElement::IsActive() const
6355 nsIDocument
* ownerDoc
= OwnerDoc();
6356 return ownerDoc
&& ownerDoc
->IsActive() && ownerDoc
->IsVisible();
6360 HTMLMediaElement::IsHidden() const
6362 nsIDocument
* ownerDoc
;
6363 return mUnboundFromTree
|| !(ownerDoc
= OwnerDoc()) || ownerDoc
->Hidden();
6366 VideoFrameContainer
*
6367 HTMLMediaElement::GetVideoFrameContainer()
6369 if (mShuttingDown
) {
6373 if (mVideoFrameContainer
)
6374 return mVideoFrameContainer
;
6376 // Only video frames need an image container.
6381 mVideoFrameContainer
= new VideoFrameContainer(
6382 this, LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS
));
6384 return mVideoFrameContainer
;
6388 HTMLMediaElement::PrincipalChanged(DOMMediaStream
* aStream
)
6390 LOG(LogLevel::Info
, ("HTMLMediaElement %p Stream principal changed.", this));
6391 nsContentUtils::CombineResourcePrincipals(&mSrcStreamVideoPrincipal
,
6392 aStream
->GetVideoPrincipal());
6394 LOG(LogLevel::Debug
,
6395 ("HTMLMediaElement %p Stream video principal changed to "
6396 "%p. Waiting for it to reach VideoFrameContainer before "
6399 aStream
->GetVideoPrincipal()));
6400 if (mVideoFrameContainer
) {
6401 UpdateSrcStreamVideoPrincipal(
6402 mVideoFrameContainer
->GetLastPrincipalHandle());
6407 HTMLMediaElement::UpdateSrcStreamVideoPrincipal(
6408 const PrincipalHandle
& aPrincipalHandle
)
6410 nsTArray
<RefPtr
<VideoStreamTrack
>> videoTracks
;
6411 mSrcStream
->GetVideoTracks(videoTracks
);
6413 PrincipalHandle
handle(aPrincipalHandle
);
6414 bool matchesTrackPrincipal
= false;
6415 for (const RefPtr
<VideoStreamTrack
>& track
: videoTracks
) {
6416 if (PrincipalHandleMatches(handle
, track
->GetPrincipal()) &&
6418 // When the PrincipalHandle for the VideoFrameContainer changes to that of
6419 // a track in mSrcStream we know that a removed track was displayed but
6421 matchesTrackPrincipal
= true;
6422 LOG(LogLevel::Debug
,
6423 ("HTMLMediaElement %p VideoFrameContainer's "
6424 "PrincipalHandle matches track %p. That's all we "
6432 if (matchesTrackPrincipal
) {
6433 mSrcStreamVideoPrincipal
= mSrcStream
->GetVideoPrincipal();
6438 HTMLMediaElement::PrincipalHandleChangedForVideoFrameContainer(
6439 VideoFrameContainer
* aContainer
,
6440 const PrincipalHandle
& aNewPrincipalHandle
)
6442 MOZ_ASSERT(NS_IsMainThread());
6448 LOG(LogLevel::Debug
,
6449 ("HTMLMediaElement %p PrincipalHandle changed in "
6450 "VideoFrameContainer.",
6453 UpdateSrcStreamVideoPrincipal(aNewPrincipalHandle
);
6457 HTMLMediaElement::DispatchEvent(const nsAString
& aName
)
6461 ("%p Dispatching event %s", this, NS_ConvertUTF16toUTF8(aName
).get()));
6463 // Save events that occur while in the bfcache. These will be dispatched
6464 // if the page comes out of the bfcache.
6465 if (mEventDeliveryPaused
) {
6466 mPendingEvents
.AppendElement(aName
);
6470 return nsContentUtils::DispatchTrustedEvent(
6471 OwnerDoc(), static_cast<nsIContent
*>(this), aName
,
6477 HTMLMediaElement::DispatchAsyncEvent(const nsAString
& aName
)
6479 LOG_EVENT(LogLevel::Debug
,
6480 ("%p Queuing event %s", this, NS_ConvertUTF16toUTF8(aName
).get()));
6481 DDLOG(DDLogCategory::Event
,
6483 nsCString(NS_ConvertUTF16toUTF8(aName
)));
6485 // Save events that occur while in the bfcache. These will be dispatched
6486 // if the page comes out of the bfcache.
6487 if (mEventDeliveryPaused
) {
6488 mPendingEvents
.AppendElement(aName
);
6492 nsCOMPtr
<nsIRunnable
> event
;
6494 if (aName
.EqualsLiteral("playing")) {
6495 event
= new nsNotifyAboutPlayingRunner(this, TakePendingPlayPromises());
6497 event
= new nsAsyncEventRunner(aName
, this);
6500 mMainThreadEventTarget
->Dispatch(event
.forget());
6502 if ((aName
.EqualsLiteral("play") || aName
.EqualsLiteral("playing"))) {
6507 } else if (aName
.EqualsLiteral("waiting")) {
6510 } else if (aName
.EqualsLiteral("pause")) {
6517 HTMLMediaElement::DispatchPendingMediaEvents()
6519 NS_ASSERTION(!mEventDeliveryPaused
,
6520 "Must not be in bfcache when dispatching pending media events");
6522 uint32_t count
= mPendingEvents
.Length();
6523 for (uint32_t i
= 0; i
< count
; ++i
) {
6524 DispatchAsyncEvent(mPendingEvents
[i
]);
6526 mPendingEvents
.Clear();
6532 HTMLMediaElement::IsPotentiallyPlaying() const
6535 // playback has not stopped due to errors,
6536 // and the element has not paused for user interaction
6538 (mReadyState
== HAVE_ENOUGH_DATA
|| mReadyState
== HAVE_FUTURE_DATA
) &&
6543 HTMLMediaElement::IsPlaybackEnded() const
6546 // the current playback position is equal to the effective end of the media
6547 // resource. See bug 449157.
6548 return mReadyState
>= HAVE_METADATA
&& mDecoder
&& mDecoder
->IsEnded();
6551 already_AddRefed
<nsIPrincipal
>
6552 HTMLMediaElement::GetCurrentPrincipal()
6555 return mDecoder
->GetCurrentPrincipal();
6558 nsCOMPtr
<nsIPrincipal
> principal
= mSrcStream
->GetPrincipal();
6559 return principal
.forget();
6564 already_AddRefed
<nsIPrincipal
>
6565 HTMLMediaElement::GetCurrentVideoPrincipal()
6568 return mDecoder
->GetCurrentPrincipal();
6571 nsCOMPtr
<nsIPrincipal
> principal
= mSrcStreamVideoPrincipal
;
6572 return principal
.forget();
6578 HTMLMediaElement::NotifyDecoderPrincipalChanged()
6580 RefPtr
<nsIPrincipal
> principal
= GetCurrentPrincipal();
6582 mDecoder
->UpdateSameOriginStatus(!principal
|| IsCORSSameOrigin());
6584 for (DecoderPrincipalChangeObserver
* observer
:
6585 mDecoderPrincipalChangeObservers
) {
6586 observer
->NotifyDecoderPrincipalChanged();
6591 HTMLMediaElement::AddDecoderPrincipalChangeObserver(
6592 DecoderPrincipalChangeObserver
* aObserver
)
6594 mDecoderPrincipalChangeObservers
.AppendElement(aObserver
);
6598 HTMLMediaElement::RemoveDecoderPrincipalChangeObserver(
6599 DecoderPrincipalChangeObserver
* aObserver
)
6601 return mDecoderPrincipalChangeObservers
.RemoveElement(aObserver
);
6605 HTMLMediaElement::Invalidate(bool aImageSizeChanged
,
6606 Maybe
<nsIntSize
>& aNewIntrinsicSize
,
6607 bool aForceInvalidate
)
6609 nsIFrame
* frame
= GetPrimaryFrame();
6610 if (aNewIntrinsicSize
) {
6611 UpdateMediaSize(aNewIntrinsicSize
.value());
6613 nsPresContext
* presContext
= frame
->PresContext();
6614 nsIPresShell
* presShell
= presContext
->PresShell();
6615 presShell
->FrameNeedsReflow(
6616 frame
, nsIPresShell::eStyleChange
, NS_FRAME_IS_DIRTY
);
6620 RefPtr
<ImageContainer
> imageContainer
= GetImageContainer();
6621 bool asyncInvalidate
=
6622 imageContainer
&& imageContainer
->IsAsync() && !aForceInvalidate
;
6624 if (aImageSizeChanged
) {
6625 frame
->InvalidateFrame();
6627 frame
->InvalidateLayer(DisplayItemType::TYPE_VIDEO
,
6630 asyncInvalidate
? nsIFrame::UPDATE_IS_ASYNC
: 0);
6634 SVGObserverUtils::InvalidateDirectRenderingObservers(this);
6638 HTMLMediaElement::UpdateMediaSize(const nsIntSize
& aSize
)
6640 if (IsVideo() && mReadyState
!= HAVE_NOTHING
&&
6641 mMediaInfo
.mVideo
.mDisplay
!= aSize
) {
6642 DispatchAsyncEvent(NS_LITERAL_STRING("resize"));
6645 mMediaInfo
.mVideo
.mDisplay
= aSize
;
6646 UpdateReadyStateInternal();
6650 HTMLMediaElement::UpdateInitialMediaSize(const nsIntSize
& aSize
)
6652 if (!mMediaInfo
.HasVideo()) {
6653 UpdateMediaSize(aSize
);
6656 if (!mMediaStreamSizeListener
) {
6660 if (!mSelectedVideoStreamTrack
) {
6665 mSelectedVideoStreamTrack
->RemoveDirectListener(mMediaStreamSizeListener
);
6666 mMediaStreamSizeListener
->Forget();
6667 mMediaStreamSizeListener
= nullptr;
6671 HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement
,
6672 bool aSuspendEvents
)
6674 LOG(LogLevel::Debug
,
6675 ("%p SuspendOrResumeElement(pause=%d, suspendEvents=%d) hidden=%d",
6679 OwnerDoc()->Hidden()));
6681 if (aPauseElement
!= mPausedForInactiveDocumentOrChannel
) {
6682 mPausedForInactiveDocumentOrChannel
= aPauseElement
;
6683 UpdateSrcMediaStreamPlaying();
6684 UpdateAudioChannelPlayingState();
6685 if (aPauseElement
) {
6688 // For EME content, we may force destruction of the CDM client (and CDM
6689 // instance if this is the last client for that CDM instance) and
6690 // the CDM's decoder. This ensures the CDM gets reliable and prompt
6691 // shutdown notifications, as it may have book-keeping it needs
6692 // to do on shutdown.
6694 nsAutoString keySystem
;
6695 mMediaKeys
->GetKeySystem(keySystem
);
6699 mDecoder
->Suspend();
6701 mEventDeliveryPaused
= aSuspendEvents
;
6705 if (!mPaused
&& !mDecoder
->IsEnded()) {
6709 if (mEventDeliveryPaused
) {
6710 mEventDeliveryPaused
= false;
6711 DispatchPendingMediaEvents();
6718 HTMLMediaElement::IsBeingDestroyed()
6720 nsIDocument
* ownerDoc
= OwnerDoc();
6721 nsIDocShell
* docShell
= ownerDoc
? ownerDoc
->GetDocShell() : nullptr;
6722 bool isBeingDestroyed
= false;
6724 docShell
->IsBeingDestroyed(&isBeingDestroyed
);
6726 return isBeingDestroyed
;
6730 HTMLMediaElement::NotifyOwnerDocumentActivityChanged()
6732 bool visible
= !IsHidden();
6734 // Visible -> Just pause hidden play time (no-op if already paused).
6736 } else if (mPlayTime
.IsStarted()) {
6737 // Not visible, play time is running -> Start hidden play time if needed.
6741 if (mDecoder
&& !IsBeingDestroyed()) {
6742 NotifyDecoderActivityChanges();
6745 bool pauseElement
= ShouldElementBePaused();
6746 SuspendOrResumeElement(pauseElement
, !IsActive());
6748 // If the owning document has become inactive we should shutdown the CDM.
6749 if (!OwnerDoc()->IsCurrentActiveDocument() && mMediaKeys
) {
6750 mMediaKeys
->Shutdown();
6751 DDUNLINKCHILD(mMediaKeys
.get());
6752 mMediaKeys
= nullptr;
6758 AddRemoveSelfReference();
6762 HTMLMediaElement::AddRemoveSelfReference()
6764 // XXX we could release earlier here in many situations if we examined
6765 // which event listeners are attached. Right now we assume there is a
6766 // potential listener for every event. We would also have to keep the
6767 // element alive if it was playing and producing audio output --- right now
6768 // that's covered by the !mPaused check.
6769 nsIDocument
* ownerDoc
= OwnerDoc();
6771 // See the comment at the top of this file for the explanation of this
6772 // boolean expression.
6773 bool needSelfReference
=
6774 !mShuttingDown
&& ownerDoc
->IsActive() &&
6775 (mDelayingLoadEvent
|| (!mPaused
&& mDecoder
&& !mDecoder
->IsEnded()) ||
6776 (!mPaused
&& mSrcStream
&& !mSrcStream
->IsFinished()) ||
6777 (mDecoder
&& mDecoder
->IsSeeking()) || CanActivateAutoplay() ||
6778 (mMediaSource
? mProgressTimer
: mNetworkState
== NETWORK_LOADING
));
6780 if (needSelfReference
!= mHasSelfReference
) {
6781 mHasSelfReference
= needSelfReference
;
6782 if (needSelfReference
) {
6783 // The shutdown observer will hold a strong reference to us. This
6784 // will do to keep us alive. We need to know about shutdown so that
6785 // we can release our self-reference.
6786 mShutdownObserver
->AddRefMediaElement();
6788 // Dispatch Release asynchronously so that we don't destroy this object
6789 // inside a call stack of method calls on this object
6790 mMainThreadEventTarget
->Dispatch(
6791 NewRunnableMethod("dom::HTMLMediaElement::DoRemoveSelfReference",
6793 &HTMLMediaElement::DoRemoveSelfReference
));
6799 HTMLMediaElement::DoRemoveSelfReference()
6801 mShutdownObserver
->ReleaseMediaElement();
6805 HTMLMediaElement::NotifyShutdownEvent()
6807 mShuttingDown
= true;
6808 // Since target thread had been shutdown, it's no chance to execute the Then()
6809 // afterward. Therefore, we should disconnect the request.
6810 mAutoplayPermissionRequest
.DisconnectIfExists();
6812 AddRemoveSelfReference();
6816 HTMLMediaElement::DispatchAsyncSourceError(nsIContent
* aSourceElement
)
6818 LOG_EVENT(LogLevel::Debug
, ("%p Queuing simple source error event", this));
6820 nsCOMPtr
<nsIRunnable
> event
=
6821 new nsSourceErrorEventRunner(this, aSourceElement
);
6822 mMainThreadEventTarget
->Dispatch(event
.forget());
6826 HTMLMediaElement::NotifyAddedSource()
6828 // If a source element is inserted as a child of a media element
6829 // that has no src attribute and whose networkState has the value
6830 // NETWORK_EMPTY, the user agent must invoke the media element's
6831 // resource selection algorithm.
6832 if (!HasAttr(kNameSpaceID_None
, nsGkAtoms::src
) &&
6833 mNetworkState
== NETWORK_EMPTY
) {
6834 AssertReadyStateIsNothing();
6835 QueueSelectResourceTask();
6838 // A load was paused in the resource selection algorithm, waiting for
6839 // a new source child to be added, resume the resource selection algorithm.
6840 if (mLoadWaitStatus
== WAITING_FOR_SOURCE
) {
6841 // Rest the flag so we don't queue multiple LoadFromSourceTask() when
6842 // multiple <source> are attached in an event loop.
6843 mLoadWaitStatus
= NOT_WAITING
;
6844 QueueLoadFromSourceTask();
6849 HTMLMediaElement::GetNextSource()
6851 mSourceLoadCandidate
= nullptr;
6854 if (mSourcePointer
== nsINode::GetLastChild()) {
6855 return nullptr; // no more children
6858 if (!mSourcePointer
) {
6859 mSourcePointer
= nsINode::GetFirstChild();
6861 mSourcePointer
= mSourcePointer
->GetNextSibling();
6863 nsIContent
* child
= mSourcePointer
;
6865 // If child is a <source> element, it is the next candidate.
6866 if (child
&& child
->IsHTMLElement(nsGkAtoms::source
)) {
6867 mSourceLoadCandidate
= child
;
6868 return child
->AsElement();
6871 MOZ_ASSERT_UNREACHABLE("Execution should not reach here!");
6876 HTMLMediaElement::ChangeDelayLoadStatus(bool aDelay
)
6878 if (mDelayingLoadEvent
== aDelay
)
6881 mDelayingLoadEvent
= aDelay
;
6883 LOG(LogLevel::Debug
,
6884 ("%p ChangeDelayLoadStatus(%d) doc=0x%p",
6887 mLoadBlockedDoc
.get()));
6889 mDecoder
->SetLoadInBackground(!aDelay
);
6892 mLoadBlockedDoc
= OwnerDoc();
6893 mLoadBlockedDoc
->BlockOnload();
6895 // mLoadBlockedDoc might be null due to GC unlinking
6896 if (mLoadBlockedDoc
) {
6897 mLoadBlockedDoc
->UnblockOnload(false);
6898 mLoadBlockedDoc
= nullptr;
6902 // We changed mDelayingLoadEvent which can affect AddRemoveSelfReference
6903 AddRemoveSelfReference();
6906 already_AddRefed
<nsILoadGroup
>
6907 HTMLMediaElement::GetDocumentLoadGroup()
6909 if (!OwnerDoc()->IsActive()) {
6910 NS_WARNING("Load group requested for media element in inactive document.");
6912 return OwnerDoc()->GetDocumentLoadGroup();
6916 HTMLMediaElement::CopyInnerTo(Element
* aDest
)
6918 nsresult rv
= nsGenericHTMLElement::CopyInnerTo(aDest
);
6919 NS_ENSURE_SUCCESS(rv
, rv
);
6920 if (aDest
->OwnerDoc()->IsStaticDocument()) {
6921 HTMLMediaElement
* dest
= static_cast<HTMLMediaElement
*>(aDest
);
6922 dest
->SetMediaInfo(mMediaInfo
);
6927 already_AddRefed
<TimeRanges
>
6928 HTMLMediaElement::Buffered() const
6930 media::TimeIntervals buffered
=
6931 mDecoder
? mDecoder
->GetBuffered() : media::TimeIntervals();
6932 RefPtr
<TimeRanges
> ranges
= new TimeRanges(ToSupports(OwnerDoc()), buffered
);
6933 return ranges
.forget();
6937 HTMLMediaElement::SetRequestHeaders(nsIHttpChannel
* aChannel
)
6939 // Send Accept header for video and audio types only (Bug 489071)
6940 SetAcceptHeader(aChannel
);
6942 // Apache doesn't send Content-Length when gzip transfer encoding is used,
6943 // which prevents us from estimating the video length (if explicit
6944 // Content-Duration and a length spec in the container are not present either)
6945 // and from seeking. So, disable the standard "Accept-Encoding: gzip,deflate"
6946 // that we usually send. See bug 614760.
6947 DebugOnly
<nsresult
> rv
= aChannel
->SetRequestHeader(
6948 NS_LITERAL_CSTRING("Accept-Encoding"), EmptyCString(), false);
6949 MOZ_ASSERT(NS_SUCCEEDED(rv
));
6951 // Set the Referer header
6952 rv
= aChannel
->SetReferrerWithPolicy(OwnerDoc()->GetDocumentURI(),
6953 OwnerDoc()->GetReferrerPolicy());
6954 MOZ_ASSERT(NS_SUCCEEDED(rv
));
6958 HTMLMediaElement::FireTimeUpdate(bool aPeriodic
)
6960 NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
6962 TimeStamp now
= TimeStamp::Now();
6963 double time
= CurrentTime();
6965 // Fire a timeupdate event if this is not a periodic update (i.e. it's a
6966 // timeupdate event mandated by the spec), or if it's a periodic update
6967 // and TIMEUPDATE_MS has passed since the last timeupdate event fired and
6968 // the time has changed.
6969 if (!aPeriodic
|| (mLastCurrentTime
!= time
&&
6970 (mTimeUpdateTime
.IsNull() ||
6971 now
- mTimeUpdateTime
>=
6972 TimeDuration::FromMilliseconds(TIMEUPDATE_MS
)))) {
6973 DispatchAsyncEvent(NS_LITERAL_STRING("timeupdate"));
6974 mTimeUpdateTime
= now
;
6975 mLastCurrentTime
= time
;
6977 if (mFragmentEnd
>= 0.0 && time
>= mFragmentEnd
) {
6979 mFragmentEnd
= -1.0;
6980 mFragmentStart
= -1.0;
6981 mDecoder
->SetFragmentEndTime(mFragmentEnd
);
6984 // Update the cues displaying on the video.
6985 // Here mTextTrackManager can be null if the cycle collector has unlinked
6986 // us before our parent. In that case UnbindFromTree will call us
6987 // when our parent is unlinked.
6988 if (mTextTrackManager
) {
6989 mTextTrackManager
->TimeMarchesOn();
6994 HTMLMediaElement::GetSrcMediaStream() const
6999 return mSrcStream
->GetPlaybackStream();
7003 HTMLMediaElement::GetError() const
7005 return mErrorSink
->mError
;
7009 HTMLMediaElement::GetCurrentSpec(nsCString
& aString
)
7012 mLoadingSrc
->GetSpec(aString
);
7019 HTMLMediaElement::MozFragmentEnd()
7021 double duration
= Duration();
7023 // If there is no end fragment, or the fragment end is greater than the
7024 // duration, return the duration.
7025 return (mFragmentEnd
< 0.0 || mFragmentEnd
> duration
) ? duration
7030 ClampPlaybackRate(double aPlaybackRate
)
7032 MOZ_ASSERT(aPlaybackRate
>= 0.0);
7034 if (aPlaybackRate
== 0.0) {
7035 return aPlaybackRate
;
7037 if (aPlaybackRate
< MIN_PLAYBACKRATE
) {
7038 return MIN_PLAYBACKRATE
;
7040 if (aPlaybackRate
> MAX_PLAYBACKRATE
) {
7041 return MAX_PLAYBACKRATE
;
7043 return aPlaybackRate
;
7047 HTMLMediaElement::SetDefaultPlaybackRate(double aDefaultPlaybackRate
,
7050 if (aDefaultPlaybackRate
< 0) {
7051 aRv
.Throw(NS_ERROR_NOT_IMPLEMENTED
);
7055 mDefaultPlaybackRate
= ClampPlaybackRate(aDefaultPlaybackRate
);
7056 DispatchAsyncEvent(NS_LITERAL_STRING("ratechange"));
7060 HTMLMediaElement::SetPlaybackRate(double aPlaybackRate
, ErrorResult
& aRv
)
7062 // Changing the playback rate of a media that has more than two channels is
7064 if (aPlaybackRate
< 0) {
7065 aRv
.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
7069 if (mPlaybackRate
== aPlaybackRate
) {
7073 mPlaybackRate
= aPlaybackRate
;
7075 if (mPlaybackRate
!= 0.0 &&
7076 (mPlaybackRate
> THRESHOLD_HIGH_PLAYBACKRATE_AUDIO
||
7077 mPlaybackRate
< THRESHOLD_LOW_PLAYBACKRATE_AUDIO
)) {
7078 SetMutedInternal(mMuted
| MUTED_BY_INVALID_PLAYBACK_RATE
);
7080 SetMutedInternal(mMuted
& ~MUTED_BY_INVALID_PLAYBACK_RATE
);
7084 mDecoder
->SetPlaybackRate(ClampPlaybackRate(mPlaybackRate
));
7086 DispatchAsyncEvent(NS_LITERAL_STRING("ratechange"));
7090 HTMLMediaElement::SetMozPreservesPitch(bool aPreservesPitch
)
7092 mPreservesPitch
= aPreservesPitch
;
7094 mDecoder
->SetPreservesPitch(mPreservesPitch
);
7099 HTMLMediaElement::GetImageContainer()
7101 VideoFrameContainer
* container
= GetVideoFrameContainer();
7102 return container
? container
->GetImageContainer() : nullptr;
7106 HTMLMediaElement::UpdateAudioChannelPlayingState(bool aForcePlaying
)
7108 if (mAudioChannelWrapper
) {
7109 mAudioChannelWrapper
->UpdateAudioChannelPlayingState(aForcePlaying
);
7114 HTMLMediaElement::AudioChannelAgentBlockedPlay()
7116 if (!mAudioChannelWrapper
) {
7117 // If the mAudioChannelWrapper doesn't exist that means the CC happened.
7118 LOG(LogLevel::Debug
,
7119 ("%p AudioChannelAgentBlockedPlay() returning true due to null "
7120 "AudioChannelAgent.",
7125 // Note: SUSPENDED_PAUSE and SUSPENDED_BLOCK will be merged into one single
7127 const auto suspendType
= mAudioChannelWrapper
->GetSuspendType();
7128 return suspendType
== nsISuspendedTypes::SUSPENDED_PAUSE
||
7129 suspendType
== nsISuspendedTypes::SUSPENDED_BLOCK
;
7133 VisibilityString(Visibility aVisibility
)
7135 switch (aVisibility
) {
7136 case Visibility::UNTRACKED
: {
7139 case Visibility::APPROXIMATELY_NONVISIBLE
: {
7140 return "APPROXIMATELY_NONVISIBLE";
7142 case Visibility::APPROXIMATELY_VISIBLE
: {
7143 return "APPROXIMATELY_VISIBLE";
7151 HTMLMediaElement::OnVisibilityChange(Visibility aNewVisibility
)
7153 LOG(LogLevel::Debug
,
7154 ("OnVisibilityChange(): %s\n", VisibilityString(aNewVisibility
)));
7156 mVisibilityState
= aNewVisibility
;
7162 switch (aNewVisibility
) {
7163 case Visibility::UNTRACKED
: {
7164 MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility");
7167 case Visibility::APPROXIMATELY_NONVISIBLE
: {
7168 if (mPlayTime
.IsStarted()) {
7169 // Not visible, play time is running -> Start hidden play time if
7175 case Visibility::APPROXIMATELY_VISIBLE
: {
7176 // Visible -> Just pause hidden play time (no-op if already paused).
7182 NotifyDecoderActivityChanges();
7186 HTMLMediaElement::GetMediaKeys() const
7192 HTMLMediaElement::ContainsRestrictedContent()
7194 return GetMediaKeys() != nullptr;
7198 HTMLMediaElement::SetCDMProxyFailure(const MediaResult
& aResult
)
7200 LOG(LogLevel::Debug
, ("%s", __func__
));
7201 MOZ_ASSERT(mSetMediaKeysDOMPromise
);
7203 ResetSetMediaKeysTempVariables();
7205 mSetMediaKeysDOMPromise
->MaybeReject(aResult
.Code(), aResult
.Message());
7209 HTMLMediaElement::RemoveMediaKeys()
7211 LOG(LogLevel::Debug
, ("%s", __func__
));
7212 // 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
7213 // to decrypt media data and remove the association with the media element.
7215 mMediaKeys
->Unbind();
7217 mMediaKeys
= nullptr;
7221 HTMLMediaElement::TryRemoveMediaKeysAssociation()
7223 MOZ_ASSERT(mMediaKeys
);
7224 LOG(LogLevel::Debug
, ("%s", __func__
));
7225 // 5.2.1 If the user agent or CDM do not support removing the association,
7226 // let this object's attaching media keys value be false and reject promise
7227 // with a new DOMException whose name is NotSupportedError.
7228 // 5.2.2 If the association cannot currently be removed, let this object's
7229 // attaching media keys value be false and reject promise with a new
7230 // DOMException whose name is InvalidStateError.
7232 RefPtr
<HTMLMediaElement
> self
= this;
7233 mDecoder
->SetCDMProxy(nullptr)
7234 ->Then(mAbstractMainThread
,
7237 self
->mSetCDMRequest
.Complete();
7239 self
->RemoveMediaKeys();
7240 if (self
->AttachNewMediaKeys()) {
7241 // No incoming MediaKeys object or MediaDecoder is not created
7243 self
->MakeAssociationWithCDMResolved();
7246 [self
](const MediaResult
& aResult
) {
7247 self
->mSetCDMRequest
.Complete();
7248 // 5.2.4 If the preceding step failed, let this object's
7249 // attaching media keys value be false and reject promise with a
7250 // new DOMException whose name is the appropriate error name.
7251 self
->SetCDMProxyFailure(aResult
);
7253 ->Track(mSetCDMRequest
);
7262 HTMLMediaElement::DetachExistingMediaKeys()
7264 LOG(LogLevel::Debug
, ("%s", __func__
));
7265 MOZ_ASSERT(mSetMediaKeysDOMPromise
);
7266 // 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
7267 // already in use by another media element, and the user agent is unable
7268 // to use it with this element, let this object's attaching media keys
7269 // value be false and reject promise with a new DOMException whose name
7270 // is QuotaExceededError.
7271 if (mIncomingMediaKeys
&& mIncomingMediaKeys
->IsBoundToMediaElement()) {
7272 SetCDMProxyFailure(MediaResult(
7273 NS_ERROR_DOM_QUOTA_EXCEEDED_ERR
,
7274 "MediaKeys object is already bound to another HTMLMediaElement"));
7278 // 5.2 If the mediaKeys attribute is not null, run the following steps:
7280 return TryRemoveMediaKeysAssociation();
7286 HTMLMediaElement::MakeAssociationWithCDMResolved()
7288 LOG(LogLevel::Debug
, ("%s", __func__
));
7289 MOZ_ASSERT(mSetMediaKeysDOMPromise
);
7291 // 5.4 Set the mediaKeys attribute to mediaKeys.
7292 mMediaKeys
= mIncomingMediaKeys
;
7293 // 5.5 Let this object's attaching media keys value be false.
7294 ResetSetMediaKeysTempVariables();
7295 // 5.6 Resolve promise.
7296 mSetMediaKeysDOMPromise
->MaybeResolveWithUndefined();
7297 mSetMediaKeysDOMPromise
= nullptr;
7301 HTMLMediaElement::TryMakeAssociationWithCDM(CDMProxy
* aProxy
)
7303 LOG(LogLevel::Debug
, ("%s", __func__
));
7306 // 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
7307 // algorithm on the media element.
7308 // Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
7310 // CDMProxy is set asynchronously in MediaFormatReader, once it's done,
7311 // HTMLMediaElement should resolve or reject the DOM promise.
7312 RefPtr
<HTMLMediaElement
> self
= this;
7313 mDecoder
->SetCDMProxy(aProxy
)
7314 ->Then(mAbstractMainThread
,
7317 self
->mSetCDMRequest
.Complete();
7318 self
->MakeAssociationWithCDMResolved();
7320 [self
](const MediaResult
& aResult
) {
7321 self
->mSetCDMRequest
.Complete();
7322 self
->SetCDMProxyFailure(aResult
);
7324 ->Track(mSetCDMRequest
);
7331 HTMLMediaElement::AttachNewMediaKeys()
7333 LOG(LogLevel::Debug
,
7334 ("%s incoming MediaKeys(%p)", __func__
, mIncomingMediaKeys
.get()));
7335 MOZ_ASSERT(mSetMediaKeysDOMPromise
);
7337 // 5.3. If mediaKeys is not null, run the following steps:
7338 if (mIncomingMediaKeys
) {
7339 auto cdmProxy
= mIncomingMediaKeys
->GetCDMProxy();
7341 SetCDMProxyFailure(MediaResult(
7342 NS_ERROR_DOM_INVALID_STATE_ERR
,
7343 "CDM crashed before binding MediaKeys object to HTMLMediaElement"));
7347 // 5.3.1 Associate the CDM instance represented by mediaKeys with the
7348 // media element for decrypting media data.
7349 if (NS_FAILED(mIncomingMediaKeys
->Bind(this))) {
7350 // 5.3.2 If the preceding step failed, run the following steps:
7352 // 5.3.2.1 Set the mediaKeys attribute to null.
7353 mMediaKeys
= nullptr;
7354 // 5.3.2.2 Let this object's attaching media keys value be false.
7355 // 5.3.2.3 Reject promise with a new DOMException whose name is
7356 // the appropriate error name.
7358 MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR
,
7359 "Failed to bind MediaKeys object to HTMLMediaElement"));
7362 return TryMakeAssociationWithCDM(cdmProxy
);
7368 HTMLMediaElement::ResetSetMediaKeysTempVariables()
7370 mAttachingMediaKey
= false;
7371 mIncomingMediaKeys
= nullptr;
7374 already_AddRefed
<Promise
>
7375 HTMLMediaElement::SetMediaKeys(mozilla::dom::MediaKeys
* aMediaKeys
,
7378 LOG(LogLevel::Debug
,
7379 ("%p SetMediaKeys(%p) mMediaKeys=%p mDecoder=%p",
7385 if (MozAudioCaptured()) {
7386 aRv
.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR
);
7390 nsPIDOMWindowInner
* win
= OwnerDoc()->GetInnerWindow();
7392 aRv
.Throw(NS_ERROR_UNEXPECTED
);
7395 RefPtr
<DetailedPromise
> promise
= DetailedPromise::Create(
7396 win
->AsGlobal(), aRv
, NS_LITERAL_CSTRING("HTMLMediaElement.setMediaKeys"));
7401 // 1. If mediaKeys and the mediaKeys attribute are the same object,
7402 // return a resolved promise.
7403 if (mMediaKeys
== aMediaKeys
) {
7404 promise
->MaybeResolveWithUndefined();
7405 return promise
.forget();
7408 // 2. If this object's attaching media keys value is true, return a
7409 // promise rejected with a new DOMException whose name is InvalidStateError.
7410 if (mAttachingMediaKey
) {
7411 promise
->MaybeReject(
7412 NS_ERROR_DOM_INVALID_STATE_ERR
,
7413 NS_LITERAL_CSTRING("A MediaKeys object is in attaching operation."));
7414 return promise
.forget();
7417 // 3. Let this object's attaching media keys value be true.
7418 mAttachingMediaKey
= true;
7419 mIncomingMediaKeys
= aMediaKeys
;
7421 // 4. Let promise be a new promise.
7422 mSetMediaKeysDOMPromise
= promise
;
7424 // 5. Run the following steps in parallel:
7427 if (!DetachExistingMediaKeys() || !AttachNewMediaKeys()) {
7428 return promise
.forget();
7432 MakeAssociationWithCDMResolved();
7434 // 6. Return promise.
7435 return promise
.forget();
7438 EventHandlerNonNull
*
7439 HTMLMediaElement::GetOnencrypted()
7441 return EventTarget::GetEventHandler(nsGkAtoms::onencrypted
);
7445 HTMLMediaElement::SetOnencrypted(EventHandlerNonNull
* aCallback
)
7447 EventTarget::SetEventHandler(nsGkAtoms::onencrypted
, aCallback
);
7450 EventHandlerNonNull
*
7451 HTMLMediaElement::GetOnwaitingforkey()
7453 return EventTarget::GetEventHandler(nsGkAtoms::onwaitingforkey
);
7457 HTMLMediaElement::SetOnwaitingforkey(EventHandlerNonNull
* aCallback
)
7459 EventTarget::SetEventHandler(nsGkAtoms::onwaitingforkey
, aCallback
);
7463 HTMLMediaElement::DispatchEncrypted(const nsTArray
<uint8_t>& aInitData
,
7464 const nsAString
& aInitDataType
)
7466 LOG(LogLevel::Debug
,
7467 ("%p DispatchEncrypted initDataType='%s'",
7469 NS_ConvertUTF16toUTF8(aInitDataType
).get()));
7471 if (mReadyState
== HAVE_NOTHING
) {
7472 // Ready state not HAVE_METADATA (yet), don't dispatch encrypted now.
7473 // Queueing for later dispatch in MetadataLoaded.
7474 mPendingEncryptedInitData
.AddInitData(aInitDataType
, aInitData
);
7478 RefPtr
<MediaEncryptedEvent
> event
;
7479 if (IsCORSSameOrigin()) {
7480 event
= MediaEncryptedEvent::Constructor(this, aInitDataType
, aInitData
);
7482 event
= MediaEncryptedEvent::Constructor(this);
7485 RefPtr
<AsyncEventDispatcher
> asyncDispatcher
=
7486 new AsyncEventDispatcher(this, event
);
7487 asyncDispatcher
->PostDOMEvent();
7491 HTMLMediaElement::IsEventAttributeNameInternal(nsAtom
* aName
)
7493 return aName
== nsGkAtoms::onencrypted
||
7494 nsGenericHTMLElement::IsEventAttributeNameInternal(aName
);
7497 already_AddRefed
<nsIPrincipal
>
7498 HTMLMediaElement::GetTopLevelPrincipal()
7500 RefPtr
<nsIPrincipal
> principal
;
7501 nsCOMPtr
<nsPIDOMWindowInner
> window
= OwnerDoc()->GetInnerWindow();
7505 // XXXkhuey better hope we always have an outer ...
7506 nsCOMPtr
<nsPIDOMWindowOuter
> top
= window
->GetOuterWindow()->GetTop();
7510 nsIDocument
* doc
= top
->GetExtantDoc();
7514 principal
= doc
->NodePrincipal();
7515 return principal
.forget();
7519 HTMLMediaElement::NotifyWaitingForKey()
7521 LOG(LogLevel::Debug
, ("%p, NotifyWaitingForKey()", this));
7523 // http://w3c.github.io/encrypted-media/#wait-for-key
7524 // 7.3.4 Queue a "waitingforkey" Event
7525 // 1. Let the media element be the specified HTMLMediaElement object.
7526 // 2. If the media element's waiting for key value is true, abort these steps.
7527 if (mWaitingForKey
== NOT_WAITING_FOR_KEY
) {
7528 // 3. Set the media element's waiting for key value to true.
7529 // Note: algorithm continues in UpdateReadyStateInternal() when all decoded
7530 // data enqueued in the MDSM is consumed.
7531 mWaitingForKey
= WAITING_FOR_KEY
;
7532 UpdateReadyStateInternal();
7537 HTMLMediaElement::AudioTracks()
7539 if (!mAudioTrackList
) {
7540 nsCOMPtr
<nsPIDOMWindowInner
> window
=
7541 do_QueryInterface(OwnerDoc()->GetParentObject());
7542 mAudioTrackList
= new AudioTrackList(window
, this);
7544 return mAudioTrackList
;
7548 HTMLMediaElement::VideoTracks()
7550 if (!mVideoTrackList
) {
7551 nsCOMPtr
<nsPIDOMWindowInner
> window
=
7552 do_QueryInterface(OwnerDoc()->GetParentObject());
7553 mVideoTrackList
= new VideoTrackList(window
, this);
7555 return mVideoTrackList
;
7559 HTMLMediaElement::GetTextTracks()
7561 return GetOrCreateTextTrackManager()->GetTextTracks();
7564 already_AddRefed
<TextTrack
>
7565 HTMLMediaElement::AddTextTrack(TextTrackKind aKind
,
7566 const nsAString
& aLabel
,
7567 const nsAString
& aLanguage
)
7569 return GetOrCreateTextTrackManager()->AddTextTrack(
7573 TextTrackMode::Hidden
,
7574 TextTrackReadyState::Loaded
,
7575 TextTrackSource::AddTextTrack
);
7579 HTMLMediaElement::PopulatePendingTextTrackList()
7581 if (mTextTrackManager
) {
7582 mTextTrackManager
->PopulatePendingList();
7587 HTMLMediaElement::GetOrCreateTextTrackManager()
7589 if (!mTextTrackManager
) {
7590 mTextTrackManager
= new TextTrackManager(this);
7591 mTextTrackManager
->AddListeners();
7593 return mTextTrackManager
;
7596 MediaDecoderOwner::NextFrameStatus
7597 HTMLMediaElement::NextFrameStatus()
7600 return mDecoder
->NextFrameStatus();
7601 } else if (mMediaStreamListener
) {
7602 return mMediaStreamListener
->NextFrameStatus();
7604 return NEXT_FRAME_UNINITIALIZED
;
7608 HTMLMediaElement::SetDecoder(MediaDecoder
* aDecoder
)
7610 MOZ_ASSERT(aDecoder
); // Use ShutdownDecoder() to clear.
7614 mDecoder
= aDecoder
;
7615 DDLINKCHILD("decoder", mDecoder
.get());
7616 if (mDecoder
&& mForcedHidden
) {
7617 mDecoder
->SetForcedHidden(mForcedHidden
);
7622 HTMLMediaElement::ComputedVolume() const
7626 : mAudioChannelWrapper
? mAudioChannelWrapper
->GetEffectiveVolume()
7631 HTMLMediaElement::ComputedMuted() const
7633 return (mMuted
& MUTED_BY_AUDIO_CHANNEL
);
7637 HTMLMediaElement::ComputedSuspended() const
7639 return mAudioChannelWrapper
? mAudioChannelWrapper
->GetSuspendType()
7640 : nsISuspendedTypes::NONE_SUSPENDED
;
7644 HTMLMediaElement::IsCurrentlyPlaying() const
7646 // We have playable data, but we still need to check whether data is "real"
7648 return mReadyState
>= HAVE_CURRENT_DATA
&& !IsPlaybackEnded();
7652 HTMLMediaElement::SetAudibleState(bool aAudible
)
7654 if (mIsAudioTrackAudible
!= aAudible
) {
7655 UpdateAudioTrackSilenceRange(aAudible
);
7656 mIsAudioTrackAudible
= aAudible
;
7657 NotifyAudioPlaybackChanged(
7658 AudioChannelService::AudibleChangedReasons::eDataAudibleChanged
);
7663 HTMLMediaElement::IsAudioTrackCurrentlySilent() const
7665 return HasAudio() && !mIsAudioTrackAudible
;
7669 HTMLMediaElement::UpdateAudioTrackSilenceRange(bool aAudible
)
7676 mAudioTrackSilenceStartedTime
= CurrentTime();
7680 AccumulateAudioTrackSilence();
7684 HTMLMediaElement::AccumulateAudioTrackSilence()
7686 MOZ_ASSERT(HasAudio());
7687 const double current
= CurrentTime();
7688 if (current
< mAudioTrackSilenceStartedTime
) {
7691 const auto start
= media::TimeUnit::FromSeconds(mAudioTrackSilenceStartedTime
);
7692 const auto end
= media::TimeUnit::FromSeconds(current
);
7693 mSilenceTimeRanges
+= media::TimeInterval(start
, end
);
7697 HTMLMediaElement::ReportAudioTrackSilenceProportionTelemetry()
7703 // Add last silence range to our ranges set.
7704 if (!mIsAudioTrackAudible
) {
7705 AccumulateAudioTrackSilence();
7708 RefPtr
<TimeRanges
> ranges
= Played();
7709 const uint32_t lengthPlayedRange
= ranges
->Length();
7710 const uint32_t lengthSilenceRange
= mSilenceTimeRanges
.Length();
7711 if (!lengthPlayedRange
|| !lengthSilenceRange
) {
7715 double playedTime
= 0.0, silenceTime
= 0.0;
7716 for (uint32_t idx
= 0; idx
< lengthPlayedRange
; idx
++) {
7717 playedTime
+= ranges
->End(idx
) - ranges
->Start(idx
);
7720 for (uint32_t idx
= 0; idx
< lengthSilenceRange
; idx
++) {
7722 mSilenceTimeRanges
.End(idx
).ToSeconds() - mSilenceTimeRanges
.Start(idx
).ToSeconds();
7725 double silenceProportion
= (silenceTime
/ playedTime
) * 100;
7726 // silenceProportion should be in the range [0, 100]
7727 silenceProportion
= std::min(100.0, std::max(silenceProportion
, 0.0));
7728 Telemetry::Accumulate(Telemetry::AUDIO_TRACK_SILENCE_PROPORTION
,
7733 HTMLMediaElement::NotifyAudioPlaybackChanged(AudibleChangedReasons aReason
)
7735 if (mAudioChannelWrapper
) {
7736 mAudioChannelWrapper
->NotifyAudioPlaybackChanged(aReason
);
7738 // only request wake lock for audible media.
7743 HTMLMediaElement::ShouldElementBePaused()
7745 // Bfcached page or inactive document.
7754 HTMLMediaElement::SetMediaInfo(const MediaInfo
& aInfo
)
7756 const bool oldHasAudio
= mMediaInfo
.HasAudio();
7758 if (aInfo
.HasAudio() != oldHasAudio
) {
7759 UpdateAudioChannelPlayingState();
7760 NotifyAudioPlaybackChanged(
7761 AudioChannelService::AudibleChangedReasons::eDataAudibleChanged
);
7763 if (mAudioChannelWrapper
) {
7764 mAudioChannelWrapper
->AudioCaptureStreamChangeIfNeeded();
7770 HTMLMediaElement::AudioCaptureStreamChange(bool aCapture
)
7772 // No need to capture a silence media element.
7777 if (aCapture
&& !mCaptureStreamPort
) {
7778 nsCOMPtr
<nsPIDOMWindowInner
> window
= OwnerDoc()->GetInnerWindow();
7779 if (!OwnerDoc()->GetInnerWindow()) {
7783 uint64_t id
= window
->WindowID();
7784 MediaStreamGraph
* msg
= MediaStreamGraph::GetInstance(
7785 MediaStreamGraph::AUDIO_THREAD_DRIVER
,
7787 MediaStreamGraph::REQUEST_DEFAULT_SAMPLE_RATE
);
7789 if (GetSrcMediaStream()) {
7790 mCaptureStreamPort
= msg
->ConnectToCaptureStream(id
, GetSrcMediaStream());
7792 RefPtr
<DOMMediaStream
> stream
=
7793 CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED
,
7794 StreamCaptureType::CAPTURE_AUDIO
,
7796 mCaptureStreamPort
=
7797 msg
->ConnectToCaptureStream(id
, stream
->GetPlaybackStream());
7799 } else if (!aCapture
&& mCaptureStreamPort
) {
7801 ProcessedMediaStream
* ps
=
7802 mCaptureStreamPort
->GetSource()->AsProcessedStream();
7805 for (uint32_t i
= 0; i
< mOutputStreams
.Length(); i
++) {
7806 if (mOutputStreams
[i
].mStream
->GetPlaybackStream() == ps
) {
7807 mOutputStreams
.RemoveElementAt(i
);
7811 mDecoder
->RemoveOutputStream(ps
);
7813 mCaptureStreamPort
->Destroy();
7814 mCaptureStreamPort
= nullptr;
7819 HTMLMediaElement::NotifyCueDisplayStatesChanged()
7821 if (!mTextTrackManager
) {
7825 mTextTrackManager
->DispatchUpdateCueDisplay();
7829 HTMLMediaElement::MarkAsContentSource(CallerAPI aAPI
)
7831 const bool isVisible
= mVisibilityState
== Visibility::APPROXIMATELY_VISIBLE
;
7835 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 0);
7837 // 1 = ALL_INVISIBLE
7838 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 1);
7840 if (IsInComposedDoc()) {
7842 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
,
7845 // 1 = ALL_NOT_IN_TREE
7846 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
,
7852 case CallerAPI::DRAW_IMAGE
: {
7854 // 2 = drawImage_VISIBLE
7855 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 2);
7857 // 3 = drawImage_INVISIBLE
7858 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 3);
7860 if (IsInComposedDoc()) {
7861 // 2 = drawImage_IN_TREE
7862 Telemetry::Accumulate(
7863 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 2);
7865 // 3 = drawImage_NOT_IN_TREE
7866 Telemetry::Accumulate(
7867 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 3);
7872 case CallerAPI::CREATE_PATTERN
: {
7874 // 4 = createPattern_VISIBLE
7875 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 4);
7877 // 5 = createPattern_INVISIBLE
7878 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 5);
7880 if (IsInComposedDoc()) {
7881 // 4 = createPattern_IN_TREE
7882 Telemetry::Accumulate(
7883 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 4);
7885 // 5 = createPattern_NOT_IN_TREE
7886 Telemetry::Accumulate(
7887 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 5);
7892 case CallerAPI::CREATE_IMAGEBITMAP
: {
7894 // 6 = createImageBitmap_VISIBLE
7895 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 6);
7897 // 7 = createImageBitmap_INVISIBLE
7898 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 7);
7900 if (IsInComposedDoc()) {
7901 // 6 = createImageBitmap_IN_TREE
7902 Telemetry::Accumulate(
7903 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 6);
7905 // 7 = createImageBitmap_NOT_IN_TREE
7906 Telemetry::Accumulate(
7907 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 7);
7912 case CallerAPI::CAPTURE_STREAM
: {
7914 // 8 = captureStream_VISIBLE
7915 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 8);
7917 // 9 = captureStream_INVISIBLE
7918 Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE
, 9);
7920 if (IsInComposedDoc()) {
7921 // 8 = captureStream_IN_TREE
7922 Telemetry::Accumulate(
7923 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 8);
7925 // 9 = captureStream_NOT_IN_TREE
7926 Telemetry::Accumulate(
7927 Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT
, 9);
7934 LOG(LogLevel::Debug
,
7935 ("%p Log VIDEO_AS_CONTENT_SOURCE: visibility = %u, API: '%d' and 'All'",
7938 static_cast<int>(aAPI
)));
7941 LOG(LogLevel::Debug
,
7942 ("%p Log VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT: inTree = %u, API: "
7946 static_cast<int>(aAPI
)));
7951 HTMLMediaElement::UpdateCustomPolicyAfterPlayed()
7953 if (mAudioChannelWrapper
) {
7954 mAudioChannelWrapper
->NotifyPlayStateChanged();
7959 HTMLMediaElement::AbstractMainThread() const
7961 MOZ_ASSERT(mAbstractMainThread
);
7963 return mAbstractMainThread
;
7966 nsTArray
<RefPtr
<PlayPromise
>>
7967 HTMLMediaElement::TakePendingPlayPromises()
7969 return std::move(mPendingPlayPromises
);
7973 HTMLMediaElement::NotifyAboutPlaying()
7975 // Stick to the DispatchAsyncEvent() call path for now because we want to
7976 // trigger some telemetry-related codes in the DispatchAsyncEvent() method.
7977 DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
7980 already_AddRefed
<PlayPromise
>
7981 HTMLMediaElement::CreatePlayPromise(ErrorResult
& aRv
) const
7983 nsPIDOMWindowInner
* win
= OwnerDoc()->GetInnerWindow();
7986 aRv
.Throw(NS_ERROR_UNEXPECTED
);
7990 RefPtr
<PlayPromise
> promise
= PlayPromise::Create(win
->AsGlobal(), aRv
);
7991 LOG(LogLevel::Debug
, ("%p created PlayPromise %p", this, promise
.get()));
7993 return promise
.forget();
7996 already_AddRefed
<Promise
>
7997 HTMLMediaElement::CreateDOMPromise(ErrorResult
& aRv
) const
7999 nsPIDOMWindowInner
* win
= OwnerDoc()->GetInnerWindow();
8002 aRv
.Throw(NS_ERROR_UNEXPECTED
);
8006 return Promise::Create(win
->AsGlobal(), aRv
);
8010 HTMLMediaElement::AsyncResolvePendingPlayPromises()
8012 // Disconnect requests for permission to play. We're playing either way,
8013 // so there's no point keeping the promise connected. Note: the front
8014 // end permission prompt code will detect that we've started playing, and
8015 // hide the permission prompt.
8016 mAutoplayPermissionRequest
.DisconnectIfExists();
8018 if (mShuttingDown
) {
8022 nsCOMPtr
<nsIRunnable
> event
= new nsResolveOrRejectPendingPlayPromisesRunner(
8023 this, TakePendingPlayPromises());
8025 mMainThreadEventTarget
->Dispatch(event
.forget());
8029 HTMLMediaElement::AsyncRejectPendingPlayPromises(nsresult aError
)
8031 mAutoplayPermissionRequest
.DisconnectIfExists();
8035 DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
8038 if (mShuttingDown
) {
8042 if (aError
== NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR
) {
8043 DispatchEventsWhenPlayWasNotAllowed();
8046 nsCOMPtr
<nsIRunnable
> event
= new nsResolveOrRejectPendingPlayPromisesRunner(
8047 this, TakePendingPlayPromises(), aError
);
8049 mMainThreadEventTarget
->Dispatch(event
.forget());
8053 HTMLMediaElement::GetEMEInfo(nsString
& aEMEInfo
)
8060 mMediaKeys
->GetKeySystem(keySystem
);
8062 nsString sessionsInfo
;
8063 mMediaKeys
->GetSessionsInfo(sessionsInfo
);
8065 aEMEInfo
.AppendLiteral("Key System=");
8066 aEMEInfo
.Append(keySystem
);
8067 aEMEInfo
.AppendLiteral(" SessionsInfo=");
8068 aEMEInfo
.Append(sessionsInfo
);
8072 HTMLMediaElement::NotifyDecoderActivityChanges() const
8075 mDecoder
->NotifyOwnerActivityChanged(
8076 !IsHidden(), mVisibilityState
, IsInComposedDoc());
8081 HTMLMediaElement::GetDocument() const
8087 HTMLMediaElement::ConstructMediaTracks(const MediaInfo
* aInfo
)
8089 if (mMediaTracksConstructed
|| !aInfo
) {
8093 mMediaTracksConstructed
= true;
8095 AudioTrackList
* audioList
= AudioTracks();
8096 if (audioList
&& aInfo
->HasAudio()) {
8097 const TrackInfo
& info
= aInfo
->mAudio
;
8098 RefPtr
<AudioTrack
> track
=
8099 MediaTrackList::CreateAudioTrack(audioList
->GetOwnerGlobal(),
8106 audioList
->AddTrack(track
);
8109 VideoTrackList
* videoList
= VideoTracks();
8110 if (videoList
&& aInfo
->HasVideo()) {
8111 const TrackInfo
& info
= aInfo
->mVideo
;
8112 RefPtr
<VideoTrack
> track
=
8113 MediaTrackList::CreateVideoTrack(videoList
->GetOwnerGlobal(),
8119 videoList
->AddTrack(track
);
8120 track
->SetEnabledInternal(info
.mEnabled
, MediaTrack::FIRE_NO_EVENTS
);
8125 HTMLMediaElement::RemoveMediaTracks()
8127 if (mAudioTrackList
) {
8128 mAudioTrackList
->RemoveTracks();
8131 if (mVideoTrackList
) {
8132 mVideoTrackList
->RemoveTracks();
8135 mMediaTracksConstructed
= false;
8137 for (OutputMediaStream
& ms
: mOutputStreams
) {
8138 if (!ms
.mCapturingDecoder
) {
8141 for (RefPtr
<MediaStreamTrack
>& t
: ms
.mPreCreatedTracks
) {
8145 mAbstractMainThread
->Dispatch(NewRunnableMethod(
8146 "dom::HTMLMediaElement::RemoveMediaTracks",
8147 t
, &MediaStreamTrack::OverrideEnded
));
8149 ms
.mPreCreatedTracks
.Clear();
8153 class MediaElementGMPCrashHelper
: public GMPCrashHelper
8156 explicit MediaElementGMPCrashHelper(HTMLMediaElement
* aElement
)
8157 : mElement(aElement
)
8159 MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
8161 already_AddRefed
<nsPIDOMWindowInner
> GetPluginCrashedEventTarget() override
8163 MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
8167 return do_AddRef(mElement
->OwnerDoc()->GetInnerWindow());
8171 WeakPtr
<HTMLMediaElement
> mElement
;
8174 already_AddRefed
<GMPCrashHelper
>
8175 HTMLMediaElement::CreateGMPCrashHelper()
8177 return MakeAndAddRef
<MediaElementGMPCrashHelper
>(this);
8181 HTMLMediaElement::MarkAsTainted()
8183 mHasSuspendTaint
= true;
8186 mDecoder
->SetSuspendTaint(true);
8191 HasDebuggerOrTabsPrivilege(JSContext
* aCx
, JSObject
* aObj
)
8193 return nsContentUtils::CallerHasPermission(aCx
, nsGkAtoms::debugger
) ||
8194 nsContentUtils::CallerHasPermission(aCx
, nsGkAtoms::tabs
);
8198 HTMLMediaElement::AsyncResolveSeekDOMPromiseIfExists()
8200 MOZ_ASSERT(NS_IsMainThread());
8201 if (mSeekDOMPromise
) {
8202 RefPtr
<dom::Promise
> promise
= mSeekDOMPromise
.forget();
8203 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
8204 "dom::HTMLMediaElement::AsyncResolveSeekDOMPromiseIfExists",
8205 [promise
]() { promise
->MaybeResolveWithUndefined(); });
8206 mAbstractMainThread
->Dispatch(r
.forget());
8207 mSeekDOMPromise
= nullptr;
8212 HTMLMediaElement::AsyncRejectSeekDOMPromiseIfExists()
8214 MOZ_ASSERT(NS_IsMainThread());
8215 if (mSeekDOMPromise
) {
8216 RefPtr
<dom::Promise
> promise
= mSeekDOMPromise
.forget();
8217 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
8218 "dom::HTMLMediaElement::AsyncRejectSeekDOMPromiseIfExists",
8219 [promise
]() { promise
->MaybeReject(NS_ERROR_DOM_ABORT_ERR
); });
8220 mAbstractMainThread
->Dispatch(r
.forget());
8221 mSeekDOMPromise
= nullptr;
8226 HTMLMediaElement::ReportCanPlayTelemetry()
8228 LOG(LogLevel::Debug
, ("%s", __func__
));
8230 RefPtr
<nsIThread
> thread
;
8231 nsresult rv
= NS_NewNamedThread("MediaTelemetry", getter_AddRefs(thread
));
8232 if (NS_WARN_IF(NS_FAILED(rv
))) {
8236 RefPtr
<AbstractThread
> abstractThread
= mAbstractMainThread
;
8239 NS_NewRunnableFunction(
8240 "dom::HTMLMediaElement::ReportCanPlayTelemetry",
8241 [thread
, abstractThread
]() {
8243 // Windows Media Foundation requires MSCOM to be inited.
8244 DebugOnly
<HRESULT
> hr
= CoInitializeEx(0, COINIT_MULTITHREADED
);
8245 MOZ_ASSERT(hr
== S_OK
);
8247 bool aac
= MP4Decoder::IsSupportedType(
8248 MediaContainerType(MEDIAMIMETYPE(AUDIO_MP4
)), nullptr);
8249 bool h264
= MP4Decoder::IsSupportedType(
8250 MediaContainerType(MEDIAMIMETYPE(VIDEO_MP4
)), nullptr);
8254 abstractThread
->Dispatch(NS_NewRunnableFunction(
8255 "dom::HTMLMediaElement::ReportCanPlayTelemetry",
8256 [thread
, aac
, h264
]() {
8257 LOG(LogLevel::Debug
, ("MediaTelemetry aac=%d h264=%d", aac
, h264
));
8258 Telemetry::Accumulate(
8259 Telemetry::HistogramID::VIDEO_CAN_CREATE_AAC_DECODER
, aac
);
8260 Telemetry::Accumulate(
8261 Telemetry::HistogramID::VIDEO_CAN_CREATE_H264_DECODER
, h264
);
8262 thread
->AsyncShutdown();
8265 NS_DISPATCH_NORMAL
);
8269 } // namespace mozilla