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