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