Line data Source code
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/. */
6 :
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
13 :
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/dom/BlobURLProtocolHandler.h"
62 : #include "mozilla/dom/ElementInlines.h"
63 : #include "mozilla/dom/HTMLAudioElement.h"
64 : #include "mozilla/dom/HTMLInputElement.h"
65 : #include "mozilla/dom/HTMLMediaElementBinding.h"
66 : #include "mozilla/dom/HTMLSourceElement.h"
67 : #include "mozilla/dom/HTMLVideoElement.h"
68 : #include "mozilla/dom/MediaEncryptedEvent.h"
69 : #include "mozilla/dom/MediaErrorBinding.h"
70 : #include "mozilla/dom/MediaSource.h"
71 : #include "mozilla/dom/PlayPromise.h"
72 : #include "mozilla/dom/Promise.h"
73 : #include "mozilla/dom/TextTrack.h"
74 : #include "mozilla/dom/VideoPlaybackQuality.h"
75 : #include "mozilla/dom/VideoTrack.h"
76 : #include "mozilla/dom/VideoTrackList.h"
77 : #include "mozilla/dom/WakeLock.h"
78 : #include "mozilla/dom/power/PowerManagerService.h"
79 : #include "nsAttrValueInlines.h"
80 : #include "nsContentPolicyUtils.h"
81 : #include "nsContentUtils.h"
82 : #include "nsCycleCollectionParticipant.h"
83 : #include "nsDisplayList.h"
84 : #include "nsDocShell.h"
85 : #include "nsError.h"
86 : #include "nsGenericHTMLElement.h"
87 : #include "nsGkAtoms.h"
88 : #include "nsIAsyncVerifyRedirectCallback.h"
89 : #include "nsICachingChannel.h"
90 : #include "nsICategoryManager.h"
91 : #include "nsIClassOfService.h"
92 : #include "nsIContentPolicy.h"
93 : #include "nsIContentSecurityPolicy.h"
94 : #include "nsIDocShell.h"
95 : #include "nsIDocument.h"
96 : #include "nsIFrame.h"
97 : #include "nsIObserverService.h"
98 : #include "nsIPermissionManager.h"
99 : #include "nsIPresShell.h"
100 : #include "nsIRequest.h"
101 : #include "nsIScriptError.h"
102 : #include "nsIScriptSecurityManager.h"
103 : #include "nsISupportsPrimitives.h"
104 : #include "nsIThreadInternal.h"
105 : #include "nsITimer.h"
106 : #include "nsIXPConnect.h"
107 : #include "nsJSUtils.h"
108 : #include "nsLayoutUtils.h"
109 : #include "nsMediaFragmentURIParser.h"
110 : #include "nsMimeTypes.h"
111 : #include "nsNetUtil.h"
112 : #include "nsNodeInfoManager.h"
113 : #include "nsPresContext.h"
114 : #include "nsQueryObject.h"
115 : #include "nsRange.h"
116 : #include "nsSize.h"
117 : #include "nsThreadUtils.h"
118 : #include "nsURIHashKey.h"
119 : #include "nsVideoFrame.h"
120 : #include "xpcpublic.h"
121 : #include <algorithm>
122 : #include <cmath>
123 : #include <limits>
124 :
125 : mozilla::LazyLogModule gMediaElementLog("nsMediaElement");
126 : static mozilla::LazyLogModule gMediaElementEventsLog("nsMediaElementEvents");
127 :
128 : #define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg)
129 : #define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg)
130 :
131 : using namespace mozilla::layers;
132 : using mozilla::net::nsMediaFragmentURIParser;
133 : using namespace mozilla::dom::HTMLMediaElementBinding;
134 :
135 : namespace mozilla {
136 : namespace dom {
137 :
138 : // Number of milliseconds between progress events as defined by spec
139 : static const uint32_t PROGRESS_MS = 350;
140 :
141 : // Number of milliseconds of no data before a stall event is fired as defined by
142 : // spec
143 : static const uint32_t STALL_MS = 3000;
144 :
145 : // Used by AudioChannel for suppresssing the volume to this ratio.
146 : #define FADED_VOLUME_RATIO 0.25
147 :
148 : // These constants are arbitrary
149 : // Minimum playbackRate for a media
150 : static const double MIN_PLAYBACKRATE = 1.0 / 16;
151 : // Maximum playbackRate for a media
152 : static const double MAX_PLAYBACKRATE = 16.0;
153 : // These are the limits beyonds which SoundTouch does not perform too well and
154 : // when speech is hard to understand anyway. Threshold above which audio is
155 : // muted
156 : static const double THRESHOLD_HIGH_PLAYBACKRATE_AUDIO = 4.0;
157 : // Threshold under which audio is muted
158 : static const double THRESHOLD_LOW_PLAYBACKRATE_AUDIO = 0.5;
159 :
160 : // Media error values. These need to match the ones in MediaError.webidl.
161 : static const unsigned short MEDIA_ERR_ABORTED = 1;
162 : static const unsigned short MEDIA_ERR_NETWORK = 2;
163 : static const unsigned short MEDIA_ERR_DECODE = 3;
164 : static const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
165 :
166 : static void
167 0 : ResolvePromisesWithUndefined(const nsTArray<RefPtr<PlayPromise>>& aPromises)
168 : {
169 0 : for (auto& promise : aPromises) {
170 0 : promise->MaybeResolveWithUndefined();
171 : }
172 0 : }
173 :
174 : static void
175 0 : RejectPromises(const nsTArray<RefPtr<PlayPromise>>& aPromises, nsresult aError)
176 : {
177 0 : for (auto& promise : aPromises) {
178 0 : promise->MaybeReject(aError);
179 : }
180 0 : }
181 :
182 : // Under certain conditions there may be no-one holding references to
183 : // a media element from script, DOM parent, etc, but the element may still
184 : // fire meaningful events in the future so we can't destroy it yet:
185 : // 1) If the element is delaying the load event (or would be, if it were
186 : // in a document), then events up to loadeddata or error could be fired,
187 : // so we need to stay alive.
188 : // 2) If the element is not paused and playback has not ended, then
189 : // we will (or might) play, sending timeupdate and ended events and possibly
190 : // audio output, so we need to stay alive.
191 : // 3) if the element is seeking then we will fire seeking events and possibly
192 : // start playing afterward, so we need to stay alive.
193 : // 4) If autoplay could start playback in this element (if we got enough data),
194 : // then we need to stay alive.
195 : // 5) if the element is currently loading, not suspended, and its source is
196 : // not a MediaSource, then script might be waiting for progress events or a
197 : // 'stalled' or 'suspend' event, so we need to stay alive.
198 : // If we're already suspended then (all other conditions being met),
199 : // it's OK to just disappear without firing any more events,
200 : // since we have the freedom to remain suspended indefinitely. Note
201 : // that we could use this 'suspended' loophole to garbage-collect a suspended
202 : // element in case 4 even if it had 'autoplay' set, but we choose not to.
203 : // If someone throws away all references to a loading 'autoplay' element
204 : // sound should still eventually play.
205 : // 6) If the source is a MediaSource, most loading events will not fire unless
206 : // appendBuffer() is called on a SourceBuffer, in which case something is
207 : // already referencing the SourceBuffer, which keeps the associated media
208 : // element alive. Further, a MediaSource will never time out the resource
209 : // fetch, and so should not keep the media element alive if it is
210 : // unreferenced. A pending 'stalled' event keeps the media element alive.
211 : //
212 : // Media elements owned by inactive documents (i.e. documents not contained in
213 : // any document viewer) should never hold a self-reference because none of the
214 : // above conditions are allowed: the element will stop loading and playing
215 : // and never resume loading or playing unless its owner document changes to
216 : // an active document (which can only happen if there is an external reference
217 : // to the element).
218 : // Media elements with no owner doc should be able to hold a self-reference.
219 : // Something native must have created the element and may expect it to
220 : // stay alive to play.
221 :
222 : // It's very important that any change in state which could change the value of
223 : // needSelfReference in AddRemoveSelfReference be followed by a call to
224 : // AddRemoveSelfReference before this element could die!
225 : // It's especially important if needSelfReference would change to 'true',
226 : // since if we neglect to add a self-reference, this element might be
227 : // garbage collected while there are still event listeners that should
228 : // receive events. If we neglect to remove the self-reference then the element
229 : // just lives longer than it needs to.
230 :
231 : class nsMediaEvent : public Runnable
232 : {
233 : public:
234 0 : explicit nsMediaEvent(const char* aName, HTMLMediaElement* aElement)
235 0 : : Runnable(aName)
236 : , mElement(aElement)
237 0 : , mLoadID(mElement->GetCurrentLoadID())
238 : {
239 0 : }
240 0 : ~nsMediaEvent() {}
241 :
242 : NS_IMETHOD Run() override = 0;
243 :
244 : protected:
245 0 : bool IsCancelled() { return mElement->GetCurrentLoadID() != mLoadID; }
246 :
247 : RefPtr<HTMLMediaElement> mElement;
248 : uint32_t mLoadID;
249 : };
250 :
251 0 : class HTMLMediaElement::nsAsyncEventRunner : public nsMediaEvent
252 : {
253 : private:
254 : nsString mName;
255 :
256 : public:
257 0 : nsAsyncEventRunner(const nsAString& aName, HTMLMediaElement* aElement)
258 0 : : nsMediaEvent("HTMLMediaElement::nsAsyncEventRunner", aElement)
259 0 : , mName(aName)
260 : {
261 0 : }
262 :
263 0 : NS_IMETHOD Run() override
264 : {
265 : // Silently cancel if our load has been cancelled.
266 0 : if (IsCancelled())
267 : return NS_OK;
268 :
269 0 : return mElement->DispatchEvent(mName);
270 : }
271 : };
272 :
273 : /*
274 : * If no error is passed while constructing an instance, the instance will
275 : * resolve the passed promises with undefined; otherwise, the instance will
276 : * reject the passed promises with the passed error.
277 : *
278 : * The constructor appends the constructed instance into the passed media
279 : * element's mPendingPlayPromisesRunners member and once the the runner is run
280 : * (whether fulfilled or canceled), it removes itself from
281 : * mPendingPlayPromisesRunners.
282 : */
283 0 : class HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner
284 : : public nsMediaEvent
285 : {
286 : nsTArray<RefPtr<PlayPromise>> mPromises;
287 : nsresult mError;
288 :
289 : public:
290 0 : nsResolveOrRejectPendingPlayPromisesRunner(
291 : HTMLMediaElement* aElement,
292 : nsTArray<RefPtr<PlayPromise>>&& aPromises,
293 : nsresult aError = NS_OK)
294 0 : : nsMediaEvent(
295 : "HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner",
296 : aElement)
297 0 : , mPromises(std::move(aPromises))
298 0 : , mError(aError)
299 : {
300 0 : mElement->mPendingPlayPromisesRunners.AppendElement(this);
301 0 : }
302 :
303 0 : void ResolveOrReject()
304 : {
305 0 : if (NS_SUCCEEDED(mError)) {
306 0 : ResolvePromisesWithUndefined(mPromises);
307 : } else {
308 0 : RejectPromises(mPromises, mError);
309 : }
310 0 : }
311 :
312 0 : NS_IMETHOD Run() override
313 : {
314 0 : if (!IsCancelled()) {
315 0 : ResolveOrReject();
316 : }
317 :
318 0 : mElement->mPendingPlayPromisesRunners.RemoveElement(this);
319 0 : return NS_OK;
320 : }
321 : };
322 :
323 0 : class HTMLMediaElement::nsNotifyAboutPlayingRunner
324 : : public nsResolveOrRejectPendingPlayPromisesRunner
325 : {
326 : public:
327 0 : nsNotifyAboutPlayingRunner(
328 : HTMLMediaElement* aElement,
329 : nsTArray<RefPtr<PlayPromise>>&& aPendingPlayPromises)
330 0 : : nsResolveOrRejectPendingPlayPromisesRunner(aElement,
331 0 : std::move(aPendingPlayPromises))
332 : {
333 0 : }
334 :
335 0 : NS_IMETHOD Run() override
336 : {
337 0 : if (IsCancelled()) {
338 0 : mElement->mPendingPlayPromisesRunners.RemoveElement(this);
339 0 : return NS_OK;
340 : }
341 :
342 0 : mElement->DispatchEvent(NS_LITERAL_STRING("playing"));
343 0 : return nsResolveOrRejectPendingPlayPromisesRunner::Run();
344 : }
345 : };
346 :
347 0 : class nsSourceErrorEventRunner : public nsMediaEvent
348 : {
349 : private:
350 : nsCOMPtr<nsIContent> mSource;
351 :
352 : public:
353 0 : nsSourceErrorEventRunner(HTMLMediaElement* aElement, nsIContent* aSource)
354 0 : : nsMediaEvent("dom::nsSourceErrorEventRunner", aElement)
355 0 : , mSource(aSource)
356 : {
357 0 : }
358 :
359 0 : NS_IMETHOD Run() override
360 : {
361 : // Silently cancel if our load has been cancelled.
362 0 : if (IsCancelled())
363 : return NS_OK;
364 0 : LOG_EVENT(LogLevel::Debug,
365 : ("%p Dispatching simple event source error", mElement.get()));
366 0 : return nsContentUtils::DispatchTrustedEvent(
367 0 : mElement->OwnerDoc(), mSource, NS_LITERAL_STRING("error"), false, false);
368 : }
369 : };
370 :
371 : /**
372 : * This listener observes the first video frame to arrive with a non-empty size,
373 : * and calls HTMLMediaElement::UpdateInitialMediaSize() with that size.
374 : */
375 0 : class HTMLMediaElement::StreamSizeListener
376 : : public DirectMediaStreamTrackListener
377 : {
378 : public:
379 0 : explicit StreamSizeListener(HTMLMediaElement* aElement)
380 0 : : mElement(aElement)
381 : , mMainThreadEventTarget(aElement->MainThreadEventTarget())
382 0 : , mInitialSizeFound(false)
383 : {
384 0 : MOZ_ASSERT(mElement);
385 0 : MOZ_ASSERT(mMainThreadEventTarget);
386 0 : }
387 :
388 0 : void Forget() { mElement = nullptr; }
389 :
390 0 : void ReceivedSize(gfx::IntSize aSize)
391 : {
392 0 : MOZ_ASSERT(NS_IsMainThread());
393 :
394 0 : if (!mElement) {
395 0 : return;
396 : }
397 :
398 0 : RefPtr<HTMLMediaElement> deathGrip = mElement;
399 0 : deathGrip->UpdateInitialMediaSize(aSize);
400 : }
401 :
402 0 : void NotifyRealtimeTrackData(MediaStreamGraph* aGraph,
403 : StreamTime aTrackOffset,
404 : const MediaSegment& aMedia) override
405 : {
406 0 : if (mInitialSizeFound) {
407 : return;
408 : }
409 :
410 0 : if (aMedia.GetType() != MediaSegment::VIDEO) {
411 0 : MOZ_ASSERT(false, "Should only lock on to a video track");
412 : return;
413 : }
414 :
415 0 : const VideoSegment& video = static_cast<const VideoSegment&>(aMedia);
416 0 : for (VideoSegment::ConstChunkIterator c(video); !c.IsEnded(); c.Next()) {
417 0 : if (c->mFrame.GetIntrinsicSize() != gfx::IntSize(0, 0)) {
418 0 : mInitialSizeFound = true;
419 : // This is fine to dispatch straight to main thread (instead of via
420 : // ...AfterStreamUpdate()) since it reflects state of the element,
421 : // not the stream. Events reflecting stream or track state should be
422 : // dispatched so their order is preserved.
423 0 : mMainThreadEventTarget->Dispatch(NewRunnableMethod<gfx::IntSize>(
424 : "dom::HTMLMediaElement::StreamSizeListener::ReceivedSize",
425 : this,
426 : &StreamSizeListener::ReceivedSize,
427 0 : c->mFrame.GetIntrinsicSize()));
428 0 : return;
429 : }
430 : }
431 : }
432 :
433 : private:
434 : // These fields may only be accessed on the main thread
435 : HTMLMediaElement* mElement;
436 : // We hold mElement->MainThreadEventTarget() here because the mElement could
437 : // be reset in Forget().
438 : nsCOMPtr<nsISerialEventTarget> mMainThreadEventTarget;
439 :
440 : // These fields may only be accessed on the MSG's appending thread.
441 : // (this is a direct listener so we get called by whoever is producing
442 : // this track's data)
443 : bool mInitialSizeFound;
444 : };
445 :
446 : /**
447 : * There is a reference cycle involving this class: MediaLoadListener
448 : * holds a reference to the HTMLMediaElement, which holds a reference
449 : * to an nsIChannel, which holds a reference to this listener.
450 : * We break the reference cycle in OnStartRequest by clearing mElement.
451 : */
452 : class HTMLMediaElement::MediaLoadListener final
453 : : public nsIStreamListener
454 : , public nsIChannelEventSink
455 : , public nsIInterfaceRequestor
456 : , public nsIObserver
457 : , public nsIThreadRetargetableStreamListener
458 : {
459 0 : ~MediaLoadListener() {}
460 :
461 : NS_DECL_THREADSAFE_ISUPPORTS
462 : NS_DECL_NSIREQUESTOBSERVER
463 : NS_DECL_NSISTREAMLISTENER
464 : NS_DECL_NSICHANNELEVENTSINK
465 : NS_DECL_NSIOBSERVER
466 : NS_DECL_NSIINTERFACEREQUESTOR
467 : NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
468 :
469 : public:
470 0 : explicit MediaLoadListener(HTMLMediaElement* aElement)
471 0 : : mElement(aElement)
472 0 : , mLoadID(aElement->GetCurrentLoadID())
473 : {
474 0 : MOZ_ASSERT(mElement, "Must pass an element to call back");
475 0 : }
476 :
477 : private:
478 : RefPtr<HTMLMediaElement> mElement;
479 : nsCOMPtr<nsIStreamListener> mNextListener;
480 : const uint32_t mLoadID;
481 : };
482 :
483 0 : NS_IMPL_ISUPPORTS(HTMLMediaElement::MediaLoadListener,
484 : nsIRequestObserver,
485 : nsIStreamListener,
486 : nsIChannelEventSink,
487 : nsIInterfaceRequestor,
488 : nsIObserver,
489 : nsIThreadRetargetableStreamListener)
490 :
491 : NS_IMETHODIMP
492 0 : HTMLMediaElement::MediaLoadListener::Observe(nsISupports* aSubject,
493 : const char* aTopic,
494 : const char16_t* aData)
495 : {
496 0 : nsContentUtils::UnregisterShutdownObserver(this);
497 :
498 : // Clear mElement to break cycle so we don't leak on shutdown
499 0 : mElement = nullptr;
500 0 : return NS_OK;
501 : }
502 :
503 : NS_IMETHODIMP
504 0 : HTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest* aRequest,
505 : nsISupports* aContext)
506 : {
507 0 : nsContentUtils::UnregisterShutdownObserver(this);
508 :
509 0 : if (!mElement) {
510 : // We've been notified by the shutdown observer, and are shutting down.
511 : return NS_BINDING_ABORTED;
512 : }
513 :
514 : // The element is only needed until we've had a chance to call
515 : // InitializeDecoderForChannel. So make sure mElement is cleared here.
516 0 : RefPtr<HTMLMediaElement> element;
517 0 : element.swap(mElement);
518 :
519 0 : AbstractThread::AutoEnter context(element->AbstractMainThread());
520 :
521 0 : if (mLoadID != element->GetCurrentLoadID()) {
522 : // The channel has been cancelled before we had a chance to create
523 : // a decoder. Abort, don't dispatch an "error" event, as the new load
524 : // may not be in an error state.
525 : return NS_BINDING_ABORTED;
526 : }
527 :
528 : // Don't continue to load if the request failed or has been canceled.
529 : nsresult status;
530 0 : nsresult rv = aRequest->GetStatus(&status);
531 0 : NS_ENSURE_SUCCESS(rv, rv);
532 0 : if (NS_FAILED(status)) {
533 0 : if (element) {
534 : // Handle media not loading error because source was a tracking URL.
535 : // We make a note of this media node by including it in a dedicated
536 : // array of blocked tracking nodes under its parent document.
537 0 : if (status == NS_ERROR_TRACKING_URI) {
538 0 : nsIDocument* ownerDoc = element->OwnerDoc();
539 0 : if (ownerDoc) {
540 0 : ownerDoc->AddBlockedTrackingNode(element);
541 : }
542 : }
543 0 : element->NotifyLoadError(
544 0 : nsPrintfCString("%u: %s", uint32_t(status), "Request failed"));
545 : }
546 0 : return status;
547 : }
548 :
549 0 : nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
550 : bool succeeded;
551 0 : if (hc && NS_SUCCEEDED(hc->GetRequestSucceeded(&succeeded)) && !succeeded) {
552 0 : uint32_t responseStatus = 0;
553 0 : Unused << hc->GetResponseStatus(&responseStatus);
554 0 : nsAutoCString statusText;
555 0 : Unused << hc->GetResponseStatusText(statusText);
556 0 : element->NotifyLoadError(
557 0 : nsPrintfCString("%u: %s", responseStatus, statusText.get()));
558 :
559 0 : nsAutoString code;
560 0 : code.AppendInt(responseStatus);
561 0 : nsAutoString src;
562 0 : element->GetCurrentSrc(src);
563 0 : const char16_t* params[] = { code.get(), src.get() };
564 0 : element->ReportLoadError("MediaLoadHttpError", params, ArrayLength(params));
565 0 : return NS_BINDING_ABORTED;
566 : }
567 :
568 0 : nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
569 0 : if (channel &&
570 0 : NS_SUCCEEDED(rv = element->InitializeDecoderForChannel(
571 0 : channel, getter_AddRefs(mNextListener))) &&
572 0 : mNextListener) {
573 0 : rv = mNextListener->OnStartRequest(aRequest, aContext);
574 : } else {
575 : // If InitializeDecoderForChannel() returned an error, fire a network error.
576 0 : if (NS_FAILED(rv) && !mNextListener) {
577 : // Load failed, attempt to load the next candidate resource. If there
578 : // are none, this will trigger a MEDIA_ERR_SRC_NOT_SUPPORTED error.
579 0 : element->NotifyLoadError(NS_LITERAL_CSTRING("Failed to init decoder"));
580 : }
581 : // If InitializeDecoderForChannel did not return a listener (but may
582 : // have otherwise succeeded), we abort the connection since we aren't
583 : // interested in keeping the channel alive ourselves.
584 : rv = NS_BINDING_ABORTED;
585 : }
586 :
587 : return rv;
588 : }
589 :
590 : NS_IMETHODIMP
591 0 : HTMLMediaElement::MediaLoadListener::OnStopRequest(nsIRequest* aRequest,
592 : nsISupports* aContext,
593 : nsresult aStatus)
594 : {
595 0 : if (mNextListener) {
596 0 : return mNextListener->OnStopRequest(aRequest, aContext, aStatus);
597 : }
598 : return NS_OK;
599 : }
600 :
601 : NS_IMETHODIMP
602 0 : HTMLMediaElement::MediaLoadListener::OnDataAvailable(nsIRequest* aRequest,
603 : nsISupports* aContext,
604 : nsIInputStream* aStream,
605 : uint64_t aOffset,
606 : uint32_t aCount)
607 : {
608 0 : if (!mNextListener) {
609 0 : NS_ERROR("Must have a chained listener; OnStartRequest should have "
610 : "canceled this request");
611 0 : return NS_BINDING_ABORTED;
612 : }
613 0 : return mNextListener->OnDataAvailable(
614 0 : aRequest, aContext, aStream, aOffset, aCount);
615 : }
616 :
617 : NS_IMETHODIMP
618 0 : HTMLMediaElement::MediaLoadListener::AsyncOnChannelRedirect(
619 : nsIChannel* aOldChannel,
620 : nsIChannel* aNewChannel,
621 : uint32_t aFlags,
622 : nsIAsyncVerifyRedirectCallback* cb)
623 : {
624 : // TODO is this really correct?? See bug #579329.
625 0 : if (mElement) {
626 0 : mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
627 : }
628 0 : nsCOMPtr<nsIChannelEventSink> sink = do_QueryInterface(mNextListener);
629 0 : if (sink) {
630 0 : return sink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, cb);
631 : }
632 0 : cb->OnRedirectVerifyCallback(NS_OK);
633 0 : return NS_OK;
634 : }
635 :
636 : NS_IMETHODIMP
637 0 : HTMLMediaElement::MediaLoadListener::CheckListenerChain()
638 : {
639 0 : MOZ_ASSERT(mNextListener);
640 : nsCOMPtr<nsIThreadRetargetableStreamListener> retargetable =
641 0 : do_QueryInterface(mNextListener);
642 0 : if (retargetable) {
643 0 : return retargetable->CheckListenerChain();
644 : }
645 : return NS_ERROR_NO_INTERFACE;
646 : }
647 :
648 : NS_IMETHODIMP
649 0 : HTMLMediaElement::MediaLoadListener::GetInterface(const nsIID& aIID,
650 : void** aResult)
651 : {
652 0 : return QueryInterface(aIID, aResult);
653 : }
654 :
655 : void
656 0 : HTMLMediaElement::ReportLoadError(const char* aMsg,
657 : const char16_t** aParams,
658 : uint32_t aParamCount)
659 : {
660 0 : nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
661 0 : NS_LITERAL_CSTRING("Media"),
662 0 : OwnerDoc(),
663 : nsContentUtils::eDOM_PROPERTIES,
664 : aMsg,
665 : aParams,
666 0 : aParamCount);
667 0 : }
668 :
669 : class HTMLMediaElement::AudioChannelAgentCallback final
670 : : public nsIAudioChannelAgentCallback
671 : {
672 : public:
673 : NS_DECL_CYCLE_COLLECTING_ISUPPORTS
674 0 : NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgentCallback)
675 :
676 0 : explicit AudioChannelAgentCallback(HTMLMediaElement* aOwner)
677 0 : : mOwner(aOwner)
678 : , mAudioChannelVolume(1.0)
679 : , mPlayingThroughTheAudioChannel(false)
680 : , mAudioCapturedByWindow(false)
681 : , mSuspended(nsISuspendedTypes::NONE_SUSPENDED)
682 0 : , mIsOwnerAudible(IsOwnerAudible())
683 0 : , mIsShutDown(false)
684 : {
685 0 : MOZ_ASSERT(mOwner);
686 0 : MaybeCreateAudioChannelAgent();
687 0 : }
688 :
689 0 : void UpdateAudioChannelPlayingState(bool aForcePlaying = false)
690 : {
691 0 : MOZ_ASSERT(!mIsShutDown);
692 : bool playingThroughTheAudioChannel =
693 0 : aForcePlaying || IsPlayingThroughTheAudioChannel();
694 :
695 0 : if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
696 0 : if (!MaybeCreateAudioChannelAgent()) {
697 : return;
698 : }
699 :
700 0 : mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
701 0 : NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
702 : }
703 : }
704 :
705 0 : bool ShouldResetSuspend() const
706 : {
707 : // The disposable-pause should be clear after media starts playing.
708 0 : if (!mOwner->Paused() &&
709 0 : mSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE) {
710 : return true;
711 : }
712 :
713 : // If the blocked media is paused, we don't need to resume it. We reset the
714 : // mSuspended in order to unregister the agent.
715 0 : if (mOwner->Paused() && mSuspended == nsISuspendedTypes::SUSPENDED_BLOCK) {
716 : return true;
717 : }
718 :
719 0 : return false;
720 : }
721 :
722 0 : void NotifyPlayStateChanged()
723 : {
724 0 : MOZ_ASSERT(!mIsShutDown);
725 0 : if (ShouldResetSuspend()) {
726 0 : SetSuspended(nsISuspendedTypes::NONE_SUSPENDED);
727 : NotifyAudioPlaybackChanged(
728 0 : AudioChannelService::AudibleChangedReasons::ePauseStateChanged);
729 : }
730 0 : UpdateAudioChannelPlayingState();
731 0 : }
732 :
733 0 : NS_IMETHODIMP WindowVolumeChanged(float aVolume, bool aMuted) override
734 : {
735 0 : MOZ_ASSERT(mAudioChannelAgent);
736 :
737 0 : MOZ_LOG(
738 : AudioChannelService::GetAudioChannelLog(),
739 : LogLevel::Debug,
740 : ("HTMLMediaElement::AudioChannelAgentCallback, WindowVolumeChanged, "
741 : "this = %p, aVolume = %f, aMuted = %s\n",
742 : this,
743 : aVolume,
744 : aMuted ? "true" : "false"));
745 :
746 0 : if (mAudioChannelVolume != aVolume) {
747 0 : mAudioChannelVolume = aVolume;
748 0 : mOwner->SetVolumeInternal();
749 : }
750 :
751 0 : const uint32_t muted = mOwner->mMuted;
752 0 : if (aMuted && !mOwner->ComputedMuted()) {
753 0 : mOwner->SetMutedInternal(muted | MUTED_BY_AUDIO_CHANNEL);
754 0 : } else if (!aMuted && mOwner->ComputedMuted()) {
755 0 : mOwner->SetMutedInternal(muted & ~MUTED_BY_AUDIO_CHANNEL);
756 : }
757 :
758 0 : return NS_OK;
759 : }
760 :
761 0 : NS_IMETHODIMP WindowSuspendChanged(SuspendTypes aSuspend) override
762 : {
763 0 : MOZ_ASSERT(mAudioChannelAgent);
764 :
765 0 : MOZ_LOG(
766 : AudioChannelService::GetAudioChannelLog(),
767 : LogLevel::Debug,
768 : ("HTMLMediaElement::AudioChannelAgentCallback, WindowSuspendChanged, "
769 : "this = %p, aSuspend = %s\n",
770 : this,
771 : SuspendTypeToStr(aSuspend)));
772 :
773 0 : switch (aSuspend) {
774 : case nsISuspendedTypes::NONE_SUSPENDED:
775 0 : Resume();
776 0 : break;
777 : case nsISuspendedTypes::SUSPENDED_PAUSE:
778 : case nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE:
779 : case nsISuspendedTypes::SUSPENDED_BLOCK:
780 0 : Suspend(aSuspend);
781 0 : break;
782 : case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE:
783 0 : Stop();
784 0 : break;
785 : default:
786 0 : MOZ_LOG(
787 : AudioChannelService::GetAudioChannelLog(),
788 : LogLevel::Debug,
789 : ("HTMLMediaElement::AudioChannelAgentCallback, WindowSuspendChanged, "
790 : "this = %p, Error : unknown suspended type!\n",
791 : this));
792 : }
793 0 : return NS_OK;
794 : }
795 :
796 0 : NS_IMETHODIMP WindowAudioCaptureChanged(bool aCapture) override
797 : {
798 0 : MOZ_ASSERT(mAudioChannelAgent);
799 :
800 0 : if (mAudioCapturedByWindow != aCapture) {
801 0 : mAudioCapturedByWindow = aCapture;
802 0 : AudioCaptureStreamChangeIfNeeded();
803 : }
804 0 : return NS_OK;
805 : }
806 :
807 0 : void AudioCaptureStreamChangeIfNeeded()
808 : {
809 0 : MOZ_ASSERT(!mIsShutDown);
810 0 : if (!IsPlayingStarted()) {
811 : return;
812 : }
813 :
814 0 : if (!mOwner->HasAudio()) {
815 : return;
816 : }
817 :
818 0 : mOwner->AudioCaptureStreamChange(mAudioCapturedByWindow);
819 : }
820 :
821 0 : void NotifyAudioPlaybackChanged(AudibleChangedReasons aReason)
822 : {
823 0 : MOZ_ASSERT(!mIsShutDown);
824 0 : if (!IsPlayingStarted()) {
825 : return;
826 : }
827 :
828 0 : AudibleState newAudibleState = IsOwnerAudible();
829 0 : if (mIsOwnerAudible == newAudibleState) {
830 : return;
831 : }
832 :
833 0 : mIsOwnerAudible = newAudibleState;
834 0 : mAudioChannelAgent->NotifyStartedAudible(mIsOwnerAudible, aReason);
835 : }
836 :
837 0 : bool IsPlaybackBlocked()
838 : {
839 0 : MOZ_ASSERT(!mIsShutDown);
840 : // If the tab hasn't been activated yet, the media element in that tab can't
841 : // be playback now until the tab goes to foreground first time or user
842 : // clicks the unblocking tab icon.
843 0 : if (!IsTabActivated()) {
844 : // Even we haven't start playing yet, we still need to notify the audio
845 : // channe system because we need to receive the resume notification later.
846 0 : UpdateAudioChannelPlayingState(true /* force to start */);
847 0 : return true;
848 : }
849 :
850 : return false;
851 : }
852 :
853 0 : void Shutdown()
854 : {
855 0 : MOZ_ASSERT(!mIsShutDown);
856 0 : if (mAudioChannelAgent) {
857 0 : mAudioChannelAgent->NotifyStoppedPlaying();
858 0 : mAudioChannelAgent = nullptr;
859 : }
860 0 : mIsShutDown = true;
861 0 : }
862 :
863 0 : float GetEffectiveVolume() const
864 : {
865 0 : MOZ_ASSERT(!mIsShutDown);
866 0 : return mOwner->Volume() * mAudioChannelVolume;
867 : }
868 :
869 0 : SuspendTypes GetSuspendType() const
870 : {
871 0 : MOZ_ASSERT(!mIsShutDown);
872 0 : return mSuspended;
873 : }
874 :
875 : private:
876 0 : ~AudioChannelAgentCallback() { MOZ_ASSERT(mIsShutDown); };
877 :
878 0 : bool MaybeCreateAudioChannelAgent()
879 : {
880 0 : if (mAudioChannelAgent) {
881 : return true;
882 : }
883 :
884 0 : mAudioChannelAgent = new AudioChannelAgent();
885 : nsresult rv =
886 0 : mAudioChannelAgent->Init(mOwner->OwnerDoc()->GetInnerWindow(), this);
887 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
888 0 : mAudioChannelAgent = nullptr;
889 0 : MOZ_LOG(
890 : AudioChannelService::GetAudioChannelLog(),
891 : LogLevel::Debug,
892 : ("HTMLMediaElement::AudioChannelAgentCallback, Fail to initialize "
893 : "the audio channel agent, this = %p\n",
894 : this));
895 : return false;
896 : }
897 :
898 : return true;
899 : }
900 :
901 0 : void NotifyAudioChannelAgent(bool aPlaying)
902 : {
903 0 : MOZ_ASSERT(mAudioChannelAgent);
904 :
905 0 : if (aPlaying) {
906 0 : AudioPlaybackConfig config;
907 : nsresult rv =
908 0 : mAudioChannelAgent->NotifyStartedPlaying(&config, IsOwnerAudible());
909 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
910 0 : return;
911 : }
912 :
913 0 : WindowVolumeChanged(config.mVolume, config.mMuted);
914 0 : WindowSuspendChanged(config.mSuspend);
915 : } else {
916 0 : mAudioChannelAgent->NotifyStoppedPlaying();
917 : }
918 : }
919 :
920 0 : void SetSuspended(SuspendTypes aSuspend)
921 : {
922 0 : if (mSuspended == aSuspend) {
923 : return;
924 : }
925 :
926 0 : MaybeNotifyMediaResumed(aSuspend);
927 0 : mSuspended = aSuspend;
928 0 : MOZ_LOG(
929 : AudioChannelService::GetAudioChannelLog(),
930 : LogLevel::Debug,
931 : ("HTMLMediaElement::AudioChannelAgentCallback, SetAudioChannelSuspended, "
932 : "this = %p, aSuspend = %s\n",
933 : this,
934 : SuspendTypeToStr(aSuspend)));
935 : }
936 :
937 0 : void Resume()
938 : {
939 0 : if (!IsSuspended()) {
940 0 : MOZ_LOG(
941 : AudioChannelService::GetAudioChannelLog(),
942 : LogLevel::Debug,
943 : ("HTMLMediaElement::AudioChannelAgentCallback, ResumeFromAudioChannel, "
944 : "this = %p, don't need to be resumed!\n",
945 : this));
946 0 : return;
947 : }
948 :
949 0 : SetSuspended(nsISuspendedTypes::NONE_SUSPENDED);
950 0 : IgnoredErrorResult rv;
951 0 : RefPtr<Promise> toBeIgnored = mOwner->Play(rv);
952 0 : MOZ_ASSERT_IF(toBeIgnored &&
953 : toBeIgnored->State() == Promise::PromiseState::Rejected,
954 : rv.Failed());
955 0 : if (rv.Failed()) {
956 0 : NS_WARNING("Not able to resume from AudioChannel.");
957 : }
958 :
959 : NotifyAudioPlaybackChanged(
960 0 : AudioChannelService::AudibleChangedReasons::ePauseStateChanged);
961 : }
962 :
963 0 : void Suspend(SuspendTypes aSuspend)
964 : {
965 0 : if (IsSuspended()) {
966 : return;
967 : }
968 :
969 0 : SetSuspended(aSuspend);
970 0 : if (aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE ||
971 0 : aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE) {
972 0 : IgnoredErrorResult rv;
973 0 : mOwner->Pause(rv);
974 0 : if (NS_WARN_IF(rv.Failed())) {
975 0 : return;
976 : }
977 : }
978 : NotifyAudioPlaybackChanged(
979 0 : AudioChannelService::AudibleChangedReasons::ePauseStateChanged);
980 : }
981 :
982 0 : void Stop()
983 : {
984 0 : SetSuspended(nsISuspendedTypes::NONE_SUSPENDED);
985 0 : mOwner->Pause();
986 0 : }
987 :
988 0 : bool IsPlayingStarted()
989 : {
990 0 : if (MaybeCreateAudioChannelAgent()) {
991 0 : return mAudioChannelAgent->IsPlayingStarted();
992 : }
993 : return false;
994 : }
995 :
996 0 : void MaybeNotifyMediaResumed(SuspendTypes aSuspend)
997 : {
998 : // In fennec, we should send the notification when media is resumed from the
999 : // pause-disposable which was called by media control.
1000 0 : if (mSuspended != nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE &&
1001 : aSuspend != nsISuspendedTypes::NONE_SUSPENDED) {
1002 : return;
1003 : }
1004 :
1005 0 : if (!IsPlayingStarted()) {
1006 : return;
1007 : }
1008 :
1009 0 : uint64_t windowID = mAudioChannelAgent->WindowID();
1010 0 : mOwner->MainThreadEventTarget()->Dispatch(NS_NewRunnableFunction(
1011 : "dom::HTMLMediaElement::AudioChannelAgentCallback::"
1012 : "MaybeNotifyMediaResumed",
1013 0 : [windowID]() -> void {
1014 : nsCOMPtr<nsIObserverService> observerService =
1015 0 : services::GetObserverService();
1016 0 : if (NS_WARN_IF(!observerService)) {
1017 0 : return;
1018 : }
1019 :
1020 : nsCOMPtr<nsISupportsPRUint64> wrapper =
1021 0 : do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
1022 0 : if (NS_WARN_IF(!wrapper)) {
1023 0 : return;
1024 : }
1025 :
1026 0 : wrapper->SetData(windowID);
1027 0 : observerService->NotifyObservers(
1028 0 : wrapper, "media-playback-resumed", u"active");
1029 0 : }));
1030 : }
1031 :
1032 0 : bool IsTabActivated()
1033 : {
1034 0 : if (MaybeCreateAudioChannelAgent()) {
1035 0 : return !mAudioChannelAgent->ShouldBlockMedia();
1036 : }
1037 : return false;
1038 : }
1039 :
1040 0 : bool IsSuspended() const
1041 : {
1042 0 : return (mSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
1043 0 : mSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE ||
1044 0 : mSuspended == nsISuspendedTypes::SUSPENDED_BLOCK);
1045 : }
1046 :
1047 0 : AudibleState IsOwnerAudible() const
1048 : {
1049 : // Muted or the volume should not be ~0
1050 0 : if (mOwner->mMuted || (std::fabs(mOwner->Volume()) <= 1e-7)) {
1051 0 : return mOwner->HasAudio()
1052 0 : ? AudioChannelService::AudibleState::eMaybeAudible
1053 : : AudioChannelService::AudibleState::eNotAudible;
1054 : }
1055 :
1056 : // No audio track.
1057 0 : if (!mOwner->HasAudio()) {
1058 : return AudioChannelService::AudibleState::eNotAudible;
1059 : }
1060 :
1061 : // Might be audible but not yet.
1062 0 : if (mOwner->HasAudio() && !mOwner->mIsAudioTrackAudible) {
1063 : return AudioChannelService::AudibleState::eMaybeAudible;
1064 : }
1065 :
1066 : // Suspended or paused media doesn't produce any sound.
1067 0 : if (mSuspended != nsISuspendedTypes::NONE_SUSPENDED || mOwner->mPaused) {
1068 : return AudioChannelService::AudibleState::eNotAudible;
1069 : }
1070 :
1071 0 : return AudioChannelService::AudibleState::eAudible;
1072 : }
1073 :
1074 0 : bool IsPlayingThroughTheAudioChannel() const
1075 : {
1076 : // If we have an error, we are not playing.
1077 0 : if (mOwner->GetError()) {
1078 : return false;
1079 : }
1080 :
1081 : // We should consider any bfcached page or inactive document as non-playing.
1082 0 : if (!mOwner->IsActive()) {
1083 : return false;
1084 : }
1085 :
1086 : // It might be resumed from remote, we should keep the audio channel agent.
1087 0 : if (IsSuspended()) {
1088 : return true;
1089 : }
1090 :
1091 : // Are we paused
1092 0 : if (mOwner->mPaused) {
1093 : return false;
1094 : }
1095 :
1096 : // No audio track
1097 0 : if (!mOwner->HasAudio()) {
1098 : return false;
1099 : }
1100 :
1101 : // A loop always is playing
1102 0 : if (mOwner->HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) {
1103 : return true;
1104 : }
1105 :
1106 : // If we are actually playing...
1107 0 : if (mOwner->IsCurrentlyPlaying()) {
1108 : return true;
1109 : }
1110 :
1111 : // If we are playing an external stream.
1112 0 : if (mOwner->mSrcAttrStream) {
1113 : return true;
1114 : }
1115 :
1116 0 : return false;
1117 : }
1118 :
1119 : RefPtr<AudioChannelAgent> mAudioChannelAgent;
1120 : HTMLMediaElement* mOwner;
1121 :
1122 : // The audio channel volume
1123 : float mAudioChannelVolume;
1124 : // Is this media element playing?
1125 : bool mPlayingThroughTheAudioChannel;
1126 : // True if the sound is being captured by the window.
1127 : bool mAudioCapturedByWindow;
1128 : // We have different kinds of suspended cases,
1129 : // - SUSPENDED_PAUSE
1130 : // It's used when we temporary lost platform audio focus. MediaElement can
1131 : // only be resumed when we gain the audio focus again.
1132 : // - SUSPENDED_PAUSE_DISPOSABLE
1133 : // It's used when user press the pause button on the remote media-control.
1134 : // MediaElement can be resumed by remote media-control or via play().
1135 : // - SUSPENDED_BLOCK
1136 : // It's used to reduce the power consumption, we won't play the auto-play
1137 : // audio/video in the page we have never visited before. MediaElement would
1138 : // be resumed when the page is active. See bug647429 for more details.
1139 : // - SUSPENDED_STOP_DISPOSABLE
1140 : // When we permanently lost platform audio focus, we should stop playing
1141 : // and stop the audio channel agent. MediaElement can only be restarted by
1142 : // play().
1143 : SuspendTypes mSuspended;
1144 : // Indicate whether media element is audible for users.
1145 : AudibleState mIsOwnerAudible;
1146 : bool mIsShutDown;
1147 : };
1148 :
1149 : NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::AudioChannelAgentCallback)
1150 :
1151 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
1152 : HTMLMediaElement::AudioChannelAgentCallback)
1153 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent)
1154 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1155 :
1156 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(
1157 : HTMLMediaElement::AudioChannelAgentCallback)
1158 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent)
1159 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1160 :
1161 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
1162 : HTMLMediaElement::AudioChannelAgentCallback)
1163 0 : NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
1164 0 : NS_INTERFACE_MAP_END
1165 :
1166 0 : NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLMediaElement::AudioChannelAgentCallback)
1167 0 : NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLMediaElement::AudioChannelAgentCallback)
1168 :
1169 0 : class HTMLMediaElement::ChannelLoader final
1170 : {
1171 : public:
1172 0 : NS_INLINE_DECL_REFCOUNTING(ChannelLoader);
1173 :
1174 0 : void LoadInternal(HTMLMediaElement* aElement)
1175 : {
1176 0 : if (mCancelled) {
1177 0 : return;
1178 : }
1179 :
1180 : // determine what security checks need to be performed in AsyncOpen2().
1181 : nsSecurityFlags securityFlags =
1182 0 : aElement->ShouldCheckAllowOrigin()
1183 0 : ? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
1184 0 : : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
1185 :
1186 0 : if (aElement->GetCORSMode() == CORS_USE_CREDENTIALS) {
1187 0 : securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
1188 : }
1189 :
1190 0 : MOZ_ASSERT(
1191 : aElement->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
1192 : nsContentPolicyType contentPolicyType =
1193 0 : aElement->IsHTMLElement(nsGkAtoms::audio)
1194 0 : ? nsIContentPolicy::TYPE_INTERNAL_AUDIO
1195 0 : : nsIContentPolicy::TYPE_INTERNAL_VIDEO;
1196 :
1197 : // If aElement has 'triggeringprincipal' attribute, we will use the value as
1198 : // triggeringPrincipal for the channel, otherwise it will default to use
1199 : // aElement->NodePrincipal().
1200 : // This function returns true when aElement has 'triggeringprincipal', so if
1201 : // setAttrs is true we will override the origin attributes on the channel
1202 : // later.
1203 0 : nsCOMPtr<nsIPrincipal> triggeringPrincipal;
1204 0 : bool setAttrs = nsContentUtils::QueryTriggeringPrincipal(
1205 : aElement,
1206 : aElement->mLoadingSrcTriggeringPrincipal,
1207 0 : getter_AddRefs(triggeringPrincipal));
1208 :
1209 0 : nsCOMPtr<nsILoadGroup> loadGroup = aElement->GetDocumentLoadGroup();
1210 0 : nsCOMPtr<nsIChannel> channel;
1211 0 : nsresult rv = NS_NewChannelWithTriggeringPrincipal(
1212 0 : getter_AddRefs(channel),
1213 : aElement->mLoadingSrc,
1214 : static_cast<Element*>(aElement),
1215 : triggeringPrincipal,
1216 : securityFlags,
1217 : contentPolicyType,
1218 : nullptr, // aPerformanceStorage
1219 : loadGroup,
1220 : nullptr, // aCallbacks
1221 : nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
1222 : nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE |
1223 0 : nsIChannel::LOAD_CLASSIFY_URI | nsIChannel::LOAD_CALL_CONTENT_SNIFFERS);
1224 :
1225 0 : if (NS_FAILED(rv)) {
1226 : // Notify load error so the element will try next resource candidate.
1227 0 : aElement->NotifyLoadError(NS_LITERAL_CSTRING("Fail to create channel"));
1228 0 : return;
1229 : }
1230 :
1231 0 : if (setAttrs) {
1232 0 : nsCOMPtr<nsILoadInfo> loadInfo = channel->GetLoadInfo();
1233 0 : if (loadInfo) {
1234 : // The function simply returns NS_OK, so we ignore the return value.
1235 0 : Unused << loadInfo->SetOriginAttributes(
1236 0 : triggeringPrincipal->OriginAttributesRef());
1237 : }
1238 : }
1239 :
1240 0 : nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
1241 0 : if (cos) {
1242 0 : if (aElement->mUseUrgentStartForChannel) {
1243 0 : cos->AddClassFlags(nsIClassOfService::UrgentStart);
1244 :
1245 : // Reset the flag to avoid loading again without initiated by user
1246 : // interaction.
1247 0 : aElement->mUseUrgentStartForChannel = false;
1248 : }
1249 :
1250 : // Unconditionally disable throttling since we want the media to fluently
1251 : // play even when we switch the tab to background.
1252 0 : cos->AddClassFlags(nsIClassOfService::DontThrottle);
1253 : }
1254 :
1255 : // The listener holds a strong reference to us. This creates a
1256 : // reference cycle, once we've set mChannel, which is manually broken
1257 : // in the listener's OnStartRequest method after it is finished with
1258 : // the element. The cycle will also be broken if we get a shutdown
1259 : // notification before OnStartRequest fires. Necko guarantees that
1260 : // OnStartRequest will eventually fire if we don't shut down first.
1261 0 : RefPtr<MediaLoadListener> loadListener = new MediaLoadListener(aElement);
1262 :
1263 0 : channel->SetNotificationCallbacks(loadListener);
1264 :
1265 0 : nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(channel);
1266 0 : if (hc) {
1267 : // Use a byte range request from the start of the resource.
1268 : // This enables us to detect if the stream supports byte range
1269 : // requests, and therefore seeking, early.
1270 0 : rv = hc->SetRequestHeader(
1271 0 : NS_LITERAL_CSTRING("Range"), NS_LITERAL_CSTRING("bytes=0-"), false);
1272 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
1273 0 : aElement->SetRequestHeaders(hc);
1274 : }
1275 :
1276 0 : rv = channel->AsyncOpen2(loadListener);
1277 0 : if (NS_FAILED(rv)) {
1278 : // Notify load error so the element will try next resource candidate.
1279 0 : aElement->NotifyLoadError(NS_LITERAL_CSTRING("Failed to open channel"));
1280 0 : return;
1281 : }
1282 :
1283 : // Else the channel must be open and starting to download. If it encounters
1284 : // a non-catastrophic failure, it will set a new task to continue loading
1285 : // another candidate. It's safe to set it as mChannel now.
1286 0 : mChannel = channel;
1287 :
1288 : // loadListener will be unregistered either on shutdown or when
1289 : // OnStartRequest for the channel we just opened fires.
1290 0 : nsContentUtils::RegisterShutdownObserver(loadListener);
1291 : }
1292 :
1293 0 : nsresult Load(HTMLMediaElement* aElement)
1294 : {
1295 0 : MOZ_ASSERT(aElement);
1296 : // Per bug 1235183 comment 8, we can't spin the event loop from stable
1297 : // state. Defer NS_NewChannel() to a new regular runnable.
1298 0 : return aElement->MainThreadEventTarget()->Dispatch(
1299 0 : NewRunnableMethod<HTMLMediaElement*>("ChannelLoader::LoadInternal",
1300 : this,
1301 : &ChannelLoader::LoadInternal,
1302 0 : aElement));
1303 : }
1304 :
1305 0 : void Cancel()
1306 : {
1307 0 : mCancelled = true;
1308 0 : if (mChannel) {
1309 0 : mChannel->Cancel(NS_BINDING_ABORTED);
1310 0 : mChannel = nullptr;
1311 : }
1312 0 : }
1313 :
1314 0 : void Done()
1315 : {
1316 0 : MOZ_ASSERT(mChannel);
1317 : // Decoder successfully created, the decoder now owns the MediaResource
1318 : // which owns the channel.
1319 0 : mChannel = nullptr;
1320 0 : }
1321 :
1322 0 : nsresult Redirect(nsIChannel* aChannel,
1323 : nsIChannel* aNewChannel,
1324 : uint32_t aFlags)
1325 : {
1326 0 : NS_ASSERTION(aChannel == mChannel, "Channels should match!");
1327 0 : mChannel = aNewChannel;
1328 :
1329 : // Handle forwarding of Range header so that the intial detection
1330 : // of seeking support (via result code 206) works across redirects.
1331 0 : nsCOMPtr<nsIHttpChannel> http = do_QueryInterface(aChannel);
1332 0 : NS_ENSURE_STATE(http);
1333 :
1334 0 : NS_NAMED_LITERAL_CSTRING(rangeHdr, "Range");
1335 :
1336 0 : nsAutoCString rangeVal;
1337 0 : if (NS_SUCCEEDED(http->GetRequestHeader(rangeHdr, rangeVal))) {
1338 0 : NS_ENSURE_STATE(!rangeVal.IsEmpty());
1339 :
1340 0 : http = do_QueryInterface(aNewChannel);
1341 0 : NS_ENSURE_STATE(http);
1342 :
1343 0 : nsresult rv = http->SetRequestHeader(rangeHdr, rangeVal, false);
1344 0 : NS_ENSURE_SUCCESS(rv, rv);
1345 : }
1346 :
1347 : return NS_OK;
1348 : }
1349 :
1350 : private:
1351 0 : ~ChannelLoader() { MOZ_ASSERT(!mChannel); }
1352 : // Holds a reference to the first channel we open to the media resource.
1353 : // Once the decoder is created, control over the channel passes to the
1354 : // decoder, and we null out this reference. We must store this in case
1355 : // we need to cancel the channel before control of it passes to the decoder.
1356 : nsCOMPtr<nsIChannel> mChannel;
1357 :
1358 : bool mCancelled = false;
1359 : };
1360 :
1361 0 : class HTMLMediaElement::ErrorSink
1362 : {
1363 : public:
1364 0 : explicit ErrorSink(HTMLMediaElement* aOwner)
1365 0 : : mOwner(aOwner)
1366 0 : , mSrcIsUnsupportedTypeMedia(false)
1367 : {
1368 0 : MOZ_ASSERT(mOwner);
1369 0 : }
1370 :
1371 0 : void SetError(uint16_t aErrorCode, const nsACString& aErrorDetails)
1372 : {
1373 : // Since we have multiple paths calling into DecodeError, e.g.
1374 : // MediaKeys::Terminated and EMEH264Decoder::Error. We should take the 1st
1375 : // one only in order not to fire multiple 'error' events.
1376 0 : if (mError) {
1377 : return;
1378 : }
1379 :
1380 0 : if (!IsValidErrorCode(aErrorCode)) {
1381 0 : NS_ASSERTION(false, "Undefined MediaError codes!");
1382 : return;
1383 : }
1384 :
1385 : // TODO : remove unsupported type related codes after finishing native
1386 : // support for HLS, see bug 1350842.
1387 0 : if (CanOwnerPlayUnsupportedTypeMedia() &&
1388 : aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED) {
1389 : // On Fennec, we do some hack for unsupported type media, we don't set
1390 : // its error state in order to open it with external app.
1391 : mSrcIsUnsupportedTypeMedia = true;
1392 : mOwner->ChangeNetworkState(NETWORK_NO_SOURCE);
1393 : MaybeOpenUnsupportedMediaForOwner();
1394 : } else {
1395 0 : mError = new MediaError(mOwner, aErrorCode, aErrorDetails);
1396 0 : mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("error"));
1397 0 : if (mOwner->ReadyState() == HAVE_NOTHING &&
1398 : aErrorCode == MEDIA_ERR_ABORTED) {
1399 : // https://html.spec.whatwg.org/multipage/embedded-content.html#media-data-processing-steps-list
1400 : // "If the media data fetching process is aborted by the user"
1401 0 : mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
1402 0 : mOwner->ChangeNetworkState(NETWORK_EMPTY);
1403 0 : mOwner->DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
1404 0 : if (mOwner->mDecoder) {
1405 0 : mOwner->ShutdownDecoder();
1406 : }
1407 0 : } else if (aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED) {
1408 0 : mOwner->ChangeNetworkState(NETWORK_NO_SOURCE);
1409 : } else {
1410 0 : mOwner->ChangeNetworkState(NETWORK_IDLE);
1411 : }
1412 : }
1413 : }
1414 :
1415 0 : void ResetError()
1416 : {
1417 0 : mError = nullptr;
1418 0 : mSrcIsUnsupportedTypeMedia = false;
1419 0 : }
1420 :
1421 : void MaybeOpenUnsupportedMediaForOwner() const
1422 : {
1423 : // Src is supported type or we don't open the pref for external app.
1424 : if (!mSrcIsUnsupportedTypeMedia || !CanOwnerPlayUnsupportedTypeMedia()) {
1425 : return;
1426 : }
1427 :
1428 : // If media doesn't start playing, we don't need to open it.
1429 : if (mOwner->Paused()) {
1430 : return;
1431 : }
1432 :
1433 : nsContentUtils::DispatchTrustedEvent(
1434 : mOwner->OwnerDoc(),
1435 : static_cast<nsIContent*>(mOwner),
1436 : NS_LITERAL_STRING("OpenMediaWithExternalApp"),
1437 : true,
1438 : true);
1439 : }
1440 :
1441 : RefPtr<MediaError> mError;
1442 :
1443 : private:
1444 : bool IsValidErrorCode(const uint16_t& aErrorCode) const
1445 : {
1446 : return (aErrorCode == MEDIA_ERR_DECODE || aErrorCode == MEDIA_ERR_NETWORK ||
1447 0 : aErrorCode == MEDIA_ERR_ABORTED ||
1448 : aErrorCode == MEDIA_ERR_SRC_NOT_SUPPORTED);
1449 : }
1450 :
1451 : bool CanOwnerPlayUnsupportedTypeMedia() const
1452 : {
1453 : #if defined(MOZ_WIDGET_ANDROID)
1454 : // On Fennec, we will use an external app to open unsupported media types.
1455 : return Preferences::GetBool("media.openUnsupportedTypeWithExternalApp");
1456 : #endif
1457 : return false;
1458 : }
1459 :
1460 : // Media elememt's life cycle would be longer than error sink, so we use the
1461 : // raw pointer and this class would only be referenced by media element.
1462 : HTMLMediaElement* mOwner;
1463 : bool mSrcIsUnsupportedTypeMedia;
1464 : };
1465 :
1466 : NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement)
1467 :
1468 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(HTMLMediaElement,
1469 : nsGenericHTMLElement)
1470 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaSource)
1471 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcMediaSource)
1472 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcStream)
1473 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSrcAttrStream)
1474 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourcePointer)
1475 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLoadBlockedDoc)
1476 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSourceLoadCandidate)
1477 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelWrapper)
1478 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mErrorSink->mError)
1479 0 : for (uint32_t i = 0; i < tmp->mOutputStreams.Length(); ++i) {
1480 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams[i].mStream)
1481 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutputStreams[i].mPreCreatedTracks)
1482 : }
1483 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlayed);
1484 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTextTrackManager)
1485 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioTrackList)
1486 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVideoTrackList)
1487 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeys)
1488 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncomingMediaKeys)
1489 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedVideoStreamTrack)
1490 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPlayPromises)
1491 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSeekDOMPromise)
1492 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSetMediaKeysDOMPromise)
1493 0 : NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1494 :
1495 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLMediaElement,
1496 : nsGenericHTMLElement)
1497 0 : tmp->RemoveMutationObserver(tmp);
1498 0 : if (tmp->mSrcStream) {
1499 : // Need to EndMediaStreamPlayback to clear mSrcStream and make sure
1500 : // everything gets unhooked correctly.
1501 0 : tmp->EndSrcMediaStreamPlayback();
1502 : }
1503 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcAttrStream)
1504 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource)
1505 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mSrcMediaSource)
1506 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourcePointer)
1507 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mLoadBlockedDoc)
1508 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mSourceLoadCandidate)
1509 0 : if (tmp->mAudioChannelWrapper) {
1510 0 : tmp->mAudioChannelWrapper->Shutdown();
1511 : }
1512 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelWrapper)
1513 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mErrorSink->mError)
1514 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutputStreams)
1515 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlayed)
1516 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mTextTrackManager)
1517 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioTrackList)
1518 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mVideoTrackList)
1519 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaKeys)
1520 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncomingMediaKeys)
1521 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mSelectedVideoStreamTrack)
1522 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPlayPromises)
1523 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mSeekDOMPromise)
1524 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK(mSetMediaKeysDOMPromise)
1525 0 : NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1526 :
1527 8 : NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED_0(HTMLMediaElement,
1528 : nsGenericHTMLElement)
1529 :
1530 : void
1531 0 : HTMLMediaElement::ContentRemoved(nsIContent* aChild,
1532 : nsIContent* aPreviousSibling)
1533 : {
1534 0 : if (aChild == mSourcePointer) {
1535 0 : mSourcePointer = aPreviousSibling;
1536 : }
1537 0 : }
1538 :
1539 : already_AddRefed<MediaSource>
1540 0 : HTMLMediaElement::GetMozMediaSourceObject() const
1541 : {
1542 0 : RefPtr<MediaSource> source = mMediaSource;
1543 0 : return source.forget();
1544 : }
1545 :
1546 : void
1547 0 : HTMLMediaElement::GetMozDebugReaderData(nsAString& aString)
1548 : {
1549 0 : if (mDecoder && !mSrcStream) {
1550 0 : nsAutoCString result;
1551 0 : mDecoder->GetMozDebugReaderData(result);
1552 0 : CopyUTF8toUTF16(result, aString);
1553 : }
1554 0 : }
1555 :
1556 : already_AddRefed<Promise>
1557 0 : HTMLMediaElement::MozRequestDebugInfo(ErrorResult& aRv)
1558 : {
1559 0 : RefPtr<Promise> promise = CreateDOMPromise(aRv);
1560 0 : if (NS_WARN_IF(aRv.Failed())) {
1561 : return nullptr;
1562 : }
1563 :
1564 0 : nsAutoString result;
1565 0 : GetMozDebugReaderData(result);
1566 :
1567 0 : if (mVideoFrameContainer) {
1568 0 : result.AppendPrintf(
1569 : "Compositor dropped frame(including when element's invisible): %u\n",
1570 0 : mVideoFrameContainer->GetDroppedImageCount());
1571 : }
1572 :
1573 0 : if (mMediaKeys) {
1574 0 : nsString EMEInfo;
1575 0 : GetEMEInfo(EMEInfo);
1576 0 : result.AppendLiteral("EME Info: ");
1577 0 : result.Append(EMEInfo);
1578 0 : result.AppendLiteral("\n");
1579 : }
1580 :
1581 0 : if (mDecoder) {
1582 0 : mDecoder->RequestDebugInfo()->Then(
1583 : mAbstractMainThread,
1584 : __func__,
1585 0 : [promise, result](const nsACString& aString) {
1586 0 : promise->MaybeResolve(result + NS_ConvertUTF8toUTF16(aString));
1587 0 : },
1588 0 : [promise, result]() { promise->MaybeResolve(result); });
1589 : } else {
1590 0 : promise->MaybeResolve(result);
1591 : }
1592 :
1593 0 : return promise.forget();
1594 : }
1595 :
1596 : /* static */ void
1597 0 : HTMLMediaElement::MozEnableDebugLog(const GlobalObject&)
1598 : {
1599 0 : DecoderDoctorLogger::EnableLogging();
1600 0 : }
1601 :
1602 : already_AddRefed<Promise>
1603 0 : HTMLMediaElement::MozRequestDebugLog(ErrorResult& aRv)
1604 : {
1605 0 : RefPtr<Promise> promise = CreateDOMPromise(aRv);
1606 0 : if (NS_WARN_IF(aRv.Failed())) {
1607 : return nullptr;
1608 : }
1609 :
1610 0 : DecoderDoctorLogger::RetrieveMessages(this)->Then(
1611 : mAbstractMainThread,
1612 : __func__,
1613 0 : [promise](const nsACString& aString) {
1614 0 : promise->MaybeResolve(NS_ConvertUTF8toUTF16(aString));
1615 0 : },
1616 0 : [promise](nsresult rv) { promise->MaybeReject(rv); });
1617 :
1618 : return promise.forget();
1619 : }
1620 :
1621 : already_AddRefed<Promise>
1622 0 : HTMLMediaElement::MozDumpDebugInfo()
1623 : {
1624 0 : ErrorResult rv;
1625 0 : RefPtr<Promise> promise = CreateDOMPromise(rv);
1626 0 : if (NS_WARN_IF(rv.Failed())) {
1627 : return nullptr;
1628 : }
1629 0 : if (mDecoder) {
1630 0 : mDecoder->DumpDebugInfo()->Then(mAbstractMainThread,
1631 : __func__,
1632 : promise.get(),
1633 0 : &Promise::MaybeResolveWithUndefined);
1634 : } else {
1635 0 : promise->MaybeResolveWithUndefined();
1636 : }
1637 : return promise.forget();
1638 : }
1639 :
1640 : void
1641 0 : HTMLMediaElement::SetVisible(bool aVisible)
1642 : {
1643 0 : mForcedHidden = !aVisible;
1644 0 : if (mDecoder) {
1645 0 : mDecoder->SetForcedHidden(!aVisible);
1646 : }
1647 0 : }
1648 :
1649 : already_AddRefed<layers::Image>
1650 0 : HTMLMediaElement::GetCurrentImage()
1651 : {
1652 0 : MarkAsTainted();
1653 :
1654 : // TODO: In bug 1345404, handle case when video decoder is already suspended.
1655 0 : ImageContainer* container = GetImageContainer();
1656 0 : if (!container) {
1657 : return nullptr;
1658 : }
1659 :
1660 0 : AutoLockImage lockImage(container);
1661 0 : RefPtr<layers::Image> image = lockImage.GetImage(TimeStamp::Now());
1662 0 : return image.forget();
1663 : }
1664 :
1665 : bool
1666 0 : HTMLMediaElement::HasSuspendTaint() const
1667 : {
1668 0 : MOZ_ASSERT(!mDecoder || (mDecoder->HasSuspendTaint() == mHasSuspendTaint));
1669 0 : return mHasSuspendTaint;
1670 : }
1671 :
1672 : already_AddRefed<DOMMediaStream>
1673 0 : HTMLMediaElement::GetSrcObject() const
1674 : {
1675 0 : NS_ASSERTION(!mSrcAttrStream || mSrcAttrStream->GetPlaybackStream(),
1676 : "MediaStream should have been set up properly");
1677 0 : RefPtr<DOMMediaStream> stream = mSrcAttrStream;
1678 0 : return stream.forget();
1679 : }
1680 :
1681 : void
1682 0 : HTMLMediaElement::SetSrcObject(DOMMediaStream& aValue)
1683 : {
1684 0 : SetSrcObject(&aValue);
1685 0 : }
1686 :
1687 : void
1688 0 : HTMLMediaElement::SetSrcObject(DOMMediaStream* aValue)
1689 : {
1690 0 : mSrcAttrStream = aValue;
1691 0 : UpdateAudioChannelPlayingState();
1692 0 : DoLoad();
1693 0 : }
1694 :
1695 : bool
1696 0 : HTMLMediaElement::Ended()
1697 : {
1698 0 : return (mDecoder && mDecoder->IsEnded()) ||
1699 0 : (mSrcStream && !mSrcStream->Active());
1700 : }
1701 :
1702 : void
1703 0 : HTMLMediaElement::GetCurrentSrc(nsAString& aCurrentSrc)
1704 : {
1705 0 : nsAutoCString src;
1706 0 : GetCurrentSpec(src);
1707 0 : CopyUTF8toUTF16(src, aCurrentSrc);
1708 0 : }
1709 :
1710 : nsresult
1711 0 : HTMLMediaElement::OnChannelRedirect(nsIChannel* aChannel,
1712 : nsIChannel* aNewChannel,
1713 : uint32_t aFlags)
1714 : {
1715 0 : MOZ_ASSERT(mChannelLoader);
1716 0 : return mChannelLoader->Redirect(aChannel, aNewChannel, aFlags);
1717 : }
1718 :
1719 : void
1720 0 : HTMLMediaElement::ShutdownDecoder()
1721 : {
1722 0 : RemoveMediaElementFromURITable();
1723 0 : NS_ASSERTION(mDecoder, "Must have decoder to shut down");
1724 :
1725 0 : mWaitingForKeyListener.DisconnectIfExists();
1726 0 : if (mMediaSource) {
1727 0 : mMediaSource->CompletePendingTransactions();
1728 : }
1729 0 : for (OutputMediaStream& out : mOutputStreams) {
1730 0 : if (!out.mCapturingDecoder) {
1731 : continue;
1732 : }
1733 0 : out.mNextAvailableTrackID = std::max<TrackID>(
1734 0 : mDecoder->NextAvailableTrackIDFor(out.mStream->GetInputStream()),
1735 0 : out.mNextAvailableTrackID);
1736 : }
1737 0 : mDecoder->Shutdown();
1738 0 : DDUNLINKCHILD(mDecoder.get());
1739 0 : mDecoder = nullptr;
1740 0 : }
1741 :
1742 : void
1743 0 : HTMLMediaElement::AbortExistingLoads()
1744 : {
1745 : // Abort any already-running instance of the resource selection algorithm.
1746 0 : mLoadWaitStatus = NOT_WAITING;
1747 :
1748 : // Set a new load ID. This will cause events which were enqueued
1749 : // with a different load ID to silently be cancelled.
1750 0 : mCurrentLoadID++;
1751 :
1752 : // Immediately reject or resolve the already-dispatched
1753 : // nsResolveOrRejectPendingPlayPromisesRunners. These runners won't be
1754 : // executed again later since the mCurrentLoadID had been changed.
1755 0 : for (auto& runner : mPendingPlayPromisesRunners) {
1756 0 : runner->ResolveOrReject();
1757 : }
1758 0 : mPendingPlayPromisesRunners.Clear();
1759 :
1760 0 : if (mChannelLoader) {
1761 0 : mChannelLoader->Cancel();
1762 0 : mChannelLoader = nullptr;
1763 : }
1764 :
1765 0 : bool fireTimeUpdate = false;
1766 :
1767 : // We need to remove StreamSizeListener before VideoTracks get emptied.
1768 0 : if (mMediaStreamSizeListener) {
1769 0 : mSelectedVideoStreamTrack->RemoveDirectListener(mMediaStreamSizeListener);
1770 0 : mMediaStreamSizeListener->Forget();
1771 0 : mMediaStreamSizeListener = nullptr;
1772 : }
1773 :
1774 : // When aborting the existing loads, empty the objects in audio track list and
1775 : // video track list, no events (in particular, no removetrack events) are
1776 : // fired as part of this. Ending MediaStream sends track ended notifications,
1777 : // so we empty the track lists prior.
1778 0 : AudioTracks()->EmptyTracks();
1779 0 : VideoTracks()->EmptyTracks();
1780 :
1781 0 : if (mDecoder) {
1782 0 : fireTimeUpdate = mDecoder->GetCurrentTime() != 0.0;
1783 0 : ShutdownDecoder();
1784 : }
1785 0 : if (mSrcStream) {
1786 0 : EndSrcMediaStreamPlayback();
1787 : }
1788 :
1789 0 : RemoveMediaElementFromURITable();
1790 0 : mLoadingSrc = nullptr;
1791 0 : mLoadingSrcTriggeringPrincipal = nullptr;
1792 0 : DDLOG(DDLogCategory::Property, "loading_src", "");
1793 0 : DDUNLINKCHILD(mMediaSource.get());
1794 0 : mMediaSource = nullptr;
1795 :
1796 0 : if (mNetworkState == NETWORK_LOADING || mNetworkState == NETWORK_IDLE) {
1797 0 : DispatchAsyncEvent(NS_LITERAL_STRING("abort"));
1798 : }
1799 :
1800 0 : mErrorSink->ResetError();
1801 0 : mCurrentPlayRangeStart = -1.0;
1802 0 : mLoadedDataFired = false;
1803 0 : mAutoplaying = true;
1804 0 : mIsLoadingFromSourceChildren = false;
1805 0 : mSuspendedAfterFirstFrame = false;
1806 0 : mAllowSuspendAfterFirstFrame = true;
1807 0 : mHaveQueuedSelectResource = false;
1808 0 : mSuspendedForPreloadNone = false;
1809 0 : mDownloadSuspendedByCache = false;
1810 0 : mMediaInfo = MediaInfo();
1811 0 : mIsEncrypted = false;
1812 0 : mPendingEncryptedInitData.Reset();
1813 0 : mWaitingForKey = NOT_WAITING_FOR_KEY;
1814 0 : mSourcePointer = nullptr;
1815 :
1816 0 : mTags = nullptr;
1817 :
1818 0 : if (mNetworkState != NETWORK_EMPTY) {
1819 0 : NS_ASSERTION(!mDecoder && !mSrcStream,
1820 : "How did someone setup a new stream/decoder already?");
1821 : // ChangeNetworkState() will call UpdateAudioChannelPlayingState()
1822 : // indirectly which depends on mPaused. So we need to update mPaused first.
1823 0 : if (!mPaused) {
1824 0 : mPaused = true;
1825 0 : DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
1826 0 : RejectPromises(TakePendingPlayPromises(), NS_ERROR_DOM_MEDIA_ABORT_ERR);
1827 : }
1828 0 : ChangeNetworkState(NETWORK_EMPTY);
1829 0 : ChangeReadyState(HAVE_NOTHING);
1830 :
1831 : // TODO: Apply the rules for text track cue rendering Bug 865407
1832 0 : if (mTextTrackManager) {
1833 0 : mTextTrackManager->GetTextTracks()->SetCuesInactive();
1834 : }
1835 :
1836 0 : if (fireTimeUpdate) {
1837 : // Since we destroyed the decoder above, the current playback position
1838 : // will now be reported as 0. The playback position was non-zero when
1839 : // we destroyed the decoder, so fire a timeupdate event so that the
1840 : // change will be reflected in the controls.
1841 0 : FireTimeUpdate(false);
1842 : }
1843 0 : DispatchAsyncEvent(NS_LITERAL_STRING("emptied"));
1844 0 : UpdateAudioChannelPlayingState();
1845 : }
1846 :
1847 : // We may have changed mPaused, mAutoplaying, and other
1848 : // things which can affect AddRemoveSelfReference
1849 0 : AddRemoveSelfReference();
1850 :
1851 0 : mIsRunningSelectResource = false;
1852 :
1853 0 : if (mTextTrackManager) {
1854 0 : mTextTrackManager->NotifyReset();
1855 : }
1856 :
1857 0 : mEventDeliveryPaused = false;
1858 0 : mPendingEvents.Clear();
1859 :
1860 0 : AssertReadyStateIsNothing();
1861 0 : }
1862 :
1863 : void
1864 0 : HTMLMediaElement::NoSupportedMediaSourceError(const nsACString& aErrorDetails)
1865 : {
1866 0 : if (mDecoder) {
1867 0 : ShutdownDecoder();
1868 : }
1869 0 : mErrorSink->SetError(MEDIA_ERR_SRC_NOT_SUPPORTED, aErrorDetails);
1870 0 : ChangeDelayLoadStatus(false);
1871 0 : UpdateAudioChannelPlayingState();
1872 0 : RejectPromises(TakePendingPlayPromises(),
1873 0 : NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR);
1874 0 : }
1875 :
1876 : typedef void (HTMLMediaElement::*SyncSectionFn)();
1877 :
1878 : // Runs a "synchronous section", a function that must run once the event loop
1879 : // has reached a "stable state". See:
1880 : // http://www.whatwg.org/specs/web-apps/current-work/multipage/webappapis.html#synchronous-section
1881 0 : class nsSyncSection : public nsMediaEvent
1882 : {
1883 : private:
1884 : nsCOMPtr<nsIRunnable> mRunnable;
1885 :
1886 : public:
1887 0 : nsSyncSection(HTMLMediaElement* aElement, nsIRunnable* aRunnable)
1888 0 : : nsMediaEvent("dom::nsSyncSection", aElement)
1889 0 : , mRunnable(aRunnable)
1890 : {
1891 0 : }
1892 :
1893 0 : NS_IMETHOD Run() override
1894 : {
1895 : // Silently cancel if our load has been cancelled.
1896 0 : if (IsCancelled())
1897 : return NS_OK;
1898 0 : mRunnable->Run();
1899 0 : return NS_OK;
1900 : }
1901 : };
1902 :
1903 : void
1904 0 : HTMLMediaElement::RunInStableState(nsIRunnable* aRunnable)
1905 : {
1906 0 : if (mShuttingDown) {
1907 0 : return;
1908 : }
1909 :
1910 0 : nsCOMPtr<nsIRunnable> event = new nsSyncSection(this, aRunnable);
1911 0 : nsContentUtils::RunInStableState(event.forget());
1912 : }
1913 :
1914 : void
1915 0 : HTMLMediaElement::QueueLoadFromSourceTask()
1916 : {
1917 0 : if (!mIsLoadingFromSourceChildren || mShuttingDown) {
1918 0 : return;
1919 : }
1920 :
1921 0 : if (mDecoder) {
1922 : // Reset readyState to HAVE_NOTHING since we're going to load a new decoder.
1923 0 : ShutdownDecoder();
1924 0 : ChangeReadyState(HAVE_NOTHING);
1925 : }
1926 :
1927 0 : AssertReadyStateIsNothing();
1928 :
1929 0 : ChangeDelayLoadStatus(true);
1930 0 : ChangeNetworkState(NETWORK_LOADING);
1931 : RefPtr<Runnable> r =
1932 0 : NewRunnableMethod("HTMLMediaElement::LoadFromSourceChildren",
1933 : this,
1934 0 : &HTMLMediaElement::LoadFromSourceChildren);
1935 0 : RunInStableState(r);
1936 : }
1937 :
1938 : void
1939 0 : HTMLMediaElement::QueueSelectResourceTask()
1940 : {
1941 : // Don't allow multiple async select resource calls to be queued.
1942 0 : if (mHaveQueuedSelectResource)
1943 0 : return;
1944 0 : mHaveQueuedSelectResource = true;
1945 0 : ChangeNetworkState(NETWORK_NO_SOURCE);
1946 : RefPtr<Runnable> r =
1947 0 : NewRunnableMethod("HTMLMediaElement::SelectResourceWrapper",
1948 : this,
1949 0 : &HTMLMediaElement::SelectResourceWrapper);
1950 0 : RunInStableState(r);
1951 : }
1952 :
1953 : static bool
1954 0 : HasSourceChildren(nsIContent* aElement)
1955 : {
1956 0 : for (nsIContent* child = aElement->GetFirstChild(); child;
1957 0 : child = child->GetNextSibling()) {
1958 0 : if (child->IsHTMLElement(nsGkAtoms::source)) {
1959 : return true;
1960 : }
1961 : }
1962 : return false;
1963 : }
1964 :
1965 : static nsCString
1966 0 : DocumentOrigin(nsIDocument* aDoc)
1967 : {
1968 0 : if (!aDoc) {
1969 0 : return NS_LITERAL_CSTRING("null");
1970 : }
1971 0 : nsCOMPtr<nsIPrincipal> principal = aDoc->NodePrincipal();
1972 0 : if (!principal) {
1973 0 : return NS_LITERAL_CSTRING("null");
1974 : }
1975 0 : nsCString origin;
1976 0 : if (NS_FAILED(principal->GetOrigin(origin))) {
1977 0 : return NS_LITERAL_CSTRING("null");
1978 : }
1979 0 : return origin;
1980 : }
1981 :
1982 : void
1983 0 : HTMLMediaElement::Load()
1984 : {
1985 0 : LOG(LogLevel::Debug,
1986 : ("%p Load() hasSrcAttrStream=%d hasSrcAttr=%d hasSourceChildren=%d "
1987 : "handlingInput=%d hasAutoplayAttr=%d IsAllowedToPlay=%d "
1988 : "ownerDoc=%p (%s) ownerDocUserActivated=%d "
1989 : "muted=%d volume=%f",
1990 : this,
1991 : !!mSrcAttrStream,
1992 : HasAttr(kNameSpaceID_None, nsGkAtoms::src),
1993 : HasSourceChildren(this),
1994 : EventStateManager::IsHandlingUserInput(),
1995 : HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay),
1996 : IsAllowedToPlay(),
1997 : OwnerDoc(),
1998 : DocumentOrigin(OwnerDoc()).get(),
1999 : OwnerDoc() ? OwnerDoc()->HasBeenUserActivated() : 0,
2000 : mMuted,
2001 : mVolume));
2002 :
2003 0 : if (mIsRunningLoadMethod) {
2004 : return;
2005 : }
2006 :
2007 0 : mIsDoingExplicitLoad = true;
2008 0 : DoLoad();
2009 : }
2010 :
2011 : void
2012 0 : HTMLMediaElement::DoLoad()
2013 : {
2014 : // Check if media is allowed for the docshell.
2015 0 : nsCOMPtr<nsIDocShell> docShell = OwnerDoc()->GetDocShell();
2016 0 : if (docShell && !docShell->GetAllowMedia()) {
2017 0 : LOG(LogLevel::Debug, ("%p Media not allowed", this));
2018 0 : return;
2019 : }
2020 :
2021 0 : if (mIsRunningLoadMethod) {
2022 : return;
2023 : }
2024 :
2025 0 : if (EventStateManager::IsHandlingUserInput()) {
2026 : // Detect if user has interacted with element so that play will not be
2027 : // blocked when initiated by a script. This enables sites to capture user
2028 : // intent to play by calling load() in the click handler of a "catalog
2029 : // view" of a gallery of videos.
2030 0 : mIsBlessed = true;
2031 : // Mark the channel as urgent-start when autopaly so that it will play the
2032 : // media from src after loading enough resource.
2033 0 : if (HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) {
2034 0 : mUseUrgentStartForChannel = true;
2035 : }
2036 : }
2037 :
2038 0 : SetPlayedOrSeeked(false);
2039 0 : mIsRunningLoadMethod = true;
2040 0 : AbortExistingLoads();
2041 0 : SetPlaybackRate(mDefaultPlaybackRate, IgnoreErrors());
2042 0 : QueueSelectResourceTask();
2043 0 : ResetState();
2044 0 : mIsRunningLoadMethod = false;
2045 : }
2046 :
2047 : void
2048 0 : HTMLMediaElement::ResetState()
2049 : {
2050 : // There might be a pending MediaDecoder::PlaybackPositionChanged() which
2051 : // will overwrite |mMediaInfo.mVideo.mDisplay| in UpdateMediaSize() to give
2052 : // staled videoWidth and videoHeight. We have to call ForgetElement() here
2053 : // such that the staled callbacks won't reach us.
2054 0 : if (mVideoFrameContainer) {
2055 0 : mVideoFrameContainer->ForgetElement();
2056 0 : mVideoFrameContainer = nullptr;
2057 : }
2058 0 : }
2059 :
2060 : void
2061 0 : HTMLMediaElement::SelectResourceWrapper()
2062 : {
2063 0 : SelectResource();
2064 0 : mIsRunningSelectResource = false;
2065 0 : mHaveQueuedSelectResource = false;
2066 0 : mIsDoingExplicitLoad = false;
2067 0 : }
2068 :
2069 : void
2070 0 : HTMLMediaElement::SelectResource()
2071 : {
2072 0 : if (!mSrcAttrStream && !HasAttr(kNameSpaceID_None, nsGkAtoms::src) &&
2073 0 : !HasSourceChildren(this)) {
2074 : // The media element has neither a src attribute nor any source
2075 : // element children, abort the load.
2076 0 : ChangeNetworkState(NETWORK_EMPTY);
2077 0 : ChangeDelayLoadStatus(false);
2078 0 : return;
2079 : }
2080 :
2081 0 : ChangeDelayLoadStatus(true);
2082 :
2083 0 : ChangeNetworkState(NETWORK_LOADING);
2084 0 : DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
2085 :
2086 : // Delay setting mIsRunningSeletResource until after UpdatePreloadAction
2087 : // so that we don't lose our state change by bailing out of the preload
2088 : // state update
2089 0 : UpdatePreloadAction();
2090 0 : mIsRunningSelectResource = true;
2091 :
2092 : // If we have a 'src' attribute, use that exclusively.
2093 0 : nsAutoString src;
2094 0 : if (mSrcAttrStream) {
2095 0 : SetupSrcMediaStreamPlayback(mSrcAttrStream);
2096 0 : } else if (GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
2097 0 : nsCOMPtr<nsIURI> uri;
2098 0 : MediaResult rv = NewURIFromString(src, getter_AddRefs(uri));
2099 0 : if (NS_SUCCEEDED(rv)) {
2100 0 : LOG(
2101 : LogLevel::Debug,
2102 : ("%p Trying load from src=%s", this, NS_ConvertUTF16toUTF8(src).get()));
2103 0 : NS_ASSERTION(
2104 : !mIsLoadingFromSourceChildren,
2105 : "Should think we're not loading from source children by default");
2106 :
2107 0 : RemoveMediaElementFromURITable();
2108 0 : mLoadingSrc = uri;
2109 0 : mLoadingSrcTriggeringPrincipal = mSrcAttrTriggeringPrincipal;
2110 0 : DDLOG(DDLogCategory::Property,
2111 : "loading_src",
2112 : nsCString(NS_ConvertUTF16toUTF8(src)));
2113 0 : mMediaSource = mSrcMediaSource;
2114 0 : DDLINKCHILD("mediasource", mMediaSource.get());
2115 0 : UpdatePreloadAction();
2116 0 : if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE && !mMediaSource) {
2117 : // preload:none media, suspend the load here before we make any
2118 : // network requests.
2119 0 : SuspendLoad();
2120 0 : return;
2121 : }
2122 :
2123 0 : rv = LoadResource();
2124 0 : if (NS_SUCCEEDED(rv)) {
2125 : return;
2126 : }
2127 : } else {
2128 0 : const char16_t* params[] = { src.get() };
2129 0 : ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
2130 0 : rv = MediaResult(rv.Code(), "MediaLoadInvalidURI");
2131 : }
2132 : // The media element has neither a src attribute nor a source element child:
2133 : // set the networkState to NETWORK_EMPTY, and abort these steps; the
2134 : // synchronous section ends.
2135 0 : mMainThreadEventTarget->Dispatch(NewRunnableMethod<nsCString>(
2136 : "HTMLMediaElement::NoSupportedMediaSourceError",
2137 : this,
2138 : &HTMLMediaElement::NoSupportedMediaSourceError,
2139 0 : rv.Description()));
2140 : } else {
2141 : // Otherwise, the source elements will be used.
2142 0 : mIsLoadingFromSourceChildren = true;
2143 0 : LoadFromSourceChildren();
2144 : }
2145 : }
2146 :
2147 : void
2148 0 : HTMLMediaElement::NotifyLoadError(const nsACString& aErrorDetails)
2149 : {
2150 0 : if (!mIsLoadingFromSourceChildren) {
2151 0 : LOG(LogLevel::Debug, ("NotifyLoadError(), no supported media error"));
2152 0 : NoSupportedMediaSourceError(aErrorDetails);
2153 0 : } else if (mSourceLoadCandidate) {
2154 0 : DispatchAsyncSourceError(mSourceLoadCandidate);
2155 0 : QueueLoadFromSourceTask();
2156 : } else {
2157 0 : NS_WARNING("Should know the source we were loading from!");
2158 : }
2159 0 : }
2160 :
2161 : void
2162 0 : HTMLMediaElement::NotifyMediaTrackEnabled(MediaTrack* aTrack)
2163 : {
2164 0 : MOZ_ASSERT(aTrack);
2165 0 : if (!aTrack) {
2166 0 : return;
2167 : }
2168 : #ifdef DEBUG
2169 0 : nsString id;
2170 0 : aTrack->GetId(id);
2171 :
2172 0 : LOG(LogLevel::Debug,
2173 : ("MediaElement %p %sTrack with id %s enabled",
2174 : this,
2175 : aTrack->AsAudioTrack() ? "Audio" : "Video",
2176 : NS_ConvertUTF16toUTF8(id).get()));
2177 : #endif
2178 :
2179 0 : MOZ_ASSERT((aTrack->AsAudioTrack() && aTrack->AsAudioTrack()->Enabled()) ||
2180 : (aTrack->AsVideoTrack() && aTrack->AsVideoTrack()->Selected()));
2181 :
2182 0 : if (aTrack->AsAudioTrack()) {
2183 0 : SetMutedInternal(mMuted & ~MUTED_BY_AUDIO_TRACK);
2184 0 : } else if (aTrack->AsVideoTrack()) {
2185 0 : if (!IsVideo()) {
2186 0 : MOZ_ASSERT(false);
2187 : return;
2188 : }
2189 0 : mDisableVideo = false;
2190 : } else {
2191 0 : MOZ_ASSERT(false, "Unknown track type");
2192 : }
2193 :
2194 0 : if (mSrcStream) {
2195 0 : if (aTrack->AsVideoTrack()) {
2196 0 : MOZ_ASSERT(!mSelectedVideoStreamTrack);
2197 0 : MOZ_ASSERT(!mMediaStreamSizeListener);
2198 :
2199 0 : mSelectedVideoStreamTrack = aTrack->AsVideoTrack()->GetVideoStreamTrack();
2200 0 : VideoFrameContainer* container = GetVideoFrameContainer();
2201 0 : if (mSrcStreamIsPlaying && container) {
2202 0 : mSelectedVideoStreamTrack->AddVideoOutput(container);
2203 : }
2204 0 : HTMLVideoElement* self = static_cast<HTMLVideoElement*>(this);
2205 0 : if (self->VideoWidth() <= 1 && self->VideoHeight() <= 1) {
2206 : // MediaInfo uses dummy values of 1 for width and height to
2207 : // mark video as valid. We need a new stream size listener
2208 : // if size is 0x0 or 1x1.
2209 0 : mMediaStreamSizeListener = new StreamSizeListener(this);
2210 0 : mSelectedVideoStreamTrack->AddDirectListener(mMediaStreamSizeListener);
2211 : }
2212 : }
2213 :
2214 0 : if (mReadyState == HAVE_NOTHING) {
2215 : // No MediaStreamTracks are captured until we have metadata.
2216 : return;
2217 : }
2218 0 : for (OutputMediaStream& ms : mOutputStreams) {
2219 0 : if (aTrack->AsVideoTrack() && ms.mCapturingAudioOnly) {
2220 : // If the output stream is for audio only we ignore video tracks.
2221 : continue;
2222 : }
2223 0 : AddCaptureMediaTrackToOutputStream(aTrack, ms);
2224 : }
2225 : }
2226 : }
2227 :
2228 : void
2229 0 : HTMLMediaElement::NotifyMediaTrackDisabled(MediaTrack* aTrack)
2230 : {
2231 0 : MOZ_ASSERT(aTrack);
2232 0 : if (!aTrack) {
2233 0 : return;
2234 : }
2235 : #ifdef DEBUG
2236 0 : nsString id;
2237 0 : aTrack->GetId(id);
2238 :
2239 0 : LOG(LogLevel::Debug,
2240 : ("MediaElement %p %sTrack with id %s disabled",
2241 : this,
2242 : aTrack->AsAudioTrack() ? "Audio" : "Video",
2243 : NS_ConvertUTF16toUTF8(id).get()));
2244 : #endif
2245 :
2246 0 : MOZ_ASSERT((!aTrack->AsAudioTrack() || !aTrack->AsAudioTrack()->Enabled()) &&
2247 : (!aTrack->AsVideoTrack() || !aTrack->AsVideoTrack()->Selected()));
2248 :
2249 0 : if (aTrack->AsAudioTrack()) {
2250 : // If we don't have any alive track , we don't need to mute MediaElement.
2251 0 : if (AudioTracks()->Length() > 0) {
2252 : bool shouldMute = true;
2253 0 : for (uint32_t i = 0; i < AudioTracks()->Length(); ++i) {
2254 0 : if ((*AudioTracks())[i]->Enabled()) {
2255 : shouldMute = false;
2256 : break;
2257 : }
2258 : }
2259 :
2260 0 : if (shouldMute) {
2261 0 : SetMutedInternal(mMuted | MUTED_BY_AUDIO_TRACK);
2262 : }
2263 : }
2264 0 : } else if (aTrack->AsVideoTrack()) {
2265 0 : if (mSrcStream) {
2266 0 : MOZ_ASSERT(mSelectedVideoStreamTrack);
2267 0 : if (mSelectedVideoStreamTrack && mMediaStreamSizeListener) {
2268 0 : mSelectedVideoStreamTrack->RemoveDirectListener(
2269 0 : mMediaStreamSizeListener);
2270 0 : mMediaStreamSizeListener->Forget();
2271 0 : mMediaStreamSizeListener = nullptr;
2272 : }
2273 0 : VideoFrameContainer* container = GetVideoFrameContainer();
2274 0 : if (mSrcStreamIsPlaying && container) {
2275 0 : mSelectedVideoStreamTrack->RemoveVideoOutput(container);
2276 : }
2277 0 : mSelectedVideoStreamTrack = nullptr;
2278 : }
2279 : }
2280 :
2281 0 : if (mReadyState == HAVE_NOTHING) {
2282 : // No MediaStreamTracks are captured until we have metadata, and code
2283 : // below doesn't do anything for captured decoders.
2284 : return;
2285 : }
2286 :
2287 0 : for (OutputMediaStream& ms : mOutputStreams) {
2288 0 : if (ms.mCapturingDecoder) {
2289 0 : MOZ_ASSERT(!ms.mCapturingMediaStream);
2290 : continue;
2291 : }
2292 0 : MOZ_ASSERT(ms.mCapturingMediaStream);
2293 0 : for (int32_t i = ms.mTrackPorts.Length() - 1; i >= 0; --i) {
2294 0 : if (ms.mTrackPorts[i].first() == aTrack->GetId()) {
2295 : // The source of this track just ended. Force-notify that it ended.
2296 : // If we bounce it to the MediaStreamGraph it might not be picked up,
2297 : // for instance if the MediaInputPort was destroyed in the same
2298 : // iteration as it was added.
2299 0 : MediaStreamTrack* outputTrack = ms.mStream->FindOwnedDOMTrack(
2300 0 : ms.mTrackPorts[i].second()->GetDestination(),
2301 0 : ms.mTrackPorts[i].second()->GetDestinationTrackId());
2302 0 : MOZ_ASSERT(outputTrack);
2303 0 : if (outputTrack) {
2304 0 : mMainThreadEventTarget->Dispatch(
2305 0 : NewRunnableMethod("MediaStreamTrack::OverrideEnded",
2306 : outputTrack,
2307 0 : &MediaStreamTrack::OverrideEnded));
2308 : }
2309 :
2310 0 : ms.mTrackPorts[i].second()->Destroy();
2311 0 : ms.mTrackPorts.RemoveElementAt(i);
2312 : break;
2313 : }
2314 : }
2315 : #ifdef DEBUG
2316 0 : for (auto pair : ms.mTrackPorts) {
2317 0 : MOZ_ASSERT(pair.first() != aTrack->GetId(),
2318 : "The same MediaTrack was forwarded to the output stream more "
2319 : "than once. This shouldn't happen.");
2320 : }
2321 : #endif
2322 : }
2323 : }
2324 :
2325 : void
2326 0 : HTMLMediaElement::NotifyMediaStreamTracksAvailable(DOMMediaStream* aStream)
2327 : {
2328 0 : if (!mSrcStream || mSrcStream != aStream) {
2329 : return;
2330 : }
2331 :
2332 0 : LOG(LogLevel::Debug, ("MediaElement %p MediaStream tracks available", this));
2333 :
2334 0 : mSrcStreamTracksAvailable = true;
2335 :
2336 0 : bool videoHasChanged = IsVideo() && HasVideo() != !VideoTracks()->IsEmpty();
2337 :
2338 0 : if (videoHasChanged) {
2339 : // We are a video element and HasVideo() changed so update the screen
2340 : // wakelock
2341 0 : NotifyOwnerDocumentActivityChanged();
2342 : }
2343 :
2344 0 : UpdateReadyStateInternal();
2345 : }
2346 :
2347 : void
2348 0 : HTMLMediaElement::DealWithFailedElement(nsIContent* aSourceElement)
2349 : {
2350 0 : if (mShuttingDown) {
2351 : return;
2352 : }
2353 :
2354 0 : DispatchAsyncSourceError(aSourceElement);
2355 0 : mMainThreadEventTarget->Dispatch(
2356 0 : NewRunnableMethod("HTMLMediaElement::QueueLoadFromSourceTask",
2357 : this,
2358 0 : &HTMLMediaElement::QueueLoadFromSourceTask));
2359 : }
2360 :
2361 : void
2362 0 : HTMLMediaElement::NotifyOutputTrackStopped(DOMMediaStream* aOwningStream,
2363 : TrackID aDestinationTrackID)
2364 : {
2365 0 : for (OutputMediaStream& ms : mOutputStreams) {
2366 0 : if (!ms.mCapturingMediaStream) {
2367 : continue;
2368 : }
2369 :
2370 0 : if (ms.mStream != aOwningStream) {
2371 : continue;
2372 : }
2373 :
2374 0 : for (int32_t i = ms.mTrackPorts.Length() - 1; i >= 0; --i) {
2375 0 : MediaInputPort* port = ms.mTrackPorts[i].second();
2376 0 : if (port->GetDestinationTrackId() != aDestinationTrackID) {
2377 : continue;
2378 : }
2379 :
2380 0 : port->Destroy();
2381 0 : ms.mTrackPorts.RemoveElementAt(i);
2382 0 : return;
2383 : }
2384 : }
2385 :
2386 : // An output track ended but its port is already gone.
2387 : // It was probably cleared by the removal of the source MediaTrack.
2388 : }
2389 :
2390 : void
2391 0 : HTMLMediaElement::LoadFromSourceChildren()
2392 : {
2393 0 : NS_ASSERTION(mDelayingLoadEvent,
2394 : "Should delay load event (if in document) during load");
2395 0 : NS_ASSERTION(mIsLoadingFromSourceChildren,
2396 : "Must remember we're loading from source children");
2397 :
2398 0 : AddMutationObserverUnlessExists(this);
2399 :
2400 : while (true) {
2401 0 : Element* child = GetNextSource();
2402 0 : if (!child) {
2403 : // Exhausted candidates, wait for more candidates to be appended to
2404 : // the media element.
2405 0 : mLoadWaitStatus = WAITING_FOR_SOURCE;
2406 0 : ChangeNetworkState(NETWORK_NO_SOURCE);
2407 0 : ChangeDelayLoadStatus(false);
2408 0 : ReportLoadError("MediaLoadExhaustedCandidates");
2409 0 : return;
2410 : }
2411 :
2412 : // Must have src attribute.
2413 0 : nsAutoString src;
2414 0 : if (!child->GetAttr(kNameSpaceID_None, nsGkAtoms::src, src)) {
2415 0 : ReportLoadError("MediaLoadSourceMissingSrc");
2416 0 : DealWithFailedElement(child);
2417 0 : return;
2418 : }
2419 :
2420 : // If we have a type attribute, it must be a supported type.
2421 0 : nsAutoString type;
2422 0 : if (child->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) {
2423 0 : DecoderDoctorDiagnostics diagnostics;
2424 0 : CanPlayStatus canPlay = GetCanPlay(type, &diagnostics);
2425 0 : diagnostics.StoreFormatDiagnostics(
2426 0 : OwnerDoc(), type, canPlay != CANPLAY_NO, __func__);
2427 0 : if (canPlay == CANPLAY_NO) {
2428 0 : const char16_t* params[] = { type.get(), src.get() };
2429 : ReportLoadError(
2430 0 : "MediaLoadUnsupportedTypeAttribute", params, ArrayLength(params));
2431 0 : DealWithFailedElement(child);
2432 : return;
2433 : }
2434 : }
2435 0 : HTMLSourceElement* childSrc = HTMLSourceElement::FromNode(child);
2436 0 : LOG(LogLevel::Debug,
2437 : ("%p Trying load from <source>=%s type=%s",
2438 : this,
2439 : NS_ConvertUTF16toUTF8(src).get(),
2440 : NS_ConvertUTF16toUTF8(type).get()));
2441 :
2442 0 : nsCOMPtr<nsIURI> uri;
2443 0 : NewURIFromString(src, getter_AddRefs(uri));
2444 0 : if (!uri) {
2445 0 : const char16_t* params[] = { src.get() };
2446 0 : ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
2447 0 : DealWithFailedElement(child);
2448 : return;
2449 : }
2450 :
2451 0 : RemoveMediaElementFromURITable();
2452 0 : mLoadingSrc = uri;
2453 0 : mLoadingSrcTriggeringPrincipal = childSrc->GetSrcTriggeringPrincipal();
2454 0 : DDLOG(DDLogCategory::Property,
2455 : "loading_src",
2456 : nsCString(NS_ConvertUTF16toUTF8(src)));
2457 0 : mMediaSource = childSrc->GetSrcMediaSource();
2458 0 : DDLINKCHILD("mediasource", mMediaSource.get());
2459 0 : NS_ASSERTION(mNetworkState == NETWORK_LOADING,
2460 : "Network state should be loading");
2461 :
2462 0 : if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE && !mMediaSource) {
2463 : // preload:none media, suspend the load here before we make any
2464 : // network requests.
2465 0 : SuspendLoad();
2466 0 : return;
2467 : }
2468 :
2469 0 : if (NS_SUCCEEDED(LoadResource())) {
2470 : return;
2471 : }
2472 :
2473 : // If we fail to load, loop back and try loading the next resource.
2474 0 : DispatchAsyncSourceError(child);
2475 : }
2476 : NS_NOTREACHED("Execution should not reach here!");
2477 : }
2478 :
2479 : void
2480 0 : HTMLMediaElement::SuspendLoad()
2481 : {
2482 0 : mSuspendedForPreloadNone = true;
2483 0 : ChangeNetworkState(NETWORK_IDLE);
2484 0 : ChangeDelayLoadStatus(false);
2485 0 : }
2486 :
2487 : void
2488 0 : HTMLMediaElement::ResumeLoad(PreloadAction aAction)
2489 : {
2490 0 : NS_ASSERTION(mSuspendedForPreloadNone,
2491 : "Must be halted for preload:none to resume from preload:none "
2492 : "suspended load.");
2493 0 : mSuspendedForPreloadNone = false;
2494 0 : mPreloadAction = aAction;
2495 0 : ChangeDelayLoadStatus(true);
2496 0 : ChangeNetworkState(NETWORK_LOADING);
2497 0 : if (!mIsLoadingFromSourceChildren) {
2498 : // We were loading from the element's src attribute.
2499 0 : MediaResult rv = LoadResource();
2500 0 : if (NS_FAILED(rv)) {
2501 0 : NoSupportedMediaSourceError(rv.Description());
2502 : }
2503 : } else {
2504 : // We were loading from a child <source> element. Try to resume the
2505 : // load of that child, and if that fails, try the next child.
2506 0 : if (NS_FAILED(LoadResource())) {
2507 0 : LoadFromSourceChildren();
2508 : }
2509 : }
2510 0 : }
2511 :
2512 : void
2513 1 : HTMLMediaElement::UpdatePreloadAction()
2514 : {
2515 1 : PreloadAction nextAction = PRELOAD_UNDEFINED;
2516 : // If autoplay is set, or we're playing, we should always preload data,
2517 : // as we'll need it to play.
2518 3 : if ((AutoplayPolicy::IsMediaElementAllowedToPlay(WrapNotNull(this)) &&
2519 2 : HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) ||
2520 2 : !mPaused) {
2521 : nextAction = HTMLMediaElement::PRELOAD_ENOUGH;
2522 : } else {
2523 : // Find the appropriate preload action by looking at the attribute.
2524 : const nsAttrValue* val =
2525 0 : mAttrsAndChildren.GetAttr(nsGkAtoms::preload, kNameSpaceID_None);
2526 : // MSE doesn't work if preload is none, so it ignores the pref when src is
2527 : // from MSE.
2528 : uint32_t preloadDefault =
2529 : mMediaSource
2530 2 : ? HTMLMediaElement::PRELOAD_ATTR_METADATA
2531 0 : : Preferences::GetInt("media.preload.default",
2532 3 : HTMLMediaElement::PRELOAD_ATTR_METADATA);
2533 0 : uint32_t preloadAuto = Preferences::GetInt(
2534 0 : "media.preload.auto", HTMLMediaElement::PRELOAD_ENOUGH);
2535 0 : if (!val) {
2536 : // Attribute is not set. Use the preload action specified by the
2537 : // media.preload.default pref, or just preload metadata if not present.
2538 : nextAction = static_cast<PreloadAction>(preloadDefault);
2539 0 : } else if (val->Type() == nsAttrValue::eEnum) {
2540 : PreloadAttrValue attr =
2541 0 : static_cast<PreloadAttrValue>(val->GetEnumValue());
2542 0 : if (attr == HTMLMediaElement::PRELOAD_ATTR_EMPTY ||
2543 0 : attr == HTMLMediaElement::PRELOAD_ATTR_AUTO) {
2544 : nextAction = static_cast<PreloadAction>(preloadAuto);
2545 0 : } else if (attr == HTMLMediaElement::PRELOAD_ATTR_METADATA) {
2546 : nextAction = HTMLMediaElement::PRELOAD_METADATA;
2547 0 : } else if (attr == HTMLMediaElement::PRELOAD_ATTR_NONE) {
2548 0 : nextAction = HTMLMediaElement::PRELOAD_NONE;
2549 : }
2550 : } else {
2551 : // Use the suggested "missing value default" of "metadata", or the value
2552 : // specified by the media.preload.default, if present.
2553 : nextAction = static_cast<PreloadAction>(preloadDefault);
2554 : }
2555 : }
2556 :
2557 1 : if (nextAction == HTMLMediaElement::PRELOAD_NONE && mIsDoingExplicitLoad) {
2558 0 : nextAction = HTMLMediaElement::PRELOAD_METADATA;
2559 : }
2560 :
2561 1 : mPreloadAction = nextAction;
2562 :
2563 1 : if (nextAction == HTMLMediaElement::PRELOAD_ENOUGH) {
2564 0 : if (mSuspendedForPreloadNone) {
2565 : // Our load was previouly suspended due to the media having preload
2566 : // value "none". The preload value has changed to preload:auto, so
2567 : // resume the load.
2568 0 : ResumeLoad(PRELOAD_ENOUGH);
2569 : } else {
2570 : // Preload as much of the video as we can, i.e. don't suspend after
2571 : // the first frame.
2572 0 : StopSuspendingAfterFirstFrame();
2573 : }
2574 :
2575 0 : } else if (nextAction == HTMLMediaElement::PRELOAD_METADATA) {
2576 : // Ensure that the video can be suspended after first frame.
2577 1 : mAllowSuspendAfterFirstFrame = true;
2578 0 : if (mSuspendedForPreloadNone) {
2579 : // Our load was previouly suspended due to the media having preload
2580 : // value "none". The preload value has changed to preload:metadata, so
2581 : // resume the load. We'll pause the load again after we've read the
2582 : // metadata.
2583 0 : ResumeLoad(PRELOAD_METADATA);
2584 : }
2585 : }
2586 1 : }
2587 :
2588 : MediaResult
2589 0 : HTMLMediaElement::LoadResource()
2590 : {
2591 0 : AbstractThread::AutoEnter context(AbstractMainThread());
2592 :
2593 0 : NS_ASSERTION(mDelayingLoadEvent,
2594 : "Should delay load event (if in document) during load");
2595 :
2596 0 : if (mChannelLoader) {
2597 0 : mChannelLoader->Cancel();
2598 0 : mChannelLoader = nullptr;
2599 : }
2600 :
2601 : // Set the media element's CORS mode only when loading a resource
2602 0 : mCORSMode = AttrValueToCORSMode(GetParsedAttr(nsGkAtoms::crossorigin));
2603 :
2604 0 : HTMLMediaElement* other = LookupMediaElementURITable(mLoadingSrc);
2605 0 : if (other && other->mDecoder) {
2606 : // Clone it.
2607 : // TODO: remove the cast by storing ChannelMediaDecoder in the URI table.
2608 : nsresult rv = InitializeDecoderAsClone(
2609 0 : static_cast<ChannelMediaDecoder*>(other->mDecoder.get()));
2610 0 : if (NS_SUCCEEDED(rv))
2611 : return rv;
2612 : }
2613 :
2614 0 : if (mMediaSource) {
2615 : MediaDecoderInit decoderInit(
2616 : this,
2617 0 : mMuted ? 0.0 : mVolume,
2618 0 : mPreservesPitch,
2619 : mPlaybackRate,
2620 0 : mPreloadAction == HTMLMediaElement::PRELOAD_METADATA,
2621 0 : mHasSuspendTaint,
2622 0 : HasAttr(kNameSpaceID_None, nsGkAtoms::loop),
2623 0 : MediaContainerType(MEDIAMIMETYPE("application/x.mediasource")));
2624 :
2625 0 : RefPtr<MediaSourceDecoder> decoder = new MediaSourceDecoder(decoderInit);
2626 0 : if (!mMediaSource->Attach(decoder)) {
2627 : // TODO: Handle failure: run "If the media data cannot be fetched at
2628 : // all, due to network errors, causing the user agent to give up
2629 : // trying to fetch the resource" section of resource fetch algorithm.
2630 0 : decoder->Shutdown();
2631 0 : return MediaResult(NS_ERROR_FAILURE, "Failed to attach MediaSource");
2632 : }
2633 0 : ChangeDelayLoadStatus(false);
2634 0 : nsresult rv = decoder->Load(mMediaSource->GetPrincipal());
2635 0 : if (NS_FAILED(rv)) {
2636 0 : decoder->Shutdown();
2637 0 : LOG(LogLevel::Debug,
2638 : ("%p Failed to load for decoder %p", this, decoder.get()));
2639 0 : return MediaResult(rv, "Fail to load decoder");
2640 : }
2641 0 : rv = FinishDecoderSetup(decoder);
2642 0 : return MediaResult(rv, "Failed to set up decoder");
2643 : }
2644 :
2645 0 : AssertReadyStateIsNothing();
2646 :
2647 0 : RefPtr<ChannelLoader> loader = new ChannelLoader;
2648 0 : nsresult rv = loader->Load(this);
2649 0 : if (NS_SUCCEEDED(rv)) {
2650 0 : mChannelLoader = loader.forget();
2651 : }
2652 0 : return MediaResult(rv, "Failed to load channel");
2653 : }
2654 :
2655 : nsresult
2656 0 : HTMLMediaElement::LoadWithChannel(nsIChannel* aChannel,
2657 : nsIStreamListener** aListener)
2658 : {
2659 0 : NS_ENSURE_ARG_POINTER(aChannel);
2660 0 : NS_ENSURE_ARG_POINTER(aListener);
2661 :
2662 0 : *aListener = nullptr;
2663 :
2664 : // Make sure we don't reenter during synchronous abort events.
2665 0 : if (mIsRunningLoadMethod)
2666 : return NS_OK;
2667 0 : mIsRunningLoadMethod = true;
2668 0 : AbortExistingLoads();
2669 0 : mIsRunningLoadMethod = false;
2670 :
2671 0 : mLoadingSrcTriggeringPrincipal = nullptr;
2672 0 : nsresult rv = aChannel->GetOriginalURI(getter_AddRefs(mLoadingSrc));
2673 0 : NS_ENSURE_SUCCESS(rv, rv);
2674 :
2675 0 : ChangeDelayLoadStatus(true);
2676 0 : rv = InitializeDecoderForChannel(aChannel, aListener);
2677 0 : if (NS_FAILED(rv)) {
2678 0 : ChangeDelayLoadStatus(false);
2679 0 : return rv;
2680 : }
2681 :
2682 0 : SetPlaybackRate(mDefaultPlaybackRate, IgnoreErrors());
2683 0 : DispatchAsyncEvent(NS_LITERAL_STRING("loadstart"));
2684 :
2685 0 : return NS_OK;
2686 : }
2687 :
2688 : bool
2689 0 : HTMLMediaElement::Seeking() const
2690 : {
2691 0 : return mDecoder && mDecoder->IsSeeking();
2692 : }
2693 :
2694 : double
2695 0 : HTMLMediaElement::CurrentTime() const
2696 : {
2697 0 : if (MediaStream* stream = GetSrcMediaStream()) {
2698 0 : if (mSrcStreamPausedCurrentTime >= 0) {
2699 : return mSrcStreamPausedCurrentTime;
2700 : }
2701 0 : return stream->StreamTimeToSeconds(stream->GetCurrentTime());
2702 : }
2703 :
2704 0 : if (mDefaultPlaybackStartPosition == 0.0 && mDecoder) {
2705 0 : return mDecoder->GetCurrentTime();
2706 : }
2707 :
2708 : return mDefaultPlaybackStartPosition;
2709 : }
2710 :
2711 : void
2712 0 : HTMLMediaElement::FastSeek(double aTime, ErrorResult& aRv)
2713 : {
2714 0 : LOG(LogLevel::Debug, ("%p FastSeek(%f) called by JS", this, aTime));
2715 0 : LOG(LogLevel::Debug, ("Reporting telemetry VIDEO_FASTSEEK_USED"));
2716 0 : Telemetry::Accumulate(Telemetry::VIDEO_FASTSEEK_USED, 1);
2717 0 : RefPtr<Promise> tobeDropped = Seek(aTime, SeekTarget::PrevSyncPoint, aRv);
2718 0 : }
2719 :
2720 : already_AddRefed<Promise>
2721 0 : HTMLMediaElement::SeekToNextFrame(ErrorResult& aRv)
2722 : {
2723 0 : if (mSeekDOMPromise) {
2724 : // We can't perform NextFrameSeek while seek is already in action.
2725 : // Just return the pending seek promise.
2726 0 : return do_AddRef(mSeekDOMPromise);
2727 : }
2728 :
2729 : /* This will cause JIT code to be kept around longer, to help performance
2730 : * when using SeekToNextFrame to iterate through every frame of a video.
2731 : */
2732 0 : nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
2733 :
2734 0 : if (win) {
2735 0 : if (JSObject* obj = win->AsGlobal()->GetGlobalJSObject()) {
2736 0 : js::NotifyAnimationActivity(obj);
2737 : }
2738 : }
2739 :
2740 0 : return Seek(CurrentTime(), SeekTarget::NextFrame, aRv);
2741 : }
2742 :
2743 : void
2744 0 : HTMLMediaElement::SetCurrentTime(double aCurrentTime, ErrorResult& aRv)
2745 : {
2746 0 : LOG(LogLevel::Debug,
2747 : ("%p SetCurrentTime(%f) called by JS", this, aCurrentTime));
2748 0 : RefPtr<Promise> tobeDropped = Seek(aCurrentTime, SeekTarget::Accurate, aRv);
2749 0 : }
2750 :
2751 : /**
2752 : * Check if aValue is inside a range of aRanges, and if so returns true
2753 : * and puts the range index in aIntervalIndex. If aValue is not
2754 : * inside a range, returns false, and aIntervalIndex
2755 : * is set to the index of the range which starts immediately after aValue
2756 : * (and can be aRanges.Length() if aValue is after the last range).
2757 : */
2758 : static bool
2759 0 : IsInRanges(TimeRanges& aRanges, double aValue, uint32_t& aIntervalIndex)
2760 : {
2761 0 : uint32_t length = aRanges.Length();
2762 :
2763 0 : for (uint32_t i = 0; i < length; i++) {
2764 0 : double start = aRanges.Start(i);
2765 0 : if (start > aValue) {
2766 0 : aIntervalIndex = i;
2767 0 : return false;
2768 : }
2769 0 : double end = aRanges.End(i);
2770 0 : if (aValue <= end) {
2771 0 : aIntervalIndex = i;
2772 0 : return true;
2773 : }
2774 : }
2775 0 : aIntervalIndex = length;
2776 0 : return false;
2777 : }
2778 :
2779 : already_AddRefed<Promise>
2780 0 : HTMLMediaElement::Seek(double aTime,
2781 : SeekTarget::Type aSeekType,
2782 : ErrorResult& aRv)
2783 : {
2784 : // aTime should be non-NaN.
2785 0 : MOZ_ASSERT(!mozilla::IsNaN(aTime));
2786 :
2787 0 : RefPtr<Promise> promise = CreateDOMPromise(aRv);
2788 :
2789 0 : if (NS_WARN_IF(aRv.Failed())) {
2790 : return nullptr;
2791 : }
2792 :
2793 : // Detect if user has interacted with element by seeking so that
2794 : // play will not be blocked when initiated by a script.
2795 0 : if (EventStateManager::IsHandlingUserInput()) {
2796 0 : mIsBlessed = true;
2797 : }
2798 :
2799 0 : StopSuspendingAfterFirstFrame();
2800 :
2801 0 : if (mSrcStream) {
2802 : // do nothing since media streams have an empty Seekable range.
2803 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
2804 : return promise.forget();
2805 : }
2806 :
2807 0 : if (mPlayed && mCurrentPlayRangeStart != -1.0) {
2808 0 : double rangeEndTime = CurrentTime();
2809 0 : LOG(LogLevel::Debug,
2810 : ("%p Adding \'played\' a range : [%f, %f]",
2811 : this,
2812 : mCurrentPlayRangeStart,
2813 : rangeEndTime));
2814 : // Multiple seek without playing, or seek while playing.
2815 0 : if (mCurrentPlayRangeStart != rangeEndTime) {
2816 0 : mPlayed->Add(mCurrentPlayRangeStart, rangeEndTime);
2817 : }
2818 : // Reset the current played range start time. We'll re-set it once
2819 : // the seek completes.
2820 0 : mCurrentPlayRangeStart = -1.0;
2821 : }
2822 :
2823 0 : if (mReadyState == HAVE_NOTHING) {
2824 0 : mDefaultPlaybackStartPosition = aTime;
2825 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
2826 : return promise.forget();
2827 : }
2828 :
2829 0 : if (!mDecoder) {
2830 : // mDecoder must always be set in order to reach this point.
2831 0 : NS_ASSERTION(mDecoder, "SetCurrentTime failed: no decoder");
2832 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
2833 : return promise.forget();
2834 : }
2835 :
2836 : // Clamp the seek target to inside the seekable ranges.
2837 0 : media::TimeIntervals seekableIntervals = mDecoder->GetSeekable();
2838 0 : if (seekableIntervals.IsInvalid()) {
2839 0 : aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); // This will reject the promise.
2840 : return promise.forget();
2841 : }
2842 : RefPtr<TimeRanges> seekable =
2843 0 : new TimeRanges(ToSupports(OwnerDoc()), seekableIntervals);
2844 0 : uint32_t length = seekable->Length();
2845 0 : if (length == 0) {
2846 0 : promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR);
2847 : return promise.forget();
2848 : }
2849 :
2850 : // If the position we want to seek to is not in a seekable range, we seek
2851 : // to the closest position in the seekable ranges instead. If two positions
2852 : // are equally close, we seek to the closest position from the currentTime.
2853 : // See seeking spec, point 7 :
2854 : // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-video-element.html#seeking
2855 0 : uint32_t range = 0;
2856 0 : bool isInRange = IsInRanges(*seekable, aTime, range);
2857 0 : if (!isInRange) {
2858 0 : if (range == 0) {
2859 : // aTime is before the first range in |seekable|, the closest point we can
2860 : // seek to is the start of the first range.
2861 0 : aTime = seekable->Start(0);
2862 0 : } else if (range == length) {
2863 : // Seek target is after the end last range in seekable data.
2864 : // Clamp the seek target to the end of the last seekable range.
2865 0 : aTime = seekable->End(length - 1);
2866 : } else {
2867 0 : double leftBound = seekable->End(range - 1);
2868 0 : double rightBound = seekable->Start(range);
2869 0 : double distanceLeft = Abs(leftBound - aTime);
2870 0 : double distanceRight = Abs(rightBound - aTime);
2871 0 : if (distanceLeft == distanceRight) {
2872 0 : double currentTime = CurrentTime();
2873 0 : distanceLeft = Abs(leftBound - currentTime);
2874 0 : distanceRight = Abs(rightBound - currentTime);
2875 : }
2876 0 : aTime = (distanceLeft < distanceRight) ? leftBound : rightBound;
2877 : }
2878 : }
2879 :
2880 : // TODO: The spec requires us to update the current time to reflect the
2881 : // actual seek target before beginning the synchronous section, but
2882 : // that requires changing all MediaDecoderReaders to support telling
2883 : // us the fastSeek target, and it's currently not possible to get
2884 : // this information as we don't yet control the demuxer for all
2885 : // MediaDecoderReaders.
2886 :
2887 0 : mPlayingBeforeSeek = IsPotentiallyPlaying();
2888 :
2889 : // The media backend is responsible for dispatching the timeupdate
2890 : // event if it changes the playback position as a result of the seek.
2891 0 : LOG(LogLevel::Debug, ("%p SetCurrentTime(%f) starting seek", this, aTime));
2892 0 : mDecoder->Seek(aTime, aSeekType);
2893 :
2894 : // We changed whether we're seeking so we need to AddRemoveSelfReference.
2895 0 : AddRemoveSelfReference();
2896 :
2897 : // Keep the DOM promise.
2898 0 : mSeekDOMPromise = promise;
2899 :
2900 : return promise.forget();
2901 : }
2902 :
2903 : double
2904 0 : HTMLMediaElement::Duration() const
2905 : {
2906 0 : if (mSrcStream) {
2907 : return std::numeric_limits<double>::infinity();
2908 : }
2909 :
2910 0 : if (mDecoder) {
2911 0 : return mDecoder->GetDuration();
2912 : }
2913 :
2914 : return std::numeric_limits<double>::quiet_NaN();
2915 : }
2916 :
2917 : already_AddRefed<TimeRanges>
2918 0 : HTMLMediaElement::Seekable() const
2919 : {
2920 : media::TimeIntervals seekable =
2921 0 : mDecoder ? mDecoder->GetSeekable() : media::TimeIntervals();
2922 0 : RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()), seekable);
2923 0 : return ranges.forget();
2924 : }
2925 :
2926 : already_AddRefed<TimeRanges>
2927 0 : HTMLMediaElement::Played()
2928 : {
2929 0 : RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()));
2930 :
2931 0 : uint32_t timeRangeCount = 0;
2932 0 : if (mPlayed) {
2933 0 : timeRangeCount = mPlayed->Length();
2934 : }
2935 0 : for (uint32_t i = 0; i < timeRangeCount; i++) {
2936 0 : double begin = mPlayed->Start(i);
2937 0 : double end = mPlayed->End(i);
2938 0 : ranges->Add(begin, end);
2939 : }
2940 :
2941 0 : if (mCurrentPlayRangeStart != -1.0) {
2942 0 : double now = CurrentTime();
2943 0 : if (mCurrentPlayRangeStart != now) {
2944 0 : ranges->Add(mCurrentPlayRangeStart, now);
2945 : }
2946 : }
2947 :
2948 0 : ranges->Normalize();
2949 0 : return ranges.forget();
2950 : }
2951 :
2952 : void
2953 0 : HTMLMediaElement::Pause(ErrorResult& aRv)
2954 : {
2955 0 : LOG(LogLevel::Debug, ("%p Pause() called by JS", this));
2956 0 : if (mNetworkState == NETWORK_EMPTY) {
2957 0 : LOG(LogLevel::Debug, ("Loading due to Pause()"));
2958 0 : DoLoad();
2959 0 : } else if (mDecoder) {
2960 0 : mDecoder->Pause();
2961 : }
2962 :
2963 0 : bool oldPaused = mPaused;
2964 0 : mPaused = true;
2965 0 : mAutoplaying = false;
2966 : // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
2967 0 : AddRemoveSelfReference();
2968 0 : UpdateSrcMediaStreamPlaying();
2969 0 : if (mAudioChannelWrapper) {
2970 0 : mAudioChannelWrapper->NotifyPlayStateChanged();
2971 : }
2972 :
2973 0 : if (!oldPaused) {
2974 0 : FireTimeUpdate(false);
2975 0 : DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
2976 0 : AsyncRejectPendingPlayPromises(NS_ERROR_DOM_MEDIA_ABORT_ERR);
2977 : }
2978 0 : }
2979 :
2980 : void
2981 1 : HTMLMediaElement::SetVolume(double aVolume, ErrorResult& aRv)
2982 : {
2983 1 : LOG(LogLevel::Debug, ("%p SetVolume(%f) called by JS", this, aVolume));
2984 :
2985 0 : if (aVolume < 0.0 || aVolume > 1.0) {
2986 0 : aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
2987 0 : return;
2988 : }
2989 :
2990 0 : if (aVolume == mVolume)
2991 : return;
2992 :
2993 0 : mVolume = aVolume;
2994 :
2995 : // Here we want just to update the volume.
2996 0 : SetVolumeInternal();
2997 :
2998 0 : DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
2999 :
3000 : // We allow inaudible autoplay. But changing our volume may make this
3001 : // media audible. So pause if we are no longer supposed to be autoplaying.
3002 0 : PauseIfShouldNotBePlaying();
3003 : }
3004 :
3005 : void
3006 0 : HTMLMediaElement::MozGetMetadata(JSContext* cx,
3007 : JS::MutableHandle<JSObject*> aRetval,
3008 : ErrorResult& aRv)
3009 : {
3010 0 : if (mReadyState < HAVE_METADATA) {
3011 0 : aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
3012 0 : return;
3013 : }
3014 :
3015 0 : JS::Rooted<JSObject*> tags(cx, JS_NewPlainObject(cx));
3016 0 : if (!tags) {
3017 0 : aRv.Throw(NS_ERROR_FAILURE);
3018 0 : return;
3019 : }
3020 0 : if (mTags) {
3021 0 : for (auto iter = mTags->ConstIter(); !iter.Done(); iter.Next()) {
3022 0 : nsString wideValue;
3023 0 : CopyUTF8toUTF16(iter.UserData(), wideValue);
3024 : JS::Rooted<JSString*> string(cx,
3025 0 : JS_NewUCStringCopyZ(cx, wideValue.Data()));
3026 0 : if (!string || !JS_DefineProperty(
3027 0 : cx, tags, iter.Key().Data(), string, JSPROP_ENUMERATE)) {
3028 0 : NS_WARNING("couldn't create metadata object!");
3029 0 : aRv.Throw(NS_ERROR_FAILURE);
3030 0 : return;
3031 : }
3032 : }
3033 : }
3034 :
3035 0 : aRetval.set(tags);
3036 : }
3037 :
3038 : void
3039 0 : HTMLMediaElement::SetMutedInternal(uint32_t aMuted)
3040 : {
3041 0 : uint32_t oldMuted = mMuted;
3042 0 : mMuted = aMuted;
3043 :
3044 0 : if (!!aMuted == !!oldMuted) {
3045 : return;
3046 : }
3047 :
3048 0 : SetVolumeInternal();
3049 : }
3050 :
3051 : void
3052 0 : HTMLMediaElement::PauseIfShouldNotBePlaying()
3053 : {
3054 0 : if (GetPaused()) {
3055 : return;
3056 : }
3057 0 : if (!AutoplayPolicy::IsMediaElementAllowedToPlay(WrapNotNull(this))) {
3058 0 : ErrorResult rv;
3059 0 : Pause(rv);
3060 : }
3061 : }
3062 :
3063 : void
3064 0 : HTMLMediaElement::SetVolumeInternal()
3065 : {
3066 0 : float effectiveVolume = ComputedVolume();
3067 :
3068 0 : if (mDecoder) {
3069 0 : mDecoder->SetVolume(effectiveVolume);
3070 0 : } else if (MediaStream* stream = GetSrcMediaStream()) {
3071 0 : if (mSrcStreamIsPlaying) {
3072 0 : stream->SetAudioOutputVolume(this, effectiveVolume);
3073 : }
3074 : }
3075 :
3076 : NotifyAudioPlaybackChanged(
3077 0 : AudioChannelService::AudibleChangedReasons::eVolumeChanged);
3078 0 : }
3079 :
3080 : void
3081 0 : HTMLMediaElement::SetMuted(bool aMuted)
3082 : {
3083 0 : LOG(LogLevel::Debug, ("%p SetMuted(%d) called by JS", this, aMuted));
3084 0 : if (aMuted == Muted()) {
3085 : return;
3086 : }
3087 :
3088 0 : if (aMuted) {
3089 0 : SetMutedInternal(mMuted | MUTED_BY_CONTENT);
3090 : } else {
3091 0 : SetMutedInternal(mMuted & ~MUTED_BY_CONTENT);
3092 : }
3093 :
3094 0 : DispatchAsyncEvent(NS_LITERAL_STRING("volumechange"));
3095 :
3096 : // We allow inaudible autoplay. But changing our mute status may make this
3097 : // media audible. So pause if we are no longer supposed to be autoplaying.
3098 0 : PauseIfShouldNotBePlaying();
3099 : }
3100 :
3101 : class HTMLMediaElement::StreamCaptureTrackSource
3102 : : public MediaStreamTrackSource
3103 : , public MediaStreamTrackSource::Sink
3104 : {
3105 : public:
3106 : NS_DECL_ISUPPORTS_INHERITED
3107 0 : NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(StreamCaptureTrackSource,
3108 : MediaStreamTrackSource)
3109 :
3110 0 : StreamCaptureTrackSource(HTMLMediaElement* aElement,
3111 : MediaStreamTrackSource* aCapturedTrackSource,
3112 : DOMMediaStream* aOwningStream,
3113 : TrackID aDestinationTrackID)
3114 0 : : MediaStreamTrackSource(aCapturedTrackSource->GetPrincipal(), nsString())
3115 : , mElement(aElement)
3116 : , mCapturedTrackSource(aCapturedTrackSource)
3117 : , mOwningStream(aOwningStream)
3118 0 : , mDestinationTrackID(aDestinationTrackID)
3119 : {
3120 0 : MOZ_ASSERT(mElement);
3121 0 : MOZ_ASSERT(mCapturedTrackSource);
3122 0 : MOZ_ASSERT(mOwningStream);
3123 0 : MOZ_ASSERT(IsTrackIDExplicit(mDestinationTrackID));
3124 :
3125 0 : mCapturedTrackSource->RegisterSink(this);
3126 0 : }
3127 :
3128 0 : void Destroy() override
3129 : {
3130 0 : if (mCapturedTrackSource) {
3131 0 : mCapturedTrackSource->UnregisterSink(this);
3132 0 : mCapturedTrackSource = nullptr;
3133 : }
3134 0 : }
3135 :
3136 0 : MediaSourceEnum GetMediaSource() const override
3137 : {
3138 0 : return MediaSourceEnum::Other;
3139 : }
3140 :
3141 0 : CORSMode GetCORSMode() const override
3142 : {
3143 0 : if (!mCapturedTrackSource) {
3144 : // This could happen during shutdown.
3145 : return CORS_NONE;
3146 : }
3147 :
3148 0 : return mCapturedTrackSource->GetCORSMode();
3149 : }
3150 :
3151 0 : void Stop() override
3152 : {
3153 0 : if (mElement && mElement->mSrcStream) {
3154 : // Only notify if we're still playing the source stream. GC might have
3155 : // cleared it before the track sources.
3156 0 : mElement->NotifyOutputTrackStopped(mOwningStream, mDestinationTrackID);
3157 : }
3158 0 : mElement = nullptr;
3159 0 : mOwningStream = nullptr;
3160 :
3161 0 : Destroy();
3162 0 : }
3163 :
3164 : /**
3165 : * Do not keep the track source alive. The source lifetime is controlled by
3166 : * its associated tracks.
3167 : */
3168 0 : bool KeepsSourceAlive() const override { return false; }
3169 :
3170 : /**
3171 : * Do not keep the track source on. It is controlled by its associated tracks.
3172 : */
3173 0 : bool Enabled() const override { return false; }
3174 :
3175 0 : void Disable() override {}
3176 :
3177 0 : void Enable() override {}
3178 :
3179 0 : void PrincipalChanged() override
3180 : {
3181 0 : if (!mCapturedTrackSource) {
3182 : // This could happen during shutdown.
3183 : return;
3184 : }
3185 :
3186 0 : mPrincipal = mCapturedTrackSource->GetPrincipal();
3187 0 : MediaStreamTrackSource::PrincipalChanged();
3188 : }
3189 :
3190 0 : void MutedChanged(bool aNewState) override
3191 : {
3192 0 : if (!mCapturedTrackSource) {
3193 : // This could happen during shutdown.
3194 : return;
3195 : }
3196 :
3197 0 : MediaStreamTrackSource::MutedChanged(aNewState);
3198 : }
3199 :
3200 : private:
3201 0 : virtual ~StreamCaptureTrackSource() {}
3202 :
3203 : RefPtr<HTMLMediaElement> mElement;
3204 : RefPtr<MediaStreamTrackSource> mCapturedTrackSource;
3205 : RefPtr<DOMMediaStream> mOwningStream;
3206 : TrackID mDestinationTrackID;
3207 : };
3208 :
3209 0 : NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
3210 : MediaStreamTrackSource)
3211 0 : NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
3212 : MediaStreamTrackSource)
3213 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
3214 : HTMLMediaElement::StreamCaptureTrackSource)
3215 0 : NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
3216 0 : NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::StreamCaptureTrackSource,
3217 : MediaStreamTrackSource,
3218 : mElement,
3219 : mCapturedTrackSource,
3220 : mOwningStream)
3221 :
3222 : class HTMLMediaElement::DecoderCaptureTrackSource
3223 : : public MediaStreamTrackSource
3224 : , public DecoderPrincipalChangeObserver
3225 : {
3226 : public:
3227 : NS_DECL_ISUPPORTS_INHERITED
3228 0 : NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DecoderCaptureTrackSource,
3229 : MediaStreamTrackSource)
3230 :
3231 0 : explicit DecoderCaptureTrackSource(HTMLMediaElement* aElement)
3232 0 : : MediaStreamTrackSource(
3233 0 : nsCOMPtr<nsIPrincipal>(aElement->GetCurrentPrincipal()).get(),
3234 0 : nsString())
3235 0 : , mElement(aElement)
3236 : {
3237 0 : MOZ_ASSERT(mElement);
3238 0 : mElement->AddDecoderPrincipalChangeObserver(this);
3239 0 : }
3240 :
3241 0 : void Destroy() override
3242 : {
3243 0 : if (mElement) {
3244 : DebugOnly<bool> res =
3245 0 : mElement->RemoveDecoderPrincipalChangeObserver(this);
3246 0 : NS_ASSERTION(res,
3247 : "Removing decoder principal changed observer failed. "
3248 : "Had it already been removed?");
3249 0 : mElement = nullptr;
3250 : }
3251 0 : }
3252 :
3253 0 : MediaSourceEnum GetMediaSource() const override
3254 : {
3255 0 : return MediaSourceEnum::Other;
3256 : }
3257 :
3258 0 : CORSMode GetCORSMode() const override
3259 : {
3260 0 : if (!mElement) {
3261 0 : MOZ_ASSERT(false, "Should always have an element if in use");
3262 : return CORS_NONE;
3263 : }
3264 :
3265 0 : return mElement->GetCORSMode();
3266 : }
3267 :
3268 0 : void Stop() override
3269 : {
3270 : // We don't notify the source that a track was stopped since it will keep
3271 : // producing tracks until the element ends. The decoder also needs the
3272 : // tracks it created to be live at the source since the decoder's clock is
3273 : // based on MediaStreams during capture.
3274 0 : }
3275 :
3276 0 : void Disable() override {}
3277 :
3278 0 : void Enable() override {}
3279 :
3280 0 : void NotifyDecoderPrincipalChanged() override
3281 : {
3282 0 : nsCOMPtr<nsIPrincipal> newPrincipal = mElement->GetCurrentPrincipal();
3283 0 : if (nsContentUtils::CombineResourcePrincipals(&mPrincipal, newPrincipal)) {
3284 0 : PrincipalChanged();
3285 : }
3286 0 : }
3287 :
3288 : protected:
3289 0 : virtual ~DecoderCaptureTrackSource() {}
3290 :
3291 : RefPtr<HTMLMediaElement> mElement;
3292 : };
3293 :
3294 0 : NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource,
3295 : MediaStreamTrackSource)
3296 0 : NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource,
3297 : MediaStreamTrackSource)
3298 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
3299 : HTMLMediaElement::DecoderCaptureTrackSource)
3300 0 : NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
3301 0 : NS_IMPL_CYCLE_COLLECTION_INHERITED(HTMLMediaElement::DecoderCaptureTrackSource,
3302 : MediaStreamTrackSource,
3303 : mElement)
3304 :
3305 : class HTMLMediaElement::CaptureStreamTrackSourceGetter
3306 : : public MediaStreamTrackSourceGetter
3307 : {
3308 : public:
3309 : NS_DECL_ISUPPORTS_INHERITED
3310 0 : NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(CaptureStreamTrackSourceGetter,
3311 : MediaStreamTrackSourceGetter)
3312 :
3313 0 : explicit CaptureStreamTrackSourceGetter(HTMLMediaElement* aElement)
3314 0 : : mElement(aElement)
3315 : {
3316 0 : }
3317 :
3318 0 : already_AddRefed<dom::MediaStreamTrackSource> GetMediaStreamTrackSource(
3319 : TrackID aInputTrackID) override
3320 : {
3321 0 : if (mElement && mElement->mSrcStream) {
3322 0 : NS_ERROR("Captured media element playing a stream adds tracks explicitly "
3323 : "on main thread.");
3324 : return nullptr;
3325 : }
3326 :
3327 : // We can return a new source each time here, even for different streams,
3328 : // since the sources don't keep any internal state and all of them call
3329 : // through to the same HTMLMediaElement.
3330 : // If this changes (after implementing Stop()?) we'll have to ensure we
3331 : // return the same source for all requests to the same TrackID, and only
3332 : // have one getter.
3333 0 : return do_AddRef(new DecoderCaptureTrackSource(mElement));
3334 : }
3335 :
3336 : protected:
3337 0 : virtual ~CaptureStreamTrackSourceGetter() {}
3338 :
3339 : RefPtr<HTMLMediaElement> mElement;
3340 : };
3341 :
3342 0 : NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter,
3343 : MediaStreamTrackSourceGetter)
3344 0 : NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::CaptureStreamTrackSourceGetter,
3345 : MediaStreamTrackSourceGetter)
3346 0 : NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
3347 : HTMLMediaElement::CaptureStreamTrackSourceGetter)
3348 0 : NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSourceGetter)
3349 0 : NS_IMPL_CYCLE_COLLECTION_INHERITED(
3350 : HTMLMediaElement::CaptureStreamTrackSourceGetter,
3351 : MediaStreamTrackSourceGetter,
3352 : mElement)
3353 :
3354 : void
3355 0 : HTMLMediaElement::SetCapturedOutputStreamsEnabled(bool aEnabled)
3356 : {
3357 0 : for (OutputMediaStream& ms : mOutputStreams) {
3358 0 : if (ms.mCapturingDecoder) {
3359 0 : MOZ_ASSERT(!ms.mCapturingMediaStream);
3360 : continue;
3361 : }
3362 0 : for (auto pair : ms.mTrackPorts) {
3363 0 : MediaStream* outputSource = ms.mStream->GetInputStream();
3364 0 : if (!outputSource) {
3365 0 : NS_ERROR("No output source stream");
3366 0 : return;
3367 : }
3368 :
3369 0 : TrackID id = pair.second()->GetDestinationTrackId();
3370 0 : outputSource->SetTrackEnabled(id,
3371 : aEnabled
3372 : ? DisabledTrackMode::ENABLED
3373 0 : : DisabledTrackMode::SILENCE_FREEZE);
3374 :
3375 0 : LOG(LogLevel::Debug,
3376 : ("%s track %d for captured MediaStream %p",
3377 : aEnabled ? "Enabled" : "Disabled",
3378 : id,
3379 : ms.mStream.get()));
3380 : }
3381 : }
3382 : }
3383 :
3384 : void
3385 0 : HTMLMediaElement::AddCaptureMediaTrackToOutputStream(
3386 : MediaTrack* aTrack,
3387 : OutputMediaStream& aOutputStream,
3388 : bool aAsyncAddtrack)
3389 : {
3390 0 : if (aOutputStream.mCapturingDecoder) {
3391 0 : MOZ_ASSERT(!aOutputStream.mCapturingMediaStream);
3392 0 : return;
3393 : }
3394 0 : aOutputStream.mCapturingMediaStream = true;
3395 :
3396 0 : if (aOutputStream.mStream == mSrcStream) {
3397 : // Cycle detected. This can happen since tracks are added async.
3398 : // We avoid forwarding it to the output here or we'd get into an infloop.
3399 : return;
3400 : }
3401 :
3402 0 : MediaStream* outputSource = aOutputStream.mStream->GetInputStream();
3403 0 : if (!outputSource) {
3404 0 : NS_ERROR("No output source stream");
3405 : return;
3406 : }
3407 :
3408 : ProcessedMediaStream* processedOutputSource =
3409 0 : outputSource->AsProcessedStream();
3410 0 : if (!processedOutputSource) {
3411 0 : NS_ERROR("Input stream not a ProcessedMediaStream");
3412 : return;
3413 : }
3414 :
3415 0 : if (!aTrack) {
3416 0 : MOZ_ASSERT(false, "Bad MediaTrack");
3417 : return;
3418 : }
3419 :
3420 0 : MediaStreamTrack* inputTrack = mSrcStream->GetTrackById(aTrack->GetId());
3421 0 : MOZ_ASSERT(inputTrack);
3422 0 : if (!inputTrack) {
3423 0 : NS_ERROR("Input track not found in source stream");
3424 : return;
3425 : }
3426 :
3427 : #if DEBUG
3428 0 : for (auto pair : aOutputStream.mTrackPorts) {
3429 0 : MOZ_ASSERT(pair.first() != aTrack->GetId(),
3430 : "Captured track already captured to output stream");
3431 : }
3432 : #endif
3433 :
3434 0 : TrackID destinationTrackID = aOutputStream.mNextAvailableTrackID++;
3435 : RefPtr<MediaStreamTrackSource> source = new StreamCaptureTrackSource(
3436 0 : this, &inputTrack->GetSource(), aOutputStream.mStream, destinationTrackID);
3437 :
3438 0 : MediaSegment::Type type = inputTrack->AsAudioStreamTrack()
3439 0 : ? MediaSegment::AUDIO
3440 0 : : MediaSegment::VIDEO;
3441 :
3442 : RefPtr<MediaStreamTrack> track =
3443 0 : aOutputStream.mStream->CreateDOMTrack(destinationTrackID, type, source);
3444 :
3445 0 : if (aAsyncAddtrack) {
3446 0 : mMainThreadEventTarget->Dispatch(
3447 0 : NewRunnableMethod<StoreRefPtrPassByPtr<MediaStreamTrack>>(
3448 : "DOMMediaStream::AddTrackInternal",
3449 : aOutputStream.mStream,
3450 : &DOMMediaStream::AddTrackInternal,
3451 0 : track));
3452 : } else {
3453 0 : aOutputStream.mStream->AddTrackInternal(track);
3454 : }
3455 :
3456 : // Track is muted initially, so we don't leak data if it's added while paused
3457 : // and an MSG iteration passes before the mute comes into effect.
3458 0 : processedOutputSource->SetTrackEnabled(destinationTrackID,
3459 0 : DisabledTrackMode::SILENCE_FREEZE);
3460 0 : RefPtr<MediaInputPort> port = inputTrack->ForwardTrackContentsTo(
3461 0 : processedOutputSource, destinationTrackID);
3462 :
3463 0 : Pair<nsString, RefPtr<MediaInputPort>> p(aTrack->GetId(), port);
3464 0 : aOutputStream.mTrackPorts.AppendElement(std::move(p));
3465 :
3466 0 : if (mSrcStreamIsPlaying) {
3467 : processedOutputSource->SetTrackEnabled(destinationTrackID,
3468 0 : DisabledTrackMode::ENABLED);
3469 : }
3470 :
3471 0 : LOG(LogLevel::Debug,
3472 : ("Created %s track %p with id %d from track %p through MediaInputPort %p",
3473 : inputTrack->AsAudioStreamTrack() ? "audio" : "video",
3474 : track.get(),
3475 : destinationTrackID,
3476 : inputTrack,
3477 : port.get()));
3478 : }
3479 :
3480 : bool
3481 0 : HTMLMediaElement::CanBeCaptured(StreamCaptureType aCaptureType)
3482 : {
3483 : // Don't bother capturing when the document has gone away
3484 0 : nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
3485 0 : if (!window) {
3486 : return false;
3487 : }
3488 :
3489 : // Prevent capturing restricted video
3490 0 : if (aCaptureType == StreamCaptureType::CAPTURE_ALL_TRACKS &&
3491 0 : ContainsRestrictedContent()) {
3492 : return false;
3493 : }
3494 0 : return true;
3495 : }
3496 :
3497 : already_AddRefed<DOMMediaStream>
3498 0 : HTMLMediaElement::CaptureStreamInternal(StreamCaptureBehavior aFinishBehavior,
3499 : StreamCaptureType aStreamCaptureType,
3500 : MediaStreamGraph* aGraph)
3501 : {
3502 0 : MOZ_RELEASE_ASSERT(aGraph);
3503 0 : MOZ_ASSERT(CanBeCaptured(aStreamCaptureType));
3504 :
3505 0 : MarkAsContentSource(CallerAPI::CAPTURE_STREAM);
3506 0 : MarkAsTainted();
3507 :
3508 : // We don't support routing to a different graph.
3509 0 : if (!mOutputStreams.IsEmpty() &&
3510 0 : aGraph != mOutputStreams[0].mStream->GetInputStream()->Graph()) {
3511 : return nullptr;
3512 : }
3513 :
3514 0 : OutputMediaStream* out = mOutputStreams.AppendElement();
3515 : MediaStreamTrackSourceGetter* getter =
3516 0 : new CaptureStreamTrackSourceGetter(this);
3517 0 : nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
3518 : out->mStream =
3519 0 : DOMMediaStream::CreateTrackUnionStreamAsInput(window, aGraph, getter);
3520 0 : out->mStream->SetInactiveOnFinish();
3521 0 : out->mFinishWhenEnded =
3522 0 : aFinishBehavior == StreamCaptureBehavior::FINISH_WHEN_ENDED;
3523 0 : out->mCapturingAudioOnly =
3524 0 : aStreamCaptureType == StreamCaptureType::CAPTURE_AUDIO;
3525 :
3526 0 : if (aStreamCaptureType == StreamCaptureType::CAPTURE_AUDIO) {
3527 0 : if (mSrcStream) {
3528 : // We don't support applying volume and mute to the captured stream, when
3529 : // capturing a MediaStream.
3530 0 : nsContentUtils::ReportToConsole(
3531 : nsIScriptError::errorFlag,
3532 0 : NS_LITERAL_CSTRING("Media"),
3533 0 : OwnerDoc(),
3534 : nsContentUtils::eDOM_PROPERTIES,
3535 0 : "MediaElementAudioCaptureOfMediaStreamError");
3536 : return nullptr;
3537 : }
3538 :
3539 : // mAudioCaptured tells the user that the audio played by this media element
3540 : // is being routed to the captureStreams *instead* of being played to
3541 : // speakers.
3542 0 : mAudioCaptured = true;
3543 : }
3544 :
3545 0 : if (mDecoder) {
3546 0 : out->mCapturingDecoder = true;
3547 0 : mDecoder->AddOutputStream(
3548 0 : out->mStream->GetInputStream()->AsProcessedStream(),
3549 : out->mNextAvailableTrackID,
3550 0 : aFinishBehavior == StreamCaptureBehavior::FINISH_WHEN_ENDED);
3551 0 : } else if (mSrcStream) {
3552 0 : out->mCapturingMediaStream = true;
3553 : }
3554 :
3555 0 : if (mReadyState == HAVE_NOTHING) {
3556 : // Do not expose the tracks until we have metadata.
3557 0 : RefPtr<DOMMediaStream> result = out->mStream;
3558 0 : return result.forget();
3559 : }
3560 :
3561 0 : if (mDecoder) {
3562 0 : if (HasAudio()) {
3563 0 : TrackID audioTrackId = out->mNextAvailableTrackID++;
3564 : RefPtr<MediaStreamTrackSource> trackSource =
3565 0 : getter->GetMediaStreamTrackSource(audioTrackId);
3566 0 : RefPtr<MediaStreamTrack> track = out->mStream->CreateDOMTrack(
3567 0 : audioTrackId, MediaSegment::AUDIO, trackSource);
3568 0 : out->mPreCreatedTracks.AppendElement(track);
3569 0 : out->mStream->AddTrackInternal(track);
3570 0 : LOG(LogLevel::Debug,
3571 : ("Created audio track %d for captured decoder", audioTrackId));
3572 : }
3573 0 : if (IsVideo() && HasVideo() && !out->mCapturingAudioOnly) {
3574 0 : TrackID videoTrackId = out->mNextAvailableTrackID++;
3575 : RefPtr<MediaStreamTrackSource> trackSource =
3576 0 : getter->GetMediaStreamTrackSource(videoTrackId);
3577 0 : RefPtr<MediaStreamTrack> track = out->mStream->CreateDOMTrack(
3578 0 : videoTrackId, MediaSegment::VIDEO, trackSource);
3579 0 : out->mPreCreatedTracks.AppendElement(track);
3580 0 : out->mStream->AddTrackInternal(track);
3581 0 : LOG(LogLevel::Debug,
3582 : ("Created video track %d for captured decoder", videoTrackId));
3583 : }
3584 : }
3585 :
3586 0 : if (mSrcStream) {
3587 0 : for (size_t i = 0; i < AudioTracks()->Length(); ++i) {
3588 0 : AudioTrack* t = (*AudioTracks())[i];
3589 0 : if (t->Enabled()) {
3590 0 : AddCaptureMediaTrackToOutputStream(t, *out, false);
3591 : }
3592 : }
3593 0 : if (IsVideo() && !out->mCapturingAudioOnly) {
3594 : // Only add video tracks if we're a video element and the output stream
3595 : // wants video.
3596 0 : for (size_t i = 0; i < VideoTracks()->Length(); ++i) {
3597 0 : VideoTrack* t = (*VideoTracks())[i];
3598 0 : if (t->Selected()) {
3599 0 : AddCaptureMediaTrackToOutputStream(t, *out, false);
3600 : }
3601 : }
3602 : }
3603 : }
3604 0 : RefPtr<DOMMediaStream> result = out->mStream;
3605 0 : return result.forget();
3606 : }
3607 :
3608 : already_AddRefed<DOMMediaStream>
3609 0 : HTMLMediaElement::CaptureAudio(ErrorResult& aRv, MediaStreamGraph* aGraph)
3610 : {
3611 0 : MOZ_RELEASE_ASSERT(aGraph);
3612 :
3613 0 : if (!CanBeCaptured(StreamCaptureType::CAPTURE_AUDIO)) {
3614 0 : aRv.Throw(NS_ERROR_FAILURE);
3615 : return nullptr;
3616 : }
3617 :
3618 : RefPtr<DOMMediaStream> stream =
3619 0 : CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED,
3620 : StreamCaptureType::CAPTURE_AUDIO,
3621 0 : aGraph);
3622 0 : if (!stream) {
3623 0 : aRv.Throw(NS_ERROR_FAILURE);
3624 : return nullptr;
3625 : }
3626 :
3627 : return stream.forget();
3628 : }
3629 :
3630 : already_AddRefed<DOMMediaStream>
3631 0 : HTMLMediaElement::MozCaptureStream(ErrorResult& aRv)
3632 : {
3633 : MediaStreamGraph::GraphDriverType graphDriverType =
3634 0 : HasAudio() ? MediaStreamGraph::AUDIO_THREAD_DRIVER
3635 0 : : MediaStreamGraph::SYSTEM_THREAD_DRIVER;
3636 :
3637 0 : nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
3638 0 : if (!window) {
3639 0 : aRv.Throw(NS_ERROR_FAILURE);
3640 : return nullptr;
3641 : }
3642 :
3643 0 : if (!CanBeCaptured(StreamCaptureType::CAPTURE_ALL_TRACKS)) {
3644 0 : aRv.Throw(NS_ERROR_FAILURE);
3645 : return nullptr;
3646 : }
3647 :
3648 : MediaStreamGraph* graph = MediaStreamGraph::GetInstance(
3649 0 : graphDriverType, window, MediaStreamGraph::REQUEST_DEFAULT_SAMPLE_RATE);
3650 :
3651 : RefPtr<DOMMediaStream> stream =
3652 0 : CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED,
3653 : StreamCaptureType::CAPTURE_ALL_TRACKS,
3654 0 : graph);
3655 0 : if (!stream) {
3656 0 : aRv.Throw(NS_ERROR_FAILURE);
3657 : return nullptr;
3658 : }
3659 :
3660 : return stream.forget();
3661 : }
3662 :
3663 : already_AddRefed<DOMMediaStream>
3664 0 : HTMLMediaElement::MozCaptureStreamUntilEnded(ErrorResult& aRv)
3665 : {
3666 : MediaStreamGraph::GraphDriverType graphDriverType =
3667 0 : HasAudio() ? MediaStreamGraph::AUDIO_THREAD_DRIVER
3668 0 : : MediaStreamGraph::SYSTEM_THREAD_DRIVER;
3669 :
3670 0 : nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
3671 0 : if (!window) {
3672 0 : aRv.Throw(NS_ERROR_FAILURE);
3673 : return nullptr;
3674 : }
3675 :
3676 0 : if (!CanBeCaptured(StreamCaptureType::CAPTURE_ALL_TRACKS)) {
3677 0 : aRv.Throw(NS_ERROR_FAILURE);
3678 : return nullptr;
3679 : }
3680 :
3681 : MediaStreamGraph* graph = MediaStreamGraph::GetInstance(
3682 0 : graphDriverType, window, MediaStreamGraph::REQUEST_DEFAULT_SAMPLE_RATE);
3683 :
3684 : RefPtr<DOMMediaStream> stream =
3685 0 : CaptureStreamInternal(StreamCaptureBehavior::FINISH_WHEN_ENDED,
3686 : StreamCaptureType::CAPTURE_ALL_TRACKS,
3687 0 : graph);
3688 0 : if (!stream) {
3689 0 : aRv.Throw(NS_ERROR_FAILURE);
3690 : return nullptr;
3691 : }
3692 :
3693 : return stream.forget();
3694 : }
3695 :
3696 0 : class MediaElementSetForURI : public nsURIHashKey
3697 : {
3698 : public:
3699 0 : explicit MediaElementSetForURI(const nsIURI* aKey)
3700 0 : : nsURIHashKey(aKey)
3701 : {
3702 0 : }
3703 : MediaElementSetForURI(const MediaElementSetForURI& toCopy)
3704 : : nsURIHashKey(toCopy)
3705 : , mElements(toCopy.mElements)
3706 : {
3707 : }
3708 : nsTArray<HTMLMediaElement*> mElements;
3709 : };
3710 :
3711 : typedef nsTHashtable<MediaElementSetForURI> MediaElementURITable;
3712 : // Elements in this table must have non-null mDecoder and mLoadingSrc, and those
3713 : // can't change while the element is in the table. The table is keyed by
3714 : // the element's mLoadingSrc. Each entry has a list of all elements with the
3715 : // same mLoadingSrc.
3716 : static MediaElementURITable* gElementTable;
3717 :
3718 : #ifdef DEBUG
3719 : static bool
3720 0 : URISafeEquals(nsIURI* a1, nsIURI* a2)
3721 : {
3722 0 : if (!a1 || !a2) {
3723 : // Consider two empty URIs *not* equal!
3724 : return false;
3725 : }
3726 0 : bool equal = false;
3727 0 : nsresult rv = a1->Equals(a2, &equal);
3728 0 : return NS_SUCCEEDED(rv) && equal;
3729 : }
3730 : // Returns the number of times aElement appears in the media element table
3731 : // for aURI. If this returns other than 0 or 1, there's a bug somewhere!
3732 : static unsigned
3733 0 : MediaElementTableCount(HTMLMediaElement* aElement, nsIURI* aURI)
3734 : {
3735 0 : if (!gElementTable || !aElement) {
3736 : return 0;
3737 : }
3738 0 : uint32_t uriCount = 0;
3739 0 : uint32_t otherCount = 0;
3740 0 : for (auto it = gElementTable->ConstIter(); !it.Done(); it.Next()) {
3741 0 : MediaElementSetForURI* entry = it.Get();
3742 0 : uint32_t count = 0;
3743 0 : for (const auto& elem : entry->mElements) {
3744 0 : if (elem == aElement) {
3745 0 : count++;
3746 : }
3747 : }
3748 0 : if (URISafeEquals(aURI, entry->GetKey())) {
3749 : uriCount = count;
3750 : } else {
3751 0 : otherCount += count;
3752 : }
3753 : }
3754 0 : NS_ASSERTION(otherCount == 0, "Should not have entries for unknown URIs");
3755 : return uriCount;
3756 : }
3757 : #endif
3758 :
3759 : void
3760 0 : HTMLMediaElement::AddMediaElementToURITable()
3761 : {
3762 0 : NS_ASSERTION(mDecoder, "Call this only with decoder Load called");
3763 0 : NS_ASSERTION(
3764 : MediaElementTableCount(this, mLoadingSrc) == 0,
3765 : "Should not have entry for element in element table before addition");
3766 0 : if (!gElementTable) {
3767 0 : gElementTable = new MediaElementURITable();
3768 : }
3769 0 : MediaElementSetForURI* entry = gElementTable->PutEntry(mLoadingSrc);
3770 0 : entry->mElements.AppendElement(this);
3771 0 : NS_ASSERTION(
3772 : MediaElementTableCount(this, mLoadingSrc) == 1,
3773 : "Should have a single entry for element in element table after addition");
3774 0 : }
3775 :
3776 : void
3777 0 : HTMLMediaElement::RemoveMediaElementFromURITable()
3778 : {
3779 0 : if (!mDecoder || !mLoadingSrc || !gElementTable) {
3780 : return;
3781 : }
3782 0 : MediaElementSetForURI* entry = gElementTable->GetEntry(mLoadingSrc);
3783 0 : if (!entry) {
3784 : return;
3785 : }
3786 0 : entry->mElements.RemoveElement(this);
3787 0 : if (entry->mElements.IsEmpty()) {
3788 0 : gElementTable->RemoveEntry(entry);
3789 0 : if (gElementTable->Count() == 0) {
3790 0 : delete gElementTable;
3791 0 : gElementTable = nullptr;
3792 : }
3793 : }
3794 0 : NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
3795 : "After remove, should no longer have an entry in element table");
3796 : }
3797 :
3798 : HTMLMediaElement*
3799 0 : HTMLMediaElement::LookupMediaElementURITable(nsIURI* aURI)
3800 : {
3801 0 : if (!gElementTable) {
3802 : return nullptr;
3803 : }
3804 0 : MediaElementSetForURI* entry = gElementTable->GetEntry(aURI);
3805 0 : if (!entry) {
3806 : return nullptr;
3807 : }
3808 0 : for (uint32_t i = 0; i < entry->mElements.Length(); ++i) {
3809 0 : HTMLMediaElement* elem = entry->mElements[i];
3810 : bool equal;
3811 : // Look for elements that have the same principal and CORS mode.
3812 : // Ditto for anything else that could cause us to send different headers.
3813 0 : if (NS_SUCCEEDED(elem->NodePrincipal()->Equals(NodePrincipal(), &equal)) &&
3814 0 : equal && elem->mCORSMode == mCORSMode) {
3815 : // See SetupDecoder() below. We only add a element to the table when
3816 : // mDecoder is a ChannelMediaDecoder.
3817 0 : auto decoder = static_cast<ChannelMediaDecoder*>(elem->mDecoder.get());
3818 0 : NS_ASSERTION(decoder, "Decoder gone");
3819 0 : if (decoder->CanClone()) {
3820 0 : return elem;
3821 : }
3822 : }
3823 : }
3824 : return nullptr;
3825 : }
3826 :
3827 3 : class HTMLMediaElement::ShutdownObserver : public nsIObserver
3828 : {
3829 : enum class Phase : int8_t
3830 : {
3831 : Init,
3832 : Subscribed,
3833 : Unsubscribed
3834 : };
3835 :
3836 : public:
3837 : NS_DECL_ISUPPORTS
3838 :
3839 0 : NS_IMETHOD Observe(nsISupports*, const char* aTopic, const char16_t*) override
3840 : {
3841 0 : if (mPhase != Phase::Subscribed) {
3842 : // Bail out if we are not subscribed for this might be called even after
3843 : // |nsContentUtils::UnregisterShutdownObserver(this)|.
3844 : return NS_OK;
3845 : }
3846 0 : MOZ_DIAGNOSTIC_ASSERT(mWeak);
3847 0 : if (strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID) == 0) {
3848 0 : mWeak->NotifyShutdownEvent();
3849 : }
3850 : return NS_OK;
3851 : }
3852 0 : void Subscribe(HTMLMediaElement* aPtr)
3853 : {
3854 0 : MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Init);
3855 0 : MOZ_DIAGNOSTIC_ASSERT(!mWeak);
3856 0 : mWeak = aPtr;
3857 0 : nsContentUtils::RegisterShutdownObserver(this);
3858 0 : mPhase = Phase::Subscribed;
3859 1 : }
3860 0 : void Unsubscribe()
3861 : {
3862 0 : MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Subscribed);
3863 0 : MOZ_DIAGNOSTIC_ASSERT(mWeak);
3864 0 : mWeak = nullptr;
3865 0 : nsContentUtils::UnregisterShutdownObserver(this);
3866 0 : mPhase = Phase::Unsubscribed;
3867 0 : }
3868 0 : void AddRefMediaElement() { mWeak->AddRef(); }
3869 0 : void ReleaseMediaElement() { mWeak->Release(); }
3870 :
3871 : private:
3872 0 : virtual ~ShutdownObserver()
3873 0 : {
3874 0 : MOZ_DIAGNOSTIC_ASSERT(mPhase == Phase::Unsubscribed);
3875 0 : MOZ_DIAGNOSTIC_ASSERT(!mWeak);
3876 0 : }
3877 : // Guaranteed to be valid by HTMLMediaElement.
3878 : HTMLMediaElement* mWeak = nullptr;
3879 : Phase mPhase = Phase::Init;
3880 : };
3881 :
3882 0 : NS_IMPL_ISUPPORTS(HTMLMediaElement::ShutdownObserver, nsIObserver)
3883 :
3884 0 : HTMLMediaElement::HTMLMediaElement(
3885 0 : already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
3886 : : nsGenericHTMLElement(aNodeInfo)
3887 0 : , mWatchManager(this, OwnerDoc()->AbstractMainThreadFor(TaskCategory::Other))
3888 1 : , mMainThreadEventTarget(OwnerDoc()->EventTargetFor(TaskCategory::Other))
3889 0 : , mAbstractMainThread(OwnerDoc()->AbstractMainThreadFor(TaskCategory::Other))
3890 1 : , mShutdownObserver(new ShutdownObserver)
3891 0 : , mPlayed(new TimeRanges(ToSupports(OwnerDoc())))
3892 : , mPaused(true, "HTMLMediaElement::mPaused")
3893 1 : , mErrorSink(new ErrorSink(this))
3894 0 : , mAudioChannelWrapper(new AudioChannelAgentCallback(this))
3895 : {
3896 2 : MOZ_ASSERT(mMainThreadEventTarget);
3897 0 : MOZ_ASSERT(mAbstractMainThread);
3898 :
3899 1 : DecoderDoctorLogger::LogConstruction(this);
3900 :
3901 1 : mWatchManager.Watch(mPaused, &HTMLMediaElement::UpdateWakeLock);
3902 :
3903 2 : ErrorResult rv;
3904 :
3905 1 : double defaultVolume = Preferences::GetFloat("media.default_volume", 1.0);
3906 0 : SetVolume(defaultVolume, rv);
3907 :
3908 0 : RegisterActivityObserver();
3909 0 : NotifyOwnerDocumentActivityChanged();
3910 :
3911 0 : mShutdownObserver->Subscribe(this);
3912 1 : }
3913 :
3914 0 : HTMLMediaElement::~HTMLMediaElement()
3915 : {
3916 0 : NS_ASSERTION(
3917 : !mHasSelfReference,
3918 : "How can we be destroyed if we're still holding a self reference?");
3919 :
3920 0 : mShutdownObserver->Unsubscribe();
3921 :
3922 0 : if (mVideoFrameContainer) {
3923 0 : mVideoFrameContainer->ForgetElement();
3924 : }
3925 0 : UnregisterActivityObserver();
3926 :
3927 0 : mSetCDMRequest.DisconnectIfExists();
3928 0 : if (mDecoder) {
3929 0 : ShutdownDecoder();
3930 : }
3931 0 : if (mProgressTimer) {
3932 0 : StopProgress();
3933 : }
3934 0 : if (mVideoDecodeSuspendTimer) {
3935 0 : mVideoDecodeSuspendTimer->Cancel();
3936 0 : mVideoDecodeSuspendTimer = nullptr;
3937 : }
3938 0 : if (mSrcStream) {
3939 0 : EndSrcMediaStreamPlayback();
3940 : }
3941 :
3942 0 : if (mCaptureStreamPort) {
3943 0 : mCaptureStreamPort->Destroy();
3944 0 : mCaptureStreamPort = nullptr;
3945 : }
3946 :
3947 0 : NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
3948 : "Destroyed media element should no longer be in element table");
3949 :
3950 0 : if (mChannelLoader) {
3951 0 : mChannelLoader->Cancel();
3952 : }
3953 :
3954 0 : if (mAudioChannelWrapper) {
3955 0 : mAudioChannelWrapper->Shutdown();
3956 0 : mAudioChannelWrapper = nullptr;
3957 : }
3958 :
3959 0 : WakeLockRelease();
3960 :
3961 0 : DecoderDoctorLogger::LogDestruction(this);
3962 0 : }
3963 :
3964 : void
3965 0 : HTMLMediaElement::StopSuspendingAfterFirstFrame()
3966 : {
3967 0 : mAllowSuspendAfterFirstFrame = false;
3968 0 : if (!mSuspendedAfterFirstFrame)
3969 : return;
3970 0 : mSuspendedAfterFirstFrame = false;
3971 0 : if (mDecoder) {
3972 0 : mDecoder->Resume();
3973 : }
3974 : }
3975 :
3976 : void
3977 0 : HTMLMediaElement::SetPlayedOrSeeked(bool aValue)
3978 : {
3979 0 : if (aValue == mHasPlayedOrSeeked) {
3980 : return;
3981 : }
3982 :
3983 0 : mHasPlayedOrSeeked = aValue;
3984 :
3985 : // Force a reflow so that the poster frame hides or shows immediately.
3986 0 : nsIFrame* frame = GetPrimaryFrame();
3987 0 : if (!frame) {
3988 : return;
3989 : }
3990 0 : frame->PresShell()->FrameNeedsReflow(
3991 0 : frame, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY);
3992 : }
3993 :
3994 : void
3995 0 : HTMLMediaElement::NotifyXPCOMShutdown()
3996 : {
3997 0 : ShutdownDecoder();
3998 0 : }
3999 :
4000 : already_AddRefed<Promise>
4001 0 : HTMLMediaElement::Play(ErrorResult& aRv)
4002 : {
4003 0 : LOG(LogLevel::Debug,
4004 : ("%p Play() called by JS readyState=%d", this, mReadyState));
4005 :
4006 0 : if (mAudioChannelWrapper && mAudioChannelWrapper->IsPlaybackBlocked()) {
4007 0 : MaybeDoLoad();
4008 :
4009 : // A blocked media element will be resumed later, so we return a pending
4010 : // promise which might be resolved/rejected depends on the result of
4011 : // resuming the blocked media element.
4012 0 : RefPtr<PlayPromise> promise = CreatePlayPromise(aRv);
4013 :
4014 0 : if (NS_WARN_IF(aRv.Failed())) {
4015 : return nullptr;
4016 : }
4017 :
4018 0 : LOG(LogLevel::Debug, ("%p Play() call delayed by AudioChannelAgent", this));
4019 :
4020 0 : mPendingPlayPromises.AppendElement(promise);
4021 0 : return promise.forget();
4022 : }
4023 :
4024 0 : RefPtr<Promise> promise = PlayInternal(aRv);
4025 :
4026 0 : UpdateCustomPolicyAfterPlayed();
4027 :
4028 0 : return promise.forget();
4029 : }
4030 :
4031 : already_AddRefed<Promise>
4032 0 : HTMLMediaElement::PlayInternal(ErrorResult& aRv)
4033 : {
4034 0 : MOZ_ASSERT(!aRv.Failed());
4035 :
4036 0 : RefPtr<PlayPromise> promise = CreatePlayPromise(aRv);
4037 :
4038 0 : if (NS_WARN_IF(aRv.Failed())) {
4039 : return nullptr;
4040 : }
4041 :
4042 : // 4.8.12.8
4043 : // When the play() method on a media element is invoked, the user agent must
4044 : // run the following steps.
4045 :
4046 : // 4.8.12.8 - Step 1:
4047 : // If the media element is not allowed to play, return a promise rejected
4048 : // with a "NotAllowedError" DOMException and abort these steps.
4049 0 : if (!IsAllowedToPlay()) {
4050 : // NOTE: for promise-based-play, will return a rejected promise here.
4051 0 : LOG(LogLevel::Debug,
4052 : ("%p Play() promise rejected because not allowed to play.", this));
4053 0 : promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
4054 0 : return promise.forget();
4055 : }
4056 :
4057 : // 4.8.12.8 - Step 2:
4058 : // If the media element's error attribute is not null and its code
4059 : // attribute has the value MEDIA_ERR_SRC_NOT_SUPPORTED, return a promise
4060 : // rejected with a "NotSupportedError" DOMException and abort these steps.
4061 0 : if (GetError() && GetError()->Code() == MEDIA_ERR_SRC_NOT_SUPPORTED) {
4062 0 : LOG(LogLevel::Debug,
4063 : ("%p Play() promise rejected because source not supported.", this));
4064 0 : promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR);
4065 0 : return promise.forget();
4066 : }
4067 :
4068 : // 4.8.12.8 - Step 3:
4069 : // Let promise be a new promise and append promise to the list of pending
4070 : // play promises.
4071 0 : mPendingPlayPromises.AppendElement(promise);
4072 :
4073 0 : if (mPreloadAction == HTMLMediaElement::PRELOAD_NONE) {
4074 : // The media load algorithm will be initiated by a user interaction.
4075 : // We want to boost the channel priority for better responsiveness.
4076 : // Note this must be done before UpdatePreloadAction() which will
4077 : // update |mPreloadAction|.
4078 0 : mUseUrgentStartForChannel = true;
4079 : }
4080 :
4081 0 : StopSuspendingAfterFirstFrame();
4082 0 : SetPlayedOrSeeked(true);
4083 :
4084 : // 4.8.12.8 - Step 4:
4085 : // If the media element's networkState attribute has the value NETWORK_EMPTY,
4086 : // invoke the media element's resource selection algorithm.
4087 0 : MaybeDoLoad();
4088 0 : if (mSuspendedForPreloadNone) {
4089 0 : ResumeLoad(PRELOAD_ENOUGH);
4090 : }
4091 :
4092 : // 4.8.12.8 - Step 5:
4093 : // If the playback has ended and the direction of playback is forwards,
4094 : // seek to the earliest possible position of the media resource.
4095 :
4096 : // Even if we just did Load() or ResumeLoad(), we could already have a decoder
4097 : // here if we managed to clone an existing decoder.
4098 0 : if (mDecoder) {
4099 0 : if (mDecoder->IsEnded()) {
4100 0 : SetCurrentTime(0);
4101 : }
4102 0 : if (!mPausedForInactiveDocumentOrChannel) {
4103 0 : mDecoder->Play();
4104 : }
4105 : }
4106 :
4107 0 : if (mCurrentPlayRangeStart == -1.0) {
4108 0 : mCurrentPlayRangeStart = CurrentTime();
4109 : }
4110 :
4111 0 : const bool oldPaused = mPaused;
4112 0 : mPaused = false;
4113 0 : mAutoplaying = false;
4114 :
4115 : // We changed mPaused and mAutoplaying which can affect AddRemoveSelfReference
4116 : // and our preload status.
4117 0 : AddRemoveSelfReference();
4118 0 : UpdatePreloadAction();
4119 0 : UpdateSrcMediaStreamPlaying();
4120 :
4121 : // Once play() has been called in a user generated event handler,
4122 : // it is allowed to autoplay. Note: we can reach here when not in
4123 : // a user generated event handler if our readyState has not yet
4124 : // reached HAVE_METADATA.
4125 0 : mIsBlessed |= EventStateManager::IsHandlingUserInput();
4126 :
4127 : // TODO: If the playback has ended, then the user agent must set
4128 : // seek to the effective start.
4129 :
4130 : // 4.8.12.8 - Step 6:
4131 : // If the media element's paused attribute is true, run the following steps:
4132 0 : if (oldPaused) {
4133 : // 6.1. Change the value of paused to false. (Already done.)
4134 : // This step is uplifted because the "block-media-playback" feature needs
4135 : // the mPaused to be false before UpdateAudioChannelPlayingState() being
4136 : // called.
4137 :
4138 : // 6.2. If the show poster flag is true, set the element's show poster flag
4139 : // to false and run the time marches on steps.
4140 :
4141 : // 6.3. Queue a task to fire a simple event named play at the element.
4142 0 : DispatchAsyncEvent(NS_LITERAL_STRING("play"));
4143 :
4144 : // 6.4. If the media element's readyState attribute has the value
4145 : // HAVE_NOTHING, HAVE_METADATA, or HAVE_CURRENT_DATA, queue a task to
4146 : // fire a simple event named waiting at the element.
4147 : // Otherwise, the media element's readyState attribute has the value
4148 : // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA: notify about playing for the
4149 : // element.
4150 0 : switch (mReadyState) {
4151 : case HAVE_NOTHING:
4152 0 : DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
4153 0 : break;
4154 : case HAVE_METADATA:
4155 : case HAVE_CURRENT_DATA:
4156 0 : FireTimeUpdate(false);
4157 0 : DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
4158 0 : break;
4159 : case HAVE_FUTURE_DATA:
4160 : case HAVE_ENOUGH_DATA:
4161 0 : FireTimeUpdate(false);
4162 0 : NotifyAboutPlaying();
4163 0 : break;
4164 : }
4165 0 : } else if (mReadyState >= HAVE_FUTURE_DATA) {
4166 : // 7. Otherwise, if the media element's readyState attribute has the value
4167 : // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA, take pending play promises and
4168 : // queue a task to resolve pending play promises with the result.
4169 0 : AsyncResolvePendingPlayPromises();
4170 : }
4171 :
4172 : // 8. Set the media element's autoplaying flag to false. (Already done.)
4173 :
4174 : // 9. Return promise.
4175 0 : return promise.forget();
4176 : }
4177 :
4178 : void
4179 0 : HTMLMediaElement::MaybeDoLoad()
4180 : {
4181 0 : if (mNetworkState == NETWORK_EMPTY) {
4182 0 : DoLoad();
4183 : }
4184 0 : }
4185 :
4186 : void
4187 0 : HTMLMediaElement::UpdateWakeLock()
4188 : {
4189 0 : MOZ_ASSERT(NS_IsMainThread());
4190 : // Ensure we have a wake lock if we're playing audibly. This ensures the
4191 : // device doesn't sleep while playing.
4192 0 : bool playing = !mPaused;
4193 : bool isAudible =
4194 0 : Volume() > 0.0 && !mMuted && mIsAudioTrackAudible;
4195 : // WakeLock when playing audible media.
4196 0 : if (playing && isAudible) {
4197 0 : WakeLockCreate();
4198 : } else {
4199 0 : WakeLockRelease();
4200 : }
4201 0 : }
4202 :
4203 : void
4204 0 : HTMLMediaElement::WakeLockCreate()
4205 : {
4206 0 : if (!mWakeLock) {
4207 : RefPtr<power::PowerManagerService> pmService =
4208 0 : power::PowerManagerService::GetInstance();
4209 0 : NS_ENSURE_TRUE_VOID(pmService);
4210 :
4211 0 : ErrorResult rv;
4212 0 : mWakeLock = pmService->NewWakeLock(
4213 0 : NS_LITERAL_STRING("audio-playing"), OwnerDoc()->GetInnerWindow(), rv);
4214 : }
4215 : }
4216 :
4217 : void
4218 0 : HTMLMediaElement::WakeLockRelease()
4219 : {
4220 0 : if (mWakeLock) {
4221 0 : ErrorResult rv;
4222 0 : mWakeLock->Unlock(rv);
4223 0 : rv.SuppressException();
4224 0 : mWakeLock = nullptr;
4225 : }
4226 0 : }
4227 :
4228 0 : HTMLMediaElement::OutputMediaStream::OutputMediaStream()
4229 : : mNextAvailableTrackID(1)
4230 : , mFinishWhenEnded(false)
4231 : , mCapturingAudioOnly(false)
4232 : , mCapturingDecoder(false)
4233 0 : , mCapturingMediaStream(false)
4234 : {
4235 0 : }
4236 :
4237 0 : HTMLMediaElement::OutputMediaStream::~OutputMediaStream()
4238 : {
4239 0 : for (auto pair : mTrackPorts) {
4240 0 : pair.second()->Destroy();
4241 : }
4242 0 : }
4243 :
4244 : void
4245 0 : HTMLMediaElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
4246 : {
4247 0 : if (!this->Controls() || !aVisitor.mEvent->mFlags.mIsTrusted) {
4248 0 : nsGenericHTMLElement::GetEventTargetParent(aVisitor);
4249 0 : return;
4250 : }
4251 :
4252 0 : HTMLInputElement* el = nullptr;
4253 0 : nsCOMPtr<nsINode> node;
4254 :
4255 : // We will need to trap pointer, touch, and mouse events within the media
4256 : // element, allowing media control exclusive consumption on these events,
4257 : // and preventing the content from handling them.
4258 0 : switch (aVisitor.mEvent->mMessage) {
4259 : case ePointerDown:
4260 : case ePointerUp:
4261 : case eTouchEnd:
4262 : // Always prevent touchmove captured in video element from being handled by
4263 : // content, since we always do that for touchstart.
4264 : case eTouchMove:
4265 : case eTouchStart:
4266 : case eMouseClick:
4267 : case eMouseDoubleClick:
4268 : case eMouseDown:
4269 : case eMouseUp:
4270 0 : aVisitor.mCanHandle = false;
4271 0 : return;
4272 :
4273 : // The *move events however are only comsumed when the range input is being
4274 : // dragged.
4275 : case ePointerMove:
4276 : case eMouseMove:
4277 0 : node = do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
4278 0 : if (node->IsInNativeAnonymousSubtree()) {
4279 0 : if (node->IsHTMLElement(nsGkAtoms::input)) {
4280 : // The node is a <input type="range">
4281 0 : el = static_cast<HTMLInputElement*>(node.get());
4282 0 : } else if (node->GetParentNode() &&
4283 0 : node->GetParentNode()->IsHTMLElement(nsGkAtoms::input)) {
4284 : // The node is a child of <input type="range">
4285 0 : el = static_cast<HTMLInputElement*>(node->GetParentNode());
4286 : }
4287 : }
4288 0 : if (el && el->IsDraggingRange()) {
4289 0 : aVisitor.mCanHandle = false;
4290 0 : return;
4291 : }
4292 0 : nsGenericHTMLElement::GetEventTargetParent(aVisitor);
4293 0 : return;
4294 :
4295 : default:
4296 0 : nsGenericHTMLElement::GetEventTargetParent(aVisitor);
4297 0 : return;
4298 : }
4299 : }
4300 :
4301 : bool
4302 0 : HTMLMediaElement::ParseAttribute(int32_t aNamespaceID,
4303 : nsAtom* aAttribute,
4304 : const nsAString& aValue,
4305 : nsIPrincipal* aMaybeScriptedPrincipal,
4306 : nsAttrValue& aResult)
4307 : {
4308 : // Mappings from 'preload' attribute strings to an enumeration.
4309 : static const nsAttrValue::EnumTable kPreloadTable[] = {
4310 : { "", HTMLMediaElement::PRELOAD_ATTR_EMPTY },
4311 : { "none", HTMLMediaElement::PRELOAD_ATTR_NONE },
4312 : { "metadata", HTMLMediaElement::PRELOAD_ATTR_METADATA },
4313 : { "auto", HTMLMediaElement::PRELOAD_ATTR_AUTO },
4314 : { nullptr, 0 }
4315 : };
4316 :
4317 1 : if (aNamespaceID == kNameSpaceID_None) {
4318 1 : if (ParseImageAttribute(aAttribute, aValue, aResult)) {
4319 : return true;
4320 : }
4321 1 : if (aAttribute == nsGkAtoms::crossorigin) {
4322 0 : ParseCORSValue(aValue, aResult);
4323 0 : return true;
4324 : }
4325 1 : if (aAttribute == nsGkAtoms::preload) {
4326 0 : return aResult.ParseEnumValue(aValue, kPreloadTable, false);
4327 : }
4328 : }
4329 :
4330 0 : return nsGenericHTMLElement::ParseAttribute(
4331 1 : aNamespaceID, aAttribute, aValue, aMaybeScriptedPrincipal, aResult);
4332 : }
4333 :
4334 : void
4335 0 : HTMLMediaElement::DoneCreatingElement()
4336 : {
4337 0 : if (HasAttr(kNameSpaceID_None, nsGkAtoms::muted)) {
4338 0 : mMuted |= MUTED_BY_CONTENT;
4339 : }
4340 0 : }
4341 :
4342 : bool
4343 0 : HTMLMediaElement::IsHTMLFocusable(bool aWithMouse,
4344 : bool* aIsFocusable,
4345 : int32_t* aTabIndex)
4346 : {
4347 0 : if (nsGenericHTMLElement::IsHTMLFocusable(
4348 : aWithMouse, aIsFocusable, aTabIndex)) {
4349 : return true;
4350 : }
4351 :
4352 0 : *aIsFocusable = true;
4353 0 : return false;
4354 : }
4355 :
4356 : int32_t
4357 0 : HTMLMediaElement::TabIndexDefault()
4358 : {
4359 0 : return 0;
4360 : }
4361 :
4362 : nsresult
4363 1 : HTMLMediaElement::AfterSetAttr(int32_t aNameSpaceID,
4364 : nsAtom* aName,
4365 : const nsAttrValue* aValue,
4366 : const nsAttrValue* aOldValue,
4367 : nsIPrincipal* aMaybeScriptedPrincipal,
4368 : bool aNotify)
4369 : {
4370 1 : if (aNameSpaceID == kNameSpaceID_None) {
4371 0 : if (aName == nsGkAtoms::src) {
4372 0 : mSrcMediaSource = nullptr;
4373 : mSrcAttrTriggeringPrincipal = nsContentUtils::GetAttrTriggeringPrincipal(
4374 : this,
4375 0 : aValue ? aValue->GetStringValue() : EmptyString(),
4376 0 : aMaybeScriptedPrincipal);
4377 0 : if (aValue) {
4378 0 : nsString srcStr = aValue->GetStringValue();
4379 0 : nsCOMPtr<nsIURI> uri;
4380 0 : NewURIFromString(srcStr, getter_AddRefs(uri));
4381 0 : if (uri && IsMediaSourceURI(uri)) {
4382 : nsresult rv =
4383 0 : NS_GetSourceForMediaSourceURI(uri, getter_AddRefs(mSrcMediaSource));
4384 0 : if (NS_FAILED(rv)) {
4385 0 : nsAutoString spec;
4386 0 : GetCurrentSrc(spec);
4387 0 : const char16_t* params[] = { spec.get() };
4388 0 : ReportLoadError("MediaLoadInvalidURI", params, ArrayLength(params));
4389 : }
4390 : }
4391 : }
4392 0 : } else if (aName == nsGkAtoms::autoplay) {
4393 0 : if (aNotify) {
4394 0 : if (aValue) {
4395 0 : StopSuspendingAfterFirstFrame();
4396 0 : CheckAutoplayDataReady();
4397 : }
4398 : // This attribute can affect AddRemoveSelfReference
4399 0 : AddRemoveSelfReference();
4400 0 : UpdatePreloadAction();
4401 : }
4402 1 : } else if (aName == nsGkAtoms::preload) {
4403 0 : UpdatePreloadAction();
4404 0 : } else if (aName == nsGkAtoms::loop) {
4405 0 : if (mDecoder) {
4406 0 : mDecoder->SetLooping(!!aValue);
4407 : }
4408 : }
4409 : }
4410 :
4411 : // Since AfterMaybeChangeAttr may call DoLoad, make sure that it is called
4412 : // *after* any possible changes to mSrcMediaSource.
4413 1 : if (aValue) {
4414 0 : AfterMaybeChangeAttr(aNameSpaceID, aName, aNotify);
4415 : }
4416 :
4417 0 : return nsGenericHTMLElement::AfterSetAttr(
4418 0 : aNameSpaceID, aName, aValue, aOldValue, aMaybeScriptedPrincipal, aNotify);
4419 : }
4420 :
4421 : nsresult
4422 0 : HTMLMediaElement::OnAttrSetButNotChanged(int32_t aNamespaceID,
4423 : nsAtom* aName,
4424 : const nsAttrValueOrString& aValue,
4425 : bool aNotify)
4426 : {
4427 0 : AfterMaybeChangeAttr(aNamespaceID, aName, aNotify);
4428 :
4429 0 : return nsGenericHTMLElement::OnAttrSetButNotChanged(
4430 0 : aNamespaceID, aName, aValue, aNotify);
4431 : }
4432 :
4433 : void
4434 0 : HTMLMediaElement::AfterMaybeChangeAttr(int32_t aNamespaceID,
4435 : nsAtom* aName,
4436 : bool aNotify)
4437 : {
4438 1 : if (aNamespaceID == kNameSpaceID_None) {
4439 0 : if (aName == nsGkAtoms::src) {
4440 0 : DoLoad();
4441 : }
4442 : }
4443 1 : }
4444 :
4445 : nsresult
4446 0 : HTMLMediaElement::BindToTree(nsIDocument* aDocument,
4447 : nsIContent* aParent,
4448 : nsIContent* aBindingParent,
4449 : bool aCompileEventHandlers)
4450 : {
4451 0 : nsresult rv = nsGenericHTMLElement::BindToTree(
4452 0 : aDocument, aParent, aBindingParent, aCompileEventHandlers);
4453 :
4454 1 : mUnboundFromTree = false;
4455 :
4456 1 : if (aDocument) {
4457 : // The preload action depends on the value of the autoplay attribute.
4458 : // It's value may have changed, so update it.
4459 1 : UpdatePreloadAction();
4460 : }
4461 :
4462 1 : NotifyDecoderActivityChanges();
4463 :
4464 0 : return rv;
4465 : }
4466 :
4467 : /* static */
4468 : void
4469 0 : HTMLMediaElement::VideoDecodeSuspendTimerCallback(nsITimer* aTimer,
4470 : void* aClosure)
4471 : {
4472 0 : MOZ_ASSERT(NS_IsMainThread());
4473 0 : auto element = static_cast<HTMLMediaElement*>(aClosure);
4474 0 : element->mVideoDecodeSuspendTime.Start();
4475 0 : element->mVideoDecodeSuspendTimer = nullptr;
4476 0 : }
4477 :
4478 : void
4479 0 : HTMLMediaElement::HiddenVideoStart()
4480 : {
4481 0 : MOZ_ASSERT(NS_IsMainThread());
4482 0 : mHiddenPlayTime.Start();
4483 0 : if (mVideoDecodeSuspendTimer) {
4484 : // Already started, just keep it running.
4485 : return;
4486 : }
4487 0 : NS_NewTimerWithFuncCallback(
4488 0 : getter_AddRefs(mVideoDecodeSuspendTimer),
4489 : VideoDecodeSuspendTimerCallback,
4490 : this,
4491 : StaticPrefs::MediaSuspendBkgndVideoDelayMs(),
4492 : nsITimer::TYPE_ONE_SHOT,
4493 : "HTMLMediaElement::VideoDecodeSuspendTimerCallback",
4494 0 : mMainThreadEventTarget);
4495 : }
4496 :
4497 : void
4498 2 : HTMLMediaElement::HiddenVideoStop()
4499 : {
4500 0 : MOZ_ASSERT(NS_IsMainThread());
4501 2 : mHiddenPlayTime.Pause();
4502 2 : mVideoDecodeSuspendTime.Pause();
4503 4 : if (!mVideoDecodeSuspendTimer) {
4504 : return;
4505 : }
4506 0 : mVideoDecodeSuspendTimer->Cancel();
4507 0 : mVideoDecodeSuspendTimer = nullptr;
4508 : }
4509 :
4510 : void
4511 0 : HTMLMediaElement::ReportTelemetry()
4512 : {
4513 : // Report telemetry for videos when a page is unloaded. We
4514 : // want to know data on what state the video is at when
4515 : // the user has exited.
4516 : enum UnloadedState
4517 : {
4518 : ENDED = 0,
4519 : PAUSED = 1,
4520 : STALLED = 2,
4521 : SEEKING = 3,
4522 : OTHER = 4
4523 : };
4524 :
4525 0 : UnloadedState state = OTHER;
4526 0 : if (Seeking()) {
4527 : state = SEEKING;
4528 0 : } else if (Ended()) {
4529 : state = ENDED;
4530 0 : } else if (Paused()) {
4531 : state = PAUSED;
4532 : } else {
4533 : // For buffering we check if the current playback position is at the end
4534 : // of a buffered range, within a margin of error. We also consider to be
4535 : // buffering if the last frame status was buffering and the ready state is
4536 : // HAVE_CURRENT_DATA to account for times where we are in a buffering state
4537 : // regardless of what actual data we have buffered.
4538 0 : bool stalled = false;
4539 0 : RefPtr<TimeRanges> ranges = Buffered();
4540 0 : const double errorMargin = 0.05;
4541 0 : double t = CurrentTime();
4542 0 : TimeRanges::index_type index = ranges->Find(t, errorMargin);
4543 0 : stalled =
4544 0 : index != TimeRanges::NoIndex && (ranges->End(index) - t) < errorMargin;
4545 0 : stalled |= mDecoder &&
4546 0 : NextFrameStatus() ==
4547 0 : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING &&
4548 0 : mReadyState == HAVE_CURRENT_DATA;
4549 0 : if (stalled) {
4550 0 : state = STALLED;
4551 : }
4552 : }
4553 :
4554 0 : Telemetry::Accumulate(Telemetry::VIDEO_UNLOAD_STATE, state);
4555 0 : LOG(LogLevel::Debug, ("%p VIDEO_UNLOAD_STATE = %d", this, state));
4556 :
4557 0 : FrameStatisticsData data;
4558 :
4559 0 : if (HTMLVideoElement* vid = HTMLVideoElement::FromNodeOrNull(this)) {
4560 0 : FrameStatistics* stats = vid->GetFrameStatistics();
4561 0 : if (stats) {
4562 0 : data = stats->GetFrameStatisticsData();
4563 0 : if (data.mParsedFrames) {
4564 0 : MOZ_ASSERT(data.mDroppedFrames <= data.mParsedFrames);
4565 : // Dropped frames <= total frames, so 'percentage' cannot be higher than
4566 : // 100 and therefore can fit in a uint32_t (that Telemetry takes).
4567 0 : uint32_t percentage = 100 * data.mDroppedFrames / data.mParsedFrames;
4568 0 : LOG(LogLevel::Debug,
4569 : ("Reporting telemetry DROPPED_FRAMES_IN_VIDEO_PLAYBACK"));
4570 : Telemetry::Accumulate(Telemetry::VIDEO_DROPPED_FRAMES_PROPORTION,
4571 0 : percentage);
4572 : }
4573 : }
4574 : }
4575 :
4576 0 : if (mMediaInfo.HasVideo() && mMediaInfo.mVideo.mImage.height > 0) {
4577 : // We have a valid video.
4578 0 : double playTime = mPlayTime.Total();
4579 0 : double hiddenPlayTime = mHiddenPlayTime.Total();
4580 0 : double videoDecodeSuspendTime = mVideoDecodeSuspendTime.Total();
4581 :
4582 0 : Telemetry::Accumulate(Telemetry::VIDEO_PLAY_TIME_MS,
4583 0 : SECONDS_TO_MS(playTime));
4584 0 : LOG(LogLevel::Debug, ("%p VIDEO_PLAY_TIME_MS = %f", this, playTime));
4585 :
4586 0 : Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_MS,
4587 0 : SECONDS_TO_MS(hiddenPlayTime));
4588 0 : LOG(LogLevel::Debug,
4589 : ("%p VIDEO_HIDDEN_PLAY_TIME_MS = %f", this, hiddenPlayTime));
4590 :
4591 0 : if (playTime > 0.0) {
4592 : // We have actually played something -> Report some valid-video telemetry.
4593 :
4594 : // Keyed by audio+video or video alone, and by a resolution range.
4595 0 : nsCString key(mMediaInfo.HasAudio() ? "AV," : "V,");
4596 : static const struct
4597 : {
4598 : int32_t mH;
4599 : const char* mRes;
4600 : } sResolutions[] = { { 240, "0<h<=240" }, { 480, "240<h<=480" },
4601 : { 576, "480<h<=576" }, { 720, "576<h<=720" },
4602 : { 1080, "720<h<=1080" }, { 2160, "1080<h<=2160" } };
4603 0 : const char* resolution = "h>2160";
4604 0 : int32_t height = mMediaInfo.mVideo.mImage.height;
4605 0 : for (const auto& res : sResolutions) {
4606 0 : if (height <= res.mH) {
4607 0 : resolution = res.mRes;
4608 0 : break;
4609 : }
4610 : }
4611 0 : key.AppendASCII(resolution);
4612 :
4613 : uint32_t hiddenPercentage =
4614 0 : uint32_t(hiddenPlayTime / playTime * 100.0 + 0.5);
4615 : Telemetry::Accumulate(
4616 0 : Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE, key, hiddenPercentage);
4617 : // Also accumulate all percentages in an "All" key.
4618 : Telemetry::Accumulate(Telemetry::VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE,
4619 0 : NS_LITERAL_CSTRING("All"),
4620 0 : hiddenPercentage);
4621 0 : LOG(LogLevel::Debug,
4622 : ("%p VIDEO_HIDDEN_PLAY_TIME_PERCENTAGE = %u, keys: '%s' and 'All'",
4623 : this,
4624 : hiddenPercentage,
4625 : key.get()));
4626 :
4627 : uint32_t videoDecodeSuspendPercentage =
4628 0 : uint32_t(videoDecodeSuspendTime / playTime * 100.0 + 0.5);
4629 : Telemetry::Accumulate(Telemetry::VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE,
4630 : key,
4631 0 : videoDecodeSuspendPercentage);
4632 : Telemetry::Accumulate(Telemetry::VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE,
4633 0 : NS_LITERAL_CSTRING("All"),
4634 0 : videoDecodeSuspendPercentage);
4635 0 : LOG(LogLevel::Debug,
4636 : ("%p VIDEO_INFERRED_DECODE_SUSPEND_PERCENTAGE = %u, keys: '%s' and "
4637 : "'All'",
4638 : this,
4639 : videoDecodeSuspendPercentage,
4640 : key.get()));
4641 :
4642 0 : if (data.mInterKeyframeCount != 0) {
4643 : uint32_t average_ms = uint32_t(
4644 0 : std::min<uint64_t>(double(data.mInterKeyframeSum_us) /
4645 0 : double(data.mInterKeyframeCount) / 1000.0 +
4646 : 0.5,
4647 0 : UINT32_MAX));
4648 : Telemetry::Accumulate(
4649 0 : Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS, key, average_ms);
4650 : Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_AVERAGE_MS,
4651 0 : NS_LITERAL_CSTRING("All"),
4652 0 : average_ms);
4653 0 : LOG(LogLevel::Debug,
4654 : ("%p VIDEO_INTER_KEYFRAME_AVERAGE_MS = %u, keys: '%s' and 'All'",
4655 : this,
4656 : average_ms,
4657 : key.get()));
4658 :
4659 0 : uint32_t max_ms = uint32_t(std::min<uint64_t>(
4660 0 : (data.mInterKeyFrameMax_us + 500) / 1000, UINT32_MAX));
4661 : Telemetry::Accumulate(
4662 0 : Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS, key, max_ms);
4663 : Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS,
4664 0 : NS_LITERAL_CSTRING("All"),
4665 0 : max_ms);
4666 0 : LOG(LogLevel::Debug,
4667 : ("%p VIDEO_INTER_KEYFRAME_MAX_MS = %u, keys: '%s' and 'All'",
4668 : this,
4669 : max_ms,
4670 : key.get()));
4671 : } else {
4672 : // Here, we have played *some* of the video, but didn't get more than 1
4673 : // keyframe. Report '0' if we have played for longer than the video-
4674 : // decode-suspend delay (showing recovery would be difficult).
4675 0 : uint32_t suspendDelay_ms = StaticPrefs::MediaSuspendBkgndVideoDelayMs();
4676 0 : if (uint32_t(playTime * 1000.0) > suspendDelay_ms) {
4677 0 : Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS, key, 0);
4678 : Telemetry::Accumulate(Telemetry::VIDEO_INTER_KEYFRAME_MAX_MS,
4679 0 : NS_LITERAL_CSTRING("All"),
4680 0 : 0);
4681 0 : LOG(LogLevel::Debug,
4682 : ("%p VIDEO_INTER_KEYFRAME_MAX_MS = 0 (only 1 keyframe), keys: "
4683 : "'%s' and 'All'",
4684 : this,
4685 : key.get()));
4686 : }
4687 : }
4688 : }
4689 : }
4690 0 : }
4691 :
4692 : void
4693 0 : HTMLMediaElement::UnbindFromTree(bool aDeep, bool aNullParent)
4694 : {
4695 0 : mUnboundFromTree = true;
4696 0 : mVisibilityState = Visibility::UNTRACKED;
4697 :
4698 0 : nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent);
4699 :
4700 0 : MOZ_ASSERT(IsHidden());
4701 0 : NotifyDecoderActivityChanges();
4702 :
4703 0 : RefPtr<HTMLMediaElement> self(this);
4704 : nsCOMPtr<nsIRunnable> task =
4705 0 : NS_NewRunnableFunction("dom::HTMLMediaElement::UnbindFromTree", [self]() {
4706 0 : if (self->mUnboundFromTree) {
4707 0 : self->Pause();
4708 : }
4709 0 : });
4710 0 : RunInStableState(task);
4711 0 : }
4712 :
4713 : static bool
4714 0 : IsVP9InMP4(const MediaContainerType& aContainerType)
4715 : {
4716 0 : const MediaContainerType mimeType(aContainerType.Type());
4717 0 : return DecoderTraits::IsMP4SupportedType(
4718 : mimeType,
4719 0 : /* DecoderDoctorDiagnostics* */ nullptr) &&
4720 0 : IsVP9CodecString(aContainerType.ExtendedType().Codecs().AsString());
4721 : }
4722 :
4723 : /* static */
4724 : CanPlayStatus
4725 0 : HTMLMediaElement::GetCanPlay(const nsAString& aType,
4726 : DecoderDoctorDiagnostics* aDiagnostics)
4727 : {
4728 0 : Maybe<MediaContainerType> containerType = MakeMediaContainerType(aType);
4729 0 : if (!containerType) {
4730 : return CANPLAY_NO;
4731 : }
4732 : CanPlayStatus status =
4733 0 : DecoderTraits::CanHandleContainerType(*containerType, aDiagnostics);
4734 0 : if (status == CANPLAY_YES && IsVP9InMP4(*containerType)) {
4735 : // We don't have a demuxer that can handle VP9 in non-fragmented MP4.
4736 : // So special-case VP9 in MP4 here, as we assume canPlayType() implies
4737 : // non-fragmented MP4 anyway. Note we report that we can play VP9
4738 : // in MP4 in MediaSource.isTypeSupported(), as the fragmented MP4
4739 : // demuxer can handle VP9 in fragmented MP4.
4740 : return CANPLAY_NO;
4741 : }
4742 0 : if (status == CANPLAY_YES &&
4743 0 : (*containerType).ExtendedType().Codecs().IsEmpty()) {
4744 : // Per spec: 'Generally, a user agent should never return "probably" for a
4745 : // type that allows the `codecs` parameter if that parameter is not
4746 : // present.' As all our currently-supported types allow for `codecs`, we can
4747 : // do this check here.
4748 : // TODO: Instead, missing `codecs` should be checked in each decoder's
4749 : // `IsSupportedType` call from `CanHandleCodecsType()`.
4750 : // See bug 1399023.
4751 : return CANPLAY_MAYBE;
4752 : }
4753 0 : return status;
4754 : }
4755 :
4756 : void
4757 0 : HTMLMediaElement::CanPlayType(const nsAString& aType, nsAString& aResult)
4758 : {
4759 0 : DecoderDoctorDiagnostics diagnostics;
4760 0 : CanPlayStatus canPlay = GetCanPlay(aType, &diagnostics);
4761 0 : diagnostics.StoreFormatDiagnostics(
4762 0 : OwnerDoc(), aType, canPlay != CANPLAY_NO, __func__);
4763 0 : switch (canPlay) {
4764 : case CANPLAY_NO:
4765 0 : aResult.Truncate();
4766 0 : break;
4767 : case CANPLAY_YES:
4768 0 : aResult.AssignLiteral("probably");
4769 0 : break;
4770 : case CANPLAY_MAYBE:
4771 0 : aResult.AssignLiteral("maybe");
4772 0 : break;
4773 : default:
4774 0 : MOZ_ASSERT_UNREACHABLE("Unexpected case.");
4775 : break;
4776 : }
4777 :
4778 0 : LOG(LogLevel::Debug,
4779 : ("%p CanPlayType(%s) = \"%s\"",
4780 : this,
4781 : NS_ConvertUTF16toUTF8(aType).get(),
4782 : NS_ConvertUTF16toUTF8(aResult).get()));
4783 0 : }
4784 :
4785 : void
4786 0 : HTMLMediaElement::AssertReadyStateIsNothing()
4787 : {
4788 : #ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED
4789 0 : if (mReadyState != HAVE_NOTHING) {
4790 : char buf[1024];
4791 0 : SprintfLiteral(buf,
4792 : "readyState=%d networkState=%d mLoadWaitStatus=%d "
4793 : "mSourceLoadCandidate=%d "
4794 : "mIsLoadingFromSourceChildren=%d mPreloadAction=%d "
4795 : "mSuspendedForPreloadNone=%d error=%d",
4796 0 : int(mReadyState),
4797 0 : int(mNetworkState),
4798 0 : int(mLoadWaitStatus),
4799 0 : !!mSourceLoadCandidate,
4800 0 : mIsLoadingFromSourceChildren,
4801 0 : int(mPreloadAction),
4802 0 : mSuspendedForPreloadNone,
4803 0 : GetError() ? GetError()->Code() : 0);
4804 0 : MOZ_CRASH_UNSAFE_PRINTF("ReadyState should be HAVE_NOTHING! %s", buf);
4805 : }
4806 : #endif
4807 0 : }
4808 :
4809 : nsresult
4810 0 : HTMLMediaElement::InitializeDecoderAsClone(ChannelMediaDecoder* aOriginal)
4811 : {
4812 0 : NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
4813 0 : NS_ASSERTION(mDecoder == nullptr, "Shouldn't have a decoder");
4814 0 : AssertReadyStateIsNothing();
4815 :
4816 : MediaDecoderInit decoderInit(this,
4817 0 : mMuted ? 0.0 : mVolume,
4818 0 : mPreservesPitch,
4819 : mPlaybackRate,
4820 0 : mPreloadAction ==
4821 : HTMLMediaElement::PRELOAD_METADATA,
4822 0 : mHasSuspendTaint,
4823 0 : HasAttr(kNameSpaceID_None, nsGkAtoms::loop),
4824 0 : aOriginal->ContainerType());
4825 :
4826 0 : RefPtr<ChannelMediaDecoder> decoder = aOriginal->Clone(decoderInit);
4827 0 : if (!decoder)
4828 : return NS_ERROR_FAILURE;
4829 :
4830 0 : LOG(LogLevel::Debug,
4831 : ("%p Cloned decoder %p from %p", this, decoder.get(), aOriginal));
4832 :
4833 0 : return FinishDecoderSetup(decoder);
4834 : }
4835 :
4836 : template<typename DecoderType, typename... LoadArgs>
4837 : nsresult
4838 0 : HTMLMediaElement::SetupDecoder(DecoderType* aDecoder, LoadArgs&&... aArgs)
4839 : {
4840 0 : LOG(LogLevel::Debug,
4841 : ("%p Created decoder %p for type %s",
4842 : this,
4843 : aDecoder,
4844 : aDecoder->ContainerType().OriginalString().Data()));
4845 :
4846 0 : nsresult rv = aDecoder->Load(std::forward<LoadArgs>(aArgs)...);
4847 0 : if (NS_FAILED(rv)) {
4848 0 : aDecoder->Shutdown();
4849 0 : LOG(LogLevel::Debug, ("%p Failed to load for decoder %p", this, aDecoder));
4850 : return rv;
4851 : }
4852 :
4853 0 : rv = FinishDecoderSetup(aDecoder);
4854 : // Only ChannelMediaDecoder supports resource cloning.
4855 0 : if (IsSame<DecoderType, ChannelMediaDecoder>::value && NS_SUCCEEDED(rv)) {
4856 0 : AddMediaElementToURITable();
4857 0 : NS_ASSERTION(
4858 : MediaElementTableCount(this, mLoadingSrc) == 1,
4859 : "Media element should have single table entry if decode initialized");
4860 : }
4861 :
4862 : return rv;
4863 : }
4864 :
4865 : nsresult
4866 0 : HTMLMediaElement::InitializeDecoderForChannel(nsIChannel* aChannel,
4867 : nsIStreamListener** aListener)
4868 : {
4869 0 : NS_ASSERTION(mLoadingSrc, "mLoadingSrc must already be set");
4870 0 : AssertReadyStateIsNothing();
4871 :
4872 0 : DecoderDoctorDiagnostics diagnostics;
4873 :
4874 0 : nsAutoCString mimeType;
4875 0 : aChannel->GetContentType(mimeType);
4876 0 : NS_ASSERTION(!mimeType.IsEmpty(), "We should have the Content-Type.");
4877 0 : NS_ConvertUTF8toUTF16 mimeUTF16(mimeType);
4878 :
4879 0 : RefPtr<HTMLMediaElement> self = this;
4880 0 : auto reportCanPlay = [&, self](bool aCanPlay) {
4881 0 : diagnostics.StoreFormatDiagnostics(
4882 0 : self->OwnerDoc(), mimeUTF16, aCanPlay, __func__);
4883 0 : if (!aCanPlay) {
4884 0 : nsAutoString src;
4885 0 : self->GetCurrentSrc(src);
4886 0 : const char16_t* params[] = { mimeUTF16.get(), src.get() };
4887 0 : self->ReportLoadError(
4888 0 : "MediaLoadUnsupportedMimeType", params, ArrayLength(params));
4889 : }
4890 0 : };
4891 :
4892 0 : auto onExit = MakeScopeExit([self] {
4893 0 : if (self->mChannelLoader) {
4894 0 : self->mChannelLoader->Done();
4895 0 : self->mChannelLoader = nullptr;
4896 : }
4897 0 : });
4898 :
4899 0 : Maybe<MediaContainerType> containerType = MakeMediaContainerType(mimeType);
4900 0 : if (!containerType) {
4901 0 : reportCanPlay(false);
4902 0 : return NS_ERROR_FAILURE;
4903 : }
4904 :
4905 : MediaDecoderInit decoderInit(this,
4906 0 : mMuted ? 0.0 : mVolume,
4907 0 : mPreservesPitch,
4908 : mPlaybackRate,
4909 0 : mPreloadAction ==
4910 : HTMLMediaElement::PRELOAD_METADATA,
4911 0 : mHasSuspendTaint,
4912 0 : HasAttr(kNameSpaceID_None, nsGkAtoms::loop),
4913 0 : *containerType);
4914 :
4915 : #ifdef MOZ_ANDROID_HLS_SUPPORT
4916 : if (HLSDecoder::IsSupportedType(*containerType)) {
4917 : RefPtr<HLSDecoder> decoder = new HLSDecoder(decoderInit);
4918 : reportCanPlay(true);
4919 : return SetupDecoder(decoder.get(), aChannel);
4920 : }
4921 : #endif
4922 :
4923 : RefPtr<ChannelMediaDecoder> decoder =
4924 0 : ChannelMediaDecoder::Create(decoderInit, &diagnostics);
4925 0 : if (!decoder) {
4926 0 : reportCanPlay(false);
4927 0 : return NS_ERROR_FAILURE;
4928 : }
4929 :
4930 0 : reportCanPlay(true);
4931 0 : bool isPrivateBrowsing = NodePrincipal()->GetPrivateBrowsingId() > 0;
4932 0 : return SetupDecoder(decoder.get(), aChannel, isPrivateBrowsing, aListener);
4933 : }
4934 :
4935 : nsresult
4936 0 : HTMLMediaElement::FinishDecoderSetup(MediaDecoder* aDecoder)
4937 : {
4938 0 : ChangeNetworkState(NETWORK_LOADING);
4939 :
4940 : // Set mDecoder now so if methods like GetCurrentSrc get called between
4941 : // here and Load(), they work.
4942 0 : SetDecoder(aDecoder);
4943 :
4944 : // Notify the decoder of the initial activity status.
4945 0 : NotifyDecoderActivityChanges();
4946 :
4947 : // Update decoder principal before we start decoding, since it
4948 : // can affect how we feed data to MediaStreams
4949 0 : NotifyDecoderPrincipalChanged();
4950 :
4951 0 : for (OutputMediaStream& ms : mOutputStreams) {
4952 0 : if (ms.mCapturingMediaStream) {
4953 0 : MOZ_ASSERT(!ms.mCapturingDecoder);
4954 : continue;
4955 : }
4956 :
4957 0 : ms.mCapturingDecoder = true;
4958 0 : aDecoder->AddOutputStream(ms.mStream->GetInputStream()->AsProcessedStream(),
4959 : ms.mNextAvailableTrackID,
4960 0 : ms.mFinishWhenEnded);
4961 : }
4962 :
4963 0 : if (mMediaKeys) {
4964 0 : if (mMediaKeys->GetCDMProxy()) {
4965 0 : mDecoder->SetCDMProxy(mMediaKeys->GetCDMProxy());
4966 : } else {
4967 : // CDM must have crashed.
4968 0 : ShutdownDecoder();
4969 0 : return NS_ERROR_FAILURE;
4970 : }
4971 : }
4972 :
4973 0 : if (mChannelLoader) {
4974 0 : mChannelLoader->Done();
4975 0 : mChannelLoader = nullptr;
4976 : }
4977 :
4978 : // We may want to suspend the new stream now.
4979 : // This will also do an AddRemoveSelfReference.
4980 0 : NotifyOwnerDocumentActivityChanged();
4981 :
4982 0 : if (mPausedForInactiveDocumentOrChannel) {
4983 0 : mDecoder->Suspend();
4984 : }
4985 :
4986 0 : if (!mPaused) {
4987 0 : SetPlayedOrSeeked(true);
4988 0 : if (!mPausedForInactiveDocumentOrChannel) {
4989 0 : mDecoder->Play();
4990 : }
4991 : }
4992 :
4993 : return NS_OK;
4994 : }
4995 :
4996 0 : class HTMLMediaElement::StreamListener : public MediaStreamListener
4997 : {
4998 : public:
4999 0 : StreamListener(HTMLMediaElement* aElement, const char* aName)
5000 0 : : mElement(aElement)
5001 : , mHaveCurrentData(false)
5002 : , mFinished(false)
5003 : , mMutex(aName)
5004 0 : , mPendingNotifyOutput(false)
5005 : {
5006 0 : }
5007 0 : void Forget()
5008 : {
5009 0 : if (mElement) {
5010 0 : HTMLMediaElement* element = mElement;
5011 0 : mElement = nullptr;
5012 0 : element->UpdateReadyStateInternal();
5013 : }
5014 0 : }
5015 :
5016 : // Main thread
5017 :
5018 0 : MediaDecoderOwner::NextFrameStatus NextFrameStatus()
5019 : {
5020 0 : if (!mElement || !mHaveCurrentData || mFinished) {
5021 : return MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
5022 : }
5023 0 : return MediaDecoderOwner::NEXT_FRAME_AVAILABLE;
5024 : }
5025 :
5026 0 : void DoNotifyOutput()
5027 : {
5028 : {
5029 0 : MutexAutoLock lock(mMutex);
5030 0 : mPendingNotifyOutput = false;
5031 : }
5032 0 : if (mElement && mHaveCurrentData) {
5033 0 : RefPtr<HTMLMediaElement> kungFuDeathGrip = mElement;
5034 0 : kungFuDeathGrip->FireTimeUpdate(true);
5035 : }
5036 0 : }
5037 0 : void DoNotifyHaveCurrentData()
5038 : {
5039 0 : mHaveCurrentData = true;
5040 0 : if (mElement) {
5041 0 : RefPtr<HTMLMediaElement> kungFuDeathGrip = mElement;
5042 0 : kungFuDeathGrip->FirstFrameLoaded();
5043 0 : kungFuDeathGrip->UpdateReadyStateInternal();
5044 : }
5045 0 : DoNotifyOutput();
5046 0 : }
5047 :
5048 : // These notifications run on the media graph thread so we need to
5049 : // dispatch events to the main thread.
5050 0 : virtual void NotifyHasCurrentData(MediaStreamGraph* aGraph) override
5051 : {
5052 0 : MutexAutoLock lock(mMutex);
5053 0 : aGraph->DispatchToMainThreadAfterStreamStateUpdate(NewRunnableMethod(
5054 : "dom::HTMLMediaElement::StreamListener::DoNotifyHaveCurrentData",
5055 : this,
5056 0 : &StreamListener::DoNotifyHaveCurrentData));
5057 0 : }
5058 0 : virtual void NotifyOutput(MediaStreamGraph* aGraph,
5059 : GraphTime aCurrentTime) override
5060 : {
5061 0 : MutexAutoLock lock(mMutex);
5062 0 : if (mPendingNotifyOutput)
5063 0 : return;
5064 0 : mPendingNotifyOutput = true;
5065 0 : aGraph->DispatchToMainThreadAfterStreamStateUpdate(
5066 0 : NewRunnableMethod("dom::HTMLMediaElement::StreamListener::DoNotifyOutput",
5067 : this,
5068 0 : &StreamListener::DoNotifyOutput));
5069 : }
5070 :
5071 : private:
5072 : // These fields may only be accessed on the main thread
5073 : HTMLMediaElement* mElement;
5074 : bool mHaveCurrentData;
5075 : bool mFinished;
5076 :
5077 : // mMutex protects the fields below; they can be accessed on any thread
5078 : Mutex mMutex;
5079 : bool mPendingNotifyOutput;
5080 : };
5081 :
5082 0 : class HTMLMediaElement::MediaStreamTracksAvailableCallback
5083 : : public OnTracksAvailableCallback
5084 : {
5085 : public:
5086 0 : explicit MediaStreamTracksAvailableCallback(HTMLMediaElement* aElement)
5087 0 : : OnTracksAvailableCallback()
5088 0 : , mElement(aElement)
5089 : {
5090 0 : }
5091 0 : virtual void NotifyTracksAvailable(DOMMediaStream* aStream) override
5092 : {
5093 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
5094 :
5095 0 : if (!mElement) {
5096 : return;
5097 : }
5098 0 : mElement->NotifyMediaStreamTracksAvailable(aStream);
5099 : }
5100 :
5101 : private:
5102 : WeakPtr<HTMLMediaElement> mElement;
5103 : };
5104 :
5105 0 : class HTMLMediaElement::MediaStreamTrackListener
5106 : : public DOMMediaStream::TrackListener
5107 : {
5108 : public:
5109 : explicit MediaStreamTrackListener(HTMLMediaElement* aElement)
5110 0 : : mElement(aElement)
5111 : {
5112 : }
5113 :
5114 0 : void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override
5115 : {
5116 0 : mElement->NotifyMediaStreamTrackAdded(aTrack);
5117 0 : }
5118 :
5119 0 : void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override
5120 : {
5121 0 : mElement->NotifyMediaStreamTrackRemoved(aTrack);
5122 0 : }
5123 :
5124 0 : void NotifyActive() override
5125 : {
5126 0 : LOG(LogLevel::Debug,
5127 : ("%p, mSrcStream %p became active",
5128 : mElement,
5129 : mElement->mSrcStream.get()));
5130 0 : mElement->CheckAutoplayDataReady();
5131 0 : }
5132 :
5133 0 : void NotifyInactive() override
5134 : {
5135 0 : LOG(LogLevel::Debug,
5136 : ("%p, mSrcStream %p became inactive",
5137 : mElement,
5138 : mElement->mSrcStream.get()));
5139 0 : MOZ_ASSERT(!mElement->mSrcStream->Active());
5140 0 : if (mElement->mMediaStreamListener) {
5141 0 : mElement->mMediaStreamListener->Forget();
5142 : }
5143 0 : mElement->PlaybackEnded();
5144 0 : }
5145 :
5146 : protected:
5147 : HTMLMediaElement* const mElement;
5148 : };
5149 :
5150 : void
5151 0 : HTMLMediaElement::UpdateSrcMediaStreamPlaying(uint32_t aFlags)
5152 : {
5153 0 : if (!mSrcStream) {
5154 : return;
5155 : }
5156 : // We might be in cycle collection with mSrcStream->GetPlaybackStream()
5157 : // already returning null due to unlinking.
5158 :
5159 0 : MediaStream* stream = GetSrcMediaStream();
5160 0 : bool shouldPlay = !(aFlags & REMOVING_SRC_STREAM) && !mPaused &&
5161 0 : !mPausedForInactiveDocumentOrChannel && stream;
5162 0 : if (shouldPlay == mSrcStreamIsPlaying) {
5163 : return;
5164 : }
5165 0 : mSrcStreamIsPlaying = shouldPlay;
5166 :
5167 0 : LOG(LogLevel::Debug,
5168 : ("MediaElement %p %s playback of DOMMediaStream %p",
5169 : this,
5170 : shouldPlay ? "Setting up" : "Removing",
5171 : mSrcStream.get()));
5172 :
5173 0 : if (shouldPlay) {
5174 0 : mSrcStreamPausedCurrentTime = -1;
5175 :
5176 : mMediaStreamListener =
5177 0 : new StreamListener(this, "HTMLMediaElement::mMediaStreamListener");
5178 0 : stream->AddListener(mMediaStreamListener);
5179 :
5180 0 : stream->AddAudioOutput(this);
5181 0 : SetVolumeInternal();
5182 :
5183 0 : VideoFrameContainer* container = GetVideoFrameContainer();
5184 0 : if (mSelectedVideoStreamTrack && container) {
5185 0 : mSelectedVideoStreamTrack->AddVideoOutput(container);
5186 : }
5187 :
5188 0 : SetCapturedOutputStreamsEnabled(true); // Unmute
5189 : // If the input is a media stream, we don't check its data and always regard
5190 : // it as audible when it's playing.
5191 0 : SetAudibleState(true);
5192 : } else {
5193 0 : if (stream) {
5194 0 : mSrcStreamPausedCurrentTime = CurrentTime();
5195 :
5196 0 : stream->RemoveListener(mMediaStreamListener);
5197 :
5198 0 : stream->RemoveAudioOutput(this);
5199 0 : VideoFrameContainer* container = GetVideoFrameContainer();
5200 0 : if (mSelectedVideoStreamTrack && container) {
5201 0 : mSelectedVideoStreamTrack->RemoveVideoOutput(container);
5202 : }
5203 :
5204 0 : SetCapturedOutputStreamsEnabled(false); // Mute
5205 : }
5206 : // If stream is null, then DOMMediaStream::Destroy must have been
5207 : // called and that will remove all listeners/outputs.
5208 :
5209 0 : mMediaStreamListener->Forget();
5210 0 : mMediaStreamListener = nullptr;
5211 : }
5212 : }
5213 :
5214 : void
5215 0 : HTMLMediaElement::SetupSrcMediaStreamPlayback(DOMMediaStream* aStream)
5216 : {
5217 0 : NS_ASSERTION(!mSrcStream && !mMediaStreamListener &&
5218 : !mMediaStreamSizeListener,
5219 : "Should have been ended already");
5220 :
5221 0 : mSrcStream = aStream;
5222 :
5223 0 : nsPIDOMWindowInner* window = OwnerDoc()->GetInnerWindow();
5224 0 : if (!window) {
5225 0 : return;
5226 : }
5227 :
5228 0 : UpdateSrcMediaStreamPlaying();
5229 :
5230 : // If we pause this media element, track changes in the underlying stream
5231 : // will continue to fire events at this element and alter its track list.
5232 : // That's simpler than delaying the events, but probably confusing...
5233 0 : nsTArray<RefPtr<MediaStreamTrack>> tracks;
5234 0 : mSrcStream->GetTracks(tracks);
5235 0 : for (const RefPtr<MediaStreamTrack>& track : tracks) {
5236 0 : NotifyMediaStreamTrackAdded(track);
5237 : }
5238 :
5239 0 : mSrcStream->OnTracksAvailable(new MediaStreamTracksAvailableCallback(this));
5240 0 : mMediaStreamTrackListener = new MediaStreamTrackListener(this);
5241 0 : mSrcStream->RegisterTrackListener(mMediaStreamTrackListener);
5242 :
5243 0 : mSrcStream->AddPrincipalChangeObserver(this);
5244 0 : mSrcStreamVideoPrincipal = mSrcStream->GetVideoPrincipal();
5245 :
5246 0 : ChangeNetworkState(NETWORK_IDLE);
5247 0 : ChangeDelayLoadStatus(false);
5248 0 : CheckAutoplayDataReady();
5249 :
5250 : // FirstFrameLoaded() will be called when the stream has current data.
5251 : }
5252 :
5253 : void
5254 0 : HTMLMediaElement::EndSrcMediaStreamPlayback()
5255 : {
5256 0 : MOZ_ASSERT(mSrcStream);
5257 :
5258 0 : UpdateSrcMediaStreamPlaying(REMOVING_SRC_STREAM);
5259 :
5260 0 : if (mMediaStreamSizeListener) {
5261 0 : MOZ_ASSERT(mSelectedVideoStreamTrack);
5262 0 : if (mSelectedVideoStreamTrack) {
5263 0 : mSelectedVideoStreamTrack->RemoveDirectListener(mMediaStreamSizeListener);
5264 : }
5265 0 : mMediaStreamSizeListener->Forget();
5266 : }
5267 0 : mSelectedVideoStreamTrack = nullptr;
5268 0 : mMediaStreamSizeListener = nullptr;
5269 :
5270 0 : mSrcStream->UnregisterTrackListener(mMediaStreamTrackListener);
5271 0 : mMediaStreamTrackListener = nullptr;
5272 0 : mSrcStreamTracksAvailable = false;
5273 :
5274 0 : mSrcStream->RemovePrincipalChangeObserver(this);
5275 0 : mSrcStreamVideoPrincipal = nullptr;
5276 :
5277 0 : for (OutputMediaStream& ms : mOutputStreams) {
5278 0 : for (auto pair : ms.mTrackPorts) {
5279 0 : pair.second()->Destroy();
5280 : }
5281 0 : ms.mTrackPorts.Clear();
5282 : }
5283 :
5284 0 : mSrcStream = nullptr;
5285 0 : }
5286 :
5287 : static already_AddRefed<AudioTrack>
5288 0 : CreateAudioTrack(AudioStreamTrack* aStreamTrack, nsIGlobalObject* aOwnerGlobal)
5289 : {
5290 0 : nsAutoString id;
5291 0 : nsAutoString label;
5292 0 : aStreamTrack->GetId(id);
5293 0 : aStreamTrack->GetLabel(label, CallerType::System);
5294 :
5295 : return MediaTrackList::CreateAudioTrack(
5296 0 : aOwnerGlobal, id, NS_LITERAL_STRING("main"), label, EmptyString(), true);
5297 : }
5298 :
5299 : static already_AddRefed<VideoTrack>
5300 0 : CreateVideoTrack(VideoStreamTrack* aStreamTrack, nsIGlobalObject* aOwnerGlobal)
5301 : {
5302 0 : nsAutoString id;
5303 0 : nsAutoString label;
5304 0 : aStreamTrack->GetId(id);
5305 0 : aStreamTrack->GetLabel(label, CallerType::System);
5306 :
5307 : return MediaTrackList::CreateVideoTrack(aOwnerGlobal,
5308 : id,
5309 0 : NS_LITERAL_STRING("main"),
5310 : label,
5311 0 : EmptyString(),
5312 0 : aStreamTrack);
5313 : }
5314 :
5315 : void
5316 0 : HTMLMediaElement::NotifyMediaStreamTrackAdded(
5317 : const RefPtr<MediaStreamTrack>& aTrack)
5318 : {
5319 0 : MOZ_ASSERT(aTrack);
5320 :
5321 0 : if (aTrack->Ended()) {
5322 0 : return;
5323 : }
5324 :
5325 : #ifdef DEBUG
5326 0 : nsString id;
5327 0 : aTrack->GetId(id);
5328 :
5329 0 : LOG(LogLevel::Debug,
5330 : ("%p, Adding %sTrack with id %s",
5331 : this,
5332 : aTrack->AsAudioStreamTrack() ? "Audio" : "Video",
5333 : NS_ConvertUTF16toUTF8(id).get()));
5334 : #endif
5335 :
5336 0 : if (AudioStreamTrack* t = aTrack->AsAudioStreamTrack()) {
5337 : RefPtr<AudioTrack> audioTrack =
5338 0 : CreateAudioTrack(t, AudioTracks()->GetOwnerGlobal());
5339 0 : AudioTracks()->AddTrack(audioTrack);
5340 0 : } else if (VideoStreamTrack* t = aTrack->AsVideoStreamTrack()) {
5341 : // TODO: Fix this per the spec on bug 1273443.
5342 0 : if (!IsVideo()) {
5343 0 : return;
5344 : }
5345 : RefPtr<VideoTrack> videoTrack =
5346 0 : CreateVideoTrack(t, VideoTracks()->GetOwnerGlobal());
5347 0 : VideoTracks()->AddTrack(videoTrack);
5348 : // New MediaStreamTrack added, set the new added video track as selected
5349 : // video track when there is no selected track.
5350 0 : if (VideoTracks()->SelectedIndex() == -1) {
5351 0 : MOZ_ASSERT(!mSelectedVideoStreamTrack);
5352 0 : videoTrack->SetEnabledInternal(true, MediaTrack::FIRE_NO_EVENTS);
5353 : }
5354 : }
5355 :
5356 0 : UpdateReadyStateInternal();
5357 : }
5358 :
5359 : void
5360 0 : HTMLMediaElement::NotifyMediaStreamTrackRemoved(
5361 : const RefPtr<MediaStreamTrack>& aTrack)
5362 : {
5363 0 : MOZ_ASSERT(aTrack);
5364 :
5365 0 : nsAutoString id;
5366 0 : aTrack->GetId(id);
5367 :
5368 0 : LOG(LogLevel::Debug,
5369 : ("%p, Removing %sTrack with id %s",
5370 : this,
5371 : aTrack->AsAudioStreamTrack() ? "Audio" : "Video",
5372 : NS_ConvertUTF16toUTF8(id).get()));
5373 :
5374 0 : if (MediaTrack* t = AudioTracks()->GetTrackById(id)) {
5375 0 : AudioTracks()->RemoveTrack(t);
5376 0 : } else if (MediaTrack* t = VideoTracks()->GetTrackById(id)) {
5377 0 : VideoTracks()->RemoveTrack(t);
5378 : } else {
5379 0 : NS_ASSERTION(aTrack->AsVideoStreamTrack() && !IsVideo(),
5380 : "MediaStreamTrack ended but did not exist in track lists. "
5381 : "This is only allowed if a video element ends and we are an "
5382 : "audio element.");
5383 0 : return;
5384 : }
5385 : }
5386 :
5387 : void
5388 0 : HTMLMediaElement::ProcessMediaFragmentURI()
5389 : {
5390 0 : nsMediaFragmentURIParser parser(mLoadingSrc);
5391 :
5392 0 : if (mDecoder && parser.HasEndTime()) {
5393 0 : mFragmentEnd = parser.GetEndTime();
5394 : }
5395 :
5396 0 : if (parser.HasStartTime()) {
5397 0 : SetCurrentTime(parser.GetStartTime());
5398 0 : mFragmentStart = parser.GetStartTime();
5399 : }
5400 0 : }
5401 :
5402 : void
5403 0 : HTMLMediaElement::MetadataLoaded(const MediaInfo* aInfo,
5404 : UniquePtr<const MetadataTags> aTags)
5405 : {
5406 0 : MOZ_ASSERT(NS_IsMainThread());
5407 :
5408 0 : SetMediaInfo(*aInfo);
5409 :
5410 0 : mIsEncrypted =
5411 0 : aInfo->IsEncrypted() || mPendingEncryptedInitData.IsEncrypted();
5412 0 : mTags = std::move(aTags);
5413 0 : mLoadedDataFired = false;
5414 0 : ChangeReadyState(HAVE_METADATA);
5415 :
5416 0 : DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
5417 0 : if (IsVideo() && HasVideo()) {
5418 0 : DispatchAsyncEvent(NS_LITERAL_STRING("resize"));
5419 : }
5420 0 : NS_ASSERTION(!HasVideo() || (mMediaInfo.mVideo.mDisplay.width > 0 &&
5421 : mMediaInfo.mVideo.mDisplay.height > 0),
5422 : "Video resolution must be known on 'loadedmetadata'");
5423 0 : DispatchAsyncEvent(NS_LITERAL_STRING("loadedmetadata"));
5424 0 : if (mDecoder && mDecoder->IsTransportSeekable() &&
5425 0 : mDecoder->IsMediaSeekable()) {
5426 0 : ProcessMediaFragmentURI();
5427 0 : mDecoder->SetFragmentEndTime(mFragmentEnd);
5428 : }
5429 0 : if (mIsEncrypted) {
5430 : // We only support playback of encrypted content via MSE by default.
5431 0 : if (!mMediaSource && Preferences::GetBool("media.eme.mse-only", true)) {
5432 : DecodeError(
5433 0 : MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
5434 0 : "Encrypted content not supported outside of MSE"));
5435 0 : return;
5436 : }
5437 :
5438 : // Dispatch a distinct 'encrypted' event for each initData we have.
5439 0 : for (const auto& initData : mPendingEncryptedInitData.mInitDatas) {
5440 0 : DispatchEncrypted(initData.mInitData, initData.mType);
5441 : }
5442 0 : mPendingEncryptedInitData.Reset();
5443 : }
5444 :
5445 0 : if (IsVideo() && aInfo->HasVideo()) {
5446 : // We are a video element playing video so update the screen wakelock
5447 0 : NotifyOwnerDocumentActivityChanged();
5448 : }
5449 :
5450 0 : if (mDefaultPlaybackStartPosition != 0.0) {
5451 0 : SetCurrentTime(mDefaultPlaybackStartPosition);
5452 0 : mDefaultPlaybackStartPosition = 0.0;
5453 : }
5454 :
5455 0 : UpdateReadyStateInternal();
5456 :
5457 0 : if (!mSrcStream) {
5458 : return;
5459 : }
5460 0 : for (OutputMediaStream& ms : mOutputStreams) {
5461 0 : for (size_t i = 0; i < AudioTracks()->Length(); ++i) {
5462 0 : AudioTrack* t = (*AudioTracks())[i];
5463 0 : if (t->Enabled()) {
5464 0 : AddCaptureMediaTrackToOutputStream(t, ms);
5465 : }
5466 : }
5467 0 : if (IsVideo() && !ms.mCapturingAudioOnly) {
5468 : // Only add video tracks if we're a video element and the output stream
5469 : // wants video.
5470 0 : for (size_t i = 0; i < VideoTracks()->Length(); ++i) {
5471 0 : VideoTrack* t = (*VideoTracks())[i];
5472 0 : if (t->Selected()) {
5473 0 : AddCaptureMediaTrackToOutputStream(t, ms);
5474 : }
5475 : }
5476 : }
5477 : }
5478 : }
5479 :
5480 : void
5481 0 : HTMLMediaElement::FirstFrameLoaded()
5482 : {
5483 0 : LOG(LogLevel::Debug,
5484 : ("%p, FirstFrameLoaded() mFirstFrameLoaded=%d mWaitingForKey=%d",
5485 : this,
5486 : mFirstFrameLoaded,
5487 : mWaitingForKey));
5488 :
5489 0 : NS_ASSERTION(!mSuspendedAfterFirstFrame, "Should not have already suspended");
5490 :
5491 0 : if (!mFirstFrameLoaded) {
5492 0 : mFirstFrameLoaded = true;
5493 0 : UpdateReadyStateInternal();
5494 : }
5495 :
5496 0 : ChangeDelayLoadStatus(false);
5497 :
5498 0 : if (mDecoder && mAllowSuspendAfterFirstFrame && mPaused &&
5499 0 : !HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay) &&
5500 0 : mPreloadAction == HTMLMediaElement::PRELOAD_METADATA) {
5501 0 : mSuspendedAfterFirstFrame = true;
5502 0 : mDecoder->Suspend();
5503 : }
5504 0 : }
5505 :
5506 : void
5507 0 : HTMLMediaElement::NetworkError(const MediaResult& aError)
5508 : {
5509 0 : if (mReadyState == HAVE_NOTHING) {
5510 0 : NoSupportedMediaSourceError(aError.Description());
5511 : } else {
5512 0 : Error(MEDIA_ERR_NETWORK);
5513 : }
5514 0 : }
5515 :
5516 : void
5517 0 : HTMLMediaElement::DecodeError(const MediaResult& aError)
5518 : {
5519 0 : nsAutoString src;
5520 0 : GetCurrentSrc(src);
5521 0 : const char16_t* params[] = { src.get() };
5522 0 : ReportLoadError("MediaLoadDecodeError", params, ArrayLength(params));
5523 :
5524 0 : DecoderDoctorDiagnostics diagnostics;
5525 0 : diagnostics.StoreDecodeError(OwnerDoc(), aError, src, __func__);
5526 :
5527 0 : AudioTracks()->EmptyTracks();
5528 0 : VideoTracks()->EmptyTracks();
5529 0 : if (mIsLoadingFromSourceChildren) {
5530 0 : mErrorSink->ResetError();
5531 0 : if (mSourceLoadCandidate) {
5532 0 : DispatchAsyncSourceError(mSourceLoadCandidate);
5533 0 : QueueLoadFromSourceTask();
5534 : } else {
5535 0 : NS_WARNING("Should know the source we were loading from!");
5536 : }
5537 0 : } else if (mReadyState == HAVE_NOTHING) {
5538 0 : NoSupportedMediaSourceError(aError.Description());
5539 : } else {
5540 0 : Error(MEDIA_ERR_DECODE, aError.Description());
5541 : }
5542 0 : }
5543 :
5544 : void
5545 0 : HTMLMediaElement::DecodeWarning(const MediaResult& aError)
5546 : {
5547 0 : nsAutoString src;
5548 0 : GetCurrentSrc(src);
5549 0 : DecoderDoctorDiagnostics diagnostics;
5550 0 : diagnostics.StoreDecodeWarning(OwnerDoc(), aError, src, __func__);
5551 0 : }
5552 :
5553 : bool
5554 0 : HTMLMediaElement::HasError() const
5555 : {
5556 0 : return GetError();
5557 : }
5558 :
5559 : void
5560 0 : HTMLMediaElement::LoadAborted()
5561 : {
5562 0 : Error(MEDIA_ERR_ABORTED);
5563 0 : }
5564 :
5565 : void
5566 0 : HTMLMediaElement::Error(uint16_t aErrorCode, const nsACString& aErrorDetails)
5567 : {
5568 0 : mErrorSink->SetError(aErrorCode, aErrorDetails);
5569 0 : ChangeDelayLoadStatus(false);
5570 0 : UpdateAudioChannelPlayingState();
5571 0 : }
5572 :
5573 : void
5574 0 : HTMLMediaElement::PlaybackEnded()
5575 : {
5576 : // We changed state which can affect AddRemoveSelfReference
5577 0 : AddRemoveSelfReference();
5578 :
5579 0 : NS_ASSERTION(!mDecoder || mDecoder->IsEnded(),
5580 : "Decoder fired ended, but not in ended state");
5581 :
5582 : // Discard all output streams that have finished now.
5583 0 : for (int32_t i = mOutputStreams.Length() - 1; i >= 0; --i) {
5584 0 : if (mOutputStreams[i].mFinishWhenEnded) {
5585 0 : LOG(LogLevel::Debug,
5586 : ("Playback ended. Removing output stream %p",
5587 : mOutputStreams[i].mStream.get()));
5588 0 : mOutputStreams.RemoveElementAt(i);
5589 : }
5590 : }
5591 :
5592 0 : if (mSrcStream) {
5593 0 : LOG(LogLevel::Debug,
5594 : ("%p, got duration by reaching the end of the resource", this));
5595 0 : DispatchAsyncEvent(NS_LITERAL_STRING("durationchange"));
5596 : }
5597 :
5598 0 : if (HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) {
5599 0 : SetCurrentTime(0);
5600 0 : return;
5601 : }
5602 :
5603 0 : FireTimeUpdate(false);
5604 :
5605 0 : if (!mPaused) {
5606 0 : Pause();
5607 : }
5608 :
5609 0 : if (mSrcStream) {
5610 : // A MediaStream that goes from inactive to active shall be eligible for
5611 : // autoplay again according to the mediacapture-main spec.
5612 0 : mAutoplaying = true;
5613 : }
5614 :
5615 0 : DispatchAsyncEvent(NS_LITERAL_STRING("ended"));
5616 : }
5617 :
5618 : void
5619 0 : HTMLMediaElement::SeekStarted()
5620 : {
5621 0 : DispatchAsyncEvent(NS_LITERAL_STRING("seeking"));
5622 0 : }
5623 :
5624 : void
5625 0 : HTMLMediaElement::SeekCompleted()
5626 : {
5627 0 : mPlayingBeforeSeek = false;
5628 0 : SetPlayedOrSeeked(true);
5629 0 : if (mTextTrackManager) {
5630 0 : mTextTrackManager->DidSeek();
5631 : }
5632 0 : FireTimeUpdate(false);
5633 0 : DispatchAsyncEvent(NS_LITERAL_STRING("seeked"));
5634 : // We changed whether we're seeking so we need to AddRemoveSelfReference
5635 0 : AddRemoveSelfReference();
5636 0 : if (mCurrentPlayRangeStart == -1.0) {
5637 0 : mCurrentPlayRangeStart = CurrentTime();
5638 : }
5639 0 : }
5640 :
5641 : void
5642 0 : HTMLMediaElement::NotifySuspendedByCache(bool aSuspendedByCache)
5643 : {
5644 0 : mDownloadSuspendedByCache = aSuspendedByCache;
5645 0 : UpdateReadyStateInternal();
5646 0 : }
5647 :
5648 : void
5649 0 : HTMLMediaElement::DownloadSuspended()
5650 : {
5651 0 : if (mNetworkState == NETWORK_LOADING) {
5652 0 : DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
5653 : }
5654 0 : ChangeNetworkState(NETWORK_IDLE);
5655 0 : }
5656 :
5657 : void
5658 0 : HTMLMediaElement::DownloadResumed()
5659 : {
5660 0 : ChangeNetworkState(NETWORK_LOADING);
5661 0 : }
5662 :
5663 : void
5664 0 : HTMLMediaElement::CheckProgress(bool aHaveNewProgress)
5665 : {
5666 0 : MOZ_ASSERT(NS_IsMainThread());
5667 0 : MOZ_ASSERT(mNetworkState == NETWORK_LOADING);
5668 :
5669 0 : TimeStamp now = TimeStamp::NowLoRes();
5670 :
5671 0 : if (aHaveNewProgress) {
5672 0 : mDataTime = now;
5673 : }
5674 :
5675 : // If this is the first progress, or PROGRESS_MS has passed since the last
5676 : // progress event fired and more data has arrived since then, fire a
5677 : // progress event.
5678 0 : NS_ASSERTION((mProgressTime.IsNull() && !aHaveNewProgress) ||
5679 : !mDataTime.IsNull(),
5680 : "null TimeStamp mDataTime should not be used in comparison");
5681 0 : if (mProgressTime.IsNull()
5682 0 : ? aHaveNewProgress
5683 0 : : (now - mProgressTime >= TimeDuration::FromMilliseconds(PROGRESS_MS) &&
5684 0 : mDataTime > mProgressTime)) {
5685 0 : DispatchAsyncEvent(NS_LITERAL_STRING("progress"));
5686 : // Resolution() ensures that future data will have now > mProgressTime,
5687 : // and so will trigger another event. mDataTime is not reset because it
5688 : // is still required to detect stalled; it is similarly offset by
5689 : // resolution to indicate the new data has not yet arrived.
5690 0 : mProgressTime = now - TimeDuration::Resolution();
5691 0 : if (mDataTime > mProgressTime) {
5692 0 : mDataTime = mProgressTime;
5693 : }
5694 0 : if (!mProgressTimer) {
5695 0 : NS_ASSERTION(aHaveNewProgress,
5696 : "timer dispatched when there was no timer");
5697 : // Were stalled. Restart timer.
5698 0 : StartProgressTimer();
5699 0 : if (!mLoadedDataFired) {
5700 0 : ChangeDelayLoadStatus(true);
5701 : }
5702 : }
5703 : // Download statistics may have been updated, force a recheck of the
5704 : // readyState.
5705 0 : UpdateReadyStateInternal();
5706 : }
5707 :
5708 0 : if (now - mDataTime >= TimeDuration::FromMilliseconds(STALL_MS)) {
5709 0 : if (!mMediaSource) {
5710 0 : DispatchAsyncEvent(NS_LITERAL_STRING("stalled"));
5711 : } else {
5712 0 : ChangeDelayLoadStatus(false);
5713 : }
5714 :
5715 0 : NS_ASSERTION(mProgressTimer, "detected stalled without timer");
5716 : // Stop timer events, which prevents repeated stalled events until there
5717 : // is more progress.
5718 0 : StopProgress();
5719 : }
5720 :
5721 0 : AddRemoveSelfReference();
5722 0 : }
5723 :
5724 : /* static */
5725 : void
5726 0 : HTMLMediaElement::ProgressTimerCallback(nsITimer* aTimer, void* aClosure)
5727 : {
5728 0 : auto decoder = static_cast<HTMLMediaElement*>(aClosure);
5729 0 : decoder->CheckProgress(false);
5730 0 : }
5731 :
5732 : void
5733 0 : HTMLMediaElement::StartProgressTimer()
5734 : {
5735 0 : MOZ_ASSERT(NS_IsMainThread());
5736 0 : MOZ_ASSERT(mNetworkState == NETWORK_LOADING);
5737 0 : NS_ASSERTION(!mProgressTimer, "Already started progress timer.");
5738 :
5739 0 : NS_NewTimerWithFuncCallback(getter_AddRefs(mProgressTimer),
5740 : ProgressTimerCallback,
5741 : this,
5742 : PROGRESS_MS,
5743 : nsITimer::TYPE_REPEATING_SLACK,
5744 : "HTMLMediaElement::ProgressTimerCallback",
5745 0 : mMainThreadEventTarget);
5746 0 : }
5747 :
5748 : void
5749 0 : HTMLMediaElement::StartProgress()
5750 : {
5751 : // Record the time now for detecting stalled.
5752 0 : mDataTime = TimeStamp::NowLoRes();
5753 : // Reset mProgressTime so that mDataTime is not indicating bytes received
5754 : // after the last progress event.
5755 0 : mProgressTime = TimeStamp();
5756 0 : StartProgressTimer();
5757 0 : }
5758 :
5759 : void
5760 0 : HTMLMediaElement::StopProgress()
5761 : {
5762 0 : MOZ_ASSERT(NS_IsMainThread());
5763 0 : if (!mProgressTimer) {
5764 : return;
5765 : }
5766 :
5767 0 : mProgressTimer->Cancel();
5768 0 : mProgressTimer = nullptr;
5769 : }
5770 :
5771 : void
5772 0 : HTMLMediaElement::DownloadProgressed()
5773 : {
5774 0 : if (mNetworkState != NETWORK_LOADING) {
5775 : return;
5776 : }
5777 0 : CheckProgress(true);
5778 : }
5779 :
5780 : bool
5781 0 : HTMLMediaElement::ShouldCheckAllowOrigin()
5782 : {
5783 0 : return mCORSMode != CORS_NONE;
5784 : }
5785 :
5786 : bool
5787 0 : HTMLMediaElement::IsCORSSameOrigin()
5788 : {
5789 : bool subsumes;
5790 0 : RefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
5791 0 : return (NS_SUCCEEDED(NodePrincipal()->Subsumes(principal, &subsumes)) &&
5792 0 : subsumes) ||
5793 0 : ShouldCheckAllowOrigin();
5794 : }
5795 :
5796 : void
5797 0 : HTMLMediaElement::UpdateReadyStateInternal()
5798 : {
5799 0 : if (!mDecoder && !mSrcStream) {
5800 : // Not initialized - bail out.
5801 0 : LOG(LogLevel::Debug,
5802 : ("MediaElement %p UpdateReadyStateInternal() "
5803 : "Not initialized",
5804 : this));
5805 : return;
5806 : }
5807 :
5808 0 : if (mDecoder && mReadyState < HAVE_METADATA) {
5809 : // aNextFrame might have a next frame because the decoder can advance
5810 : // on its own thread before MetadataLoaded gets a chance to run.
5811 : // The arrival of more data can't change us out of this readyState.
5812 0 : LOG(LogLevel::Debug,
5813 : ("MediaElement %p UpdateReadyStateInternal() "
5814 : "Decoder ready state < HAVE_METADATA",
5815 : this));
5816 : return;
5817 : }
5818 :
5819 0 : if (mSrcStream && mReadyState < HAVE_METADATA) {
5820 0 : if (!mSrcStreamTracksAvailable) {
5821 0 : LOG(LogLevel::Debug,
5822 : ("MediaElement %p UpdateReadyStateInternal() "
5823 : "MediaStreamTracks not available yet",
5824 : this));
5825 0 : return;
5826 : }
5827 :
5828 0 : bool hasAudioTracks = !AudioTracks()->IsEmpty();
5829 0 : bool hasVideoTracks = !VideoTracks()->IsEmpty();
5830 0 : if (!hasAudioTracks && !hasVideoTracks) {
5831 0 : LOG(LogLevel::Debug,
5832 : ("MediaElement %p UpdateReadyStateInternal() "
5833 : "Stream with no tracks",
5834 : this));
5835 : return;
5836 : }
5837 :
5838 0 : if (IsVideo() && hasVideoTracks && !HasVideo()) {
5839 0 : LOG(LogLevel::Debug,
5840 : ("MediaElement %p UpdateReadyStateInternal() "
5841 : "Stream waiting for video",
5842 : this));
5843 : return;
5844 : }
5845 :
5846 0 : LOG(LogLevel::Debug,
5847 : ("MediaElement %p UpdateReadyStateInternal() Stream has "
5848 : "metadata; audioTracks=%d, videoTracks=%d, "
5849 : "hasVideoFrame=%d",
5850 : this,
5851 : AudioTracks()->Length(),
5852 : VideoTracks()->Length(),
5853 : HasVideo()));
5854 :
5855 : // We are playing a stream that has video and a video frame is now set.
5856 : // This means we have all metadata needed to change ready state.
5857 0 : MediaInfo mediaInfo = mMediaInfo;
5858 0 : if (hasAudioTracks) {
5859 0 : mediaInfo.EnableAudio();
5860 : }
5861 0 : if (hasVideoTracks) {
5862 0 : mediaInfo.EnableVideo();
5863 : }
5864 0 : MetadataLoaded(&mediaInfo, nullptr);
5865 : }
5866 :
5867 0 : if (mMediaSource) {
5868 : // readyState has changed, assuming it's following the pending mediasource
5869 : // operations. Notify the Mediasource that the operations have completed.
5870 0 : mMediaSource->CompletePendingTransactions();
5871 : }
5872 :
5873 0 : enum NextFrameStatus nextFrameStatus = NextFrameStatus();
5874 0 : if (mWaitingForKey == NOT_WAITING_FOR_KEY) {
5875 0 : if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE && mDecoder &&
5876 0 : !mDecoder->IsEnded()) {
5877 0 : nextFrameStatus = mDecoder->NextFrameBufferedStatus();
5878 : }
5879 0 : } else if (mWaitingForKey == WAITING_FOR_KEY) {
5880 0 : if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE ||
5881 0 : nextFrameStatus == NEXT_FRAME_UNAVAILABLE_BUFFERING) {
5882 : // http://w3c.github.io/encrypted-media/#wait-for-key
5883 : // Continuing 7.3.4 Queue a "waitingforkey" Event
5884 : // 4. Queue a task to fire a simple event named waitingforkey
5885 : // at the media element.
5886 : // 5. Set the readyState of media element to HAVE_METADATA.
5887 : // NOTE: We'll change to HAVE_CURRENT_DATA or HAVE_METADATA
5888 : // depending on whether we've loaded the first frame or not
5889 : // below.
5890 : // 6. Suspend playback.
5891 : // Note: Playback will already be stalled, as the next frame is
5892 : // unavailable.
5893 0 : mWaitingForKey = WAITING_FOR_KEY_DISPATCHED;
5894 0 : DispatchAsyncEvent(NS_LITERAL_STRING("waitingforkey"));
5895 : }
5896 : } else {
5897 0 : MOZ_ASSERT(mWaitingForKey == WAITING_FOR_KEY_DISPATCHED);
5898 0 : if (nextFrameStatus == NEXT_FRAME_AVAILABLE) {
5899 : // We have new frames after dispatching "waitingforkey".
5900 : // This means we've got the key and can reset mWaitingForKey now.
5901 0 : mWaitingForKey = NOT_WAITING_FOR_KEY;
5902 : }
5903 : }
5904 :
5905 0 : if (nextFrameStatus == MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING) {
5906 0 : LOG(LogLevel::Debug,
5907 : ("MediaElement %p UpdateReadyStateInternal() "
5908 : "NEXT_FRAME_UNAVAILABLE_SEEKING; Forcing HAVE_METADATA",
5909 : this));
5910 0 : ChangeReadyState(HAVE_METADATA);
5911 0 : return;
5912 : }
5913 :
5914 0 : if (IsVideo() && HasVideo() && !IsPlaybackEnded() && GetImageContainer() &&
5915 0 : !GetImageContainer()->HasCurrentImage()) {
5916 : // Don't advance if we are playing video, but don't have a video frame.
5917 : // Also, if video became available after advancing to HAVE_CURRENT_DATA
5918 : // while we are still playing, we need to revert to HAVE_METADATA until
5919 : // a video frame is available.
5920 0 : LOG(LogLevel::Debug,
5921 : ("MediaElement %p UpdateReadyStateInternal() "
5922 : "Playing video but no video frame; Forcing HAVE_METADATA",
5923 : this));
5924 0 : ChangeReadyState(HAVE_METADATA);
5925 0 : return;
5926 : }
5927 :
5928 0 : if (!mFirstFrameLoaded) {
5929 : // We haven't yet loaded the first frame, making us unable to determine
5930 : // if we have enough valid data at the present stage.
5931 : return;
5932 : }
5933 :
5934 0 : if (nextFrameStatus == NEXT_FRAME_UNAVAILABLE_BUFFERING) {
5935 : // Force HAVE_CURRENT_DATA when buffering.
5936 0 : ChangeReadyState(HAVE_CURRENT_DATA);
5937 0 : return;
5938 : }
5939 :
5940 : // TextTracks must be loaded for the HAVE_ENOUGH_DATA and
5941 : // HAVE_FUTURE_DATA.
5942 : // So force HAVE_CURRENT_DATA if text tracks not loaded.
5943 0 : if (mTextTrackManager && !mTextTrackManager->IsLoaded()) {
5944 0 : ChangeReadyState(HAVE_CURRENT_DATA);
5945 0 : return;
5946 : }
5947 :
5948 0 : if (mDownloadSuspendedByCache && mDecoder && !mDecoder->IsEnded()) {
5949 : // The decoder has signaled that the download has been suspended by the
5950 : // media cache. So move readyState into HAVE_ENOUGH_DATA, in case there's
5951 : // script waiting for a "canplaythrough" event; without this forced
5952 : // transition, we will never fire the "canplaythrough" event if the
5953 : // media cache is too small, and scripts are bound to fail. Don't force
5954 : // this transition if the decoder is in ended state; the readyState
5955 : // should remain at HAVE_CURRENT_DATA in this case.
5956 : // Note that this state transition includes the case where we finished
5957 : // downloaded the whole data stream.
5958 0 : LOG(LogLevel::Debug,
5959 : ("MediaElement %p UpdateReadyStateInternal() "
5960 : "Decoder download suspended by cache",
5961 : this));
5962 0 : ChangeReadyState(HAVE_ENOUGH_DATA);
5963 0 : return;
5964 : }
5965 :
5966 0 : if (nextFrameStatus != MediaDecoderOwner::NEXT_FRAME_AVAILABLE) {
5967 0 : LOG(LogLevel::Debug,
5968 : ("MediaElement %p UpdateReadyStateInternal() "
5969 : "Next frame not available",
5970 : this));
5971 0 : ChangeReadyState(HAVE_CURRENT_DATA);
5972 0 : return;
5973 : }
5974 :
5975 0 : if (mSrcStream) {
5976 0 : LOG(LogLevel::Debug,
5977 : ("MediaElement %p UpdateReadyStateInternal() "
5978 : "Stream HAVE_ENOUGH_DATA",
5979 : this));
5980 0 : ChangeReadyState(HAVE_ENOUGH_DATA);
5981 0 : return;
5982 : }
5983 :
5984 : // Now see if we should set HAVE_ENOUGH_DATA.
5985 : // If it's something we don't know the size of, then we can't
5986 : // make a real estimate, so we go straight to HAVE_ENOUGH_DATA once
5987 : // we've downloaded enough data that our download rate is considered
5988 : // reliable. We have to move to HAVE_ENOUGH_DATA at some point or
5989 : // autoplay elements for live streams will never play. Otherwise we
5990 : // move to HAVE_ENOUGH_DATA if we can play through the entire media
5991 : // without stopping to buffer.
5992 0 : if (mDecoder->CanPlayThrough()) {
5993 0 : LOG(LogLevel::Debug,
5994 : ("MediaElement %p UpdateReadyStateInternal() "
5995 : "Decoder can play through",
5996 : this));
5997 0 : ChangeReadyState(HAVE_ENOUGH_DATA);
5998 0 : return;
5999 : }
6000 0 : LOG(LogLevel::Debug,
6001 : ("MediaElement %p UpdateReadyStateInternal() "
6002 : "Default; Decoder has future data",
6003 : this));
6004 0 : ChangeReadyState(HAVE_FUTURE_DATA);
6005 : }
6006 :
6007 : static const char* const gReadyStateToString[] = { "HAVE_NOTHING",
6008 : "HAVE_METADATA",
6009 : "HAVE_CURRENT_DATA",
6010 : "HAVE_FUTURE_DATA",
6011 : "HAVE_ENOUGH_DATA" };
6012 :
6013 : void
6014 0 : HTMLMediaElement::ChangeReadyState(nsMediaReadyState aState)
6015 : {
6016 0 : if (mReadyState == aState) {
6017 : return;
6018 : }
6019 :
6020 0 : nsMediaReadyState oldState = mReadyState;
6021 0 : mReadyState = aState;
6022 0 : LOG(LogLevel::Debug,
6023 : ("%p Ready state changed to %s", this, gReadyStateToString[aState]));
6024 :
6025 0 : DDLOG(DDLogCategory::Property, "ready_state", gReadyStateToString[aState]);
6026 :
6027 0 : if (mNetworkState == NETWORK_EMPTY) {
6028 : return;
6029 : }
6030 :
6031 0 : UpdateAudioChannelPlayingState();
6032 :
6033 : // Handle raising of "waiting" event during seek (see 4.8.10.9)
6034 : // or
6035 : // 4.8.12.7 Ready states:
6036 : // "If the previous ready state was HAVE_FUTURE_DATA or more, and the new
6037 : // ready state is HAVE_CURRENT_DATA or less
6038 : // If the media element was potentially playing before its readyState
6039 : // attribute changed to a value lower than HAVE_FUTURE_DATA, and the element
6040 : // has not ended playback, and playback has not stopped due to errors,
6041 : // paused for user interaction, or paused for in-band content, the user agent
6042 : // must queue a task to fire a simple event named timeupdate at the element,
6043 : // and queue a task to fire a simple event named waiting at the element."
6044 0 : if (mPlayingBeforeSeek && mReadyState < HAVE_FUTURE_DATA) {
6045 0 : DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
6046 0 : } else if (oldState >= HAVE_FUTURE_DATA && mReadyState < HAVE_FUTURE_DATA &&
6047 0 : !Paused() && !Ended() && !mErrorSink->mError) {
6048 0 : FireTimeUpdate(false);
6049 0 : DispatchAsyncEvent(NS_LITERAL_STRING("waiting"));
6050 : }
6051 :
6052 0 : if (oldState < HAVE_CURRENT_DATA && mReadyState >= HAVE_CURRENT_DATA &&
6053 0 : !mLoadedDataFired) {
6054 0 : DispatchAsyncEvent(NS_LITERAL_STRING("loadeddata"));
6055 0 : mLoadedDataFired = true;
6056 : }
6057 :
6058 0 : if (oldState < HAVE_FUTURE_DATA && mReadyState >= HAVE_FUTURE_DATA) {
6059 0 : DispatchAsyncEvent(NS_LITERAL_STRING("canplay"));
6060 0 : if (!mPaused) {
6061 0 : if (mDecoder) {
6062 0 : mDecoder->Play();
6063 : }
6064 0 : NotifyAboutPlaying();
6065 : }
6066 : }
6067 :
6068 0 : CheckAutoplayDataReady();
6069 :
6070 0 : if (oldState < HAVE_ENOUGH_DATA && mReadyState >= HAVE_ENOUGH_DATA) {
6071 0 : DispatchAsyncEvent(NS_LITERAL_STRING("canplaythrough"));
6072 : }
6073 : }
6074 :
6075 : static const char* const gNetworkStateToString[] = { "EMPTY",
6076 : "IDLE",
6077 : "LOADING",
6078 : "NO_SOURCE" };
6079 :
6080 : void
6081 0 : HTMLMediaElement::ChangeNetworkState(nsMediaNetworkState aState)
6082 : {
6083 0 : if (mNetworkState == aState) {
6084 : return;
6085 : }
6086 :
6087 0 : nsMediaNetworkState oldState = mNetworkState;
6088 0 : mNetworkState = aState;
6089 0 : LOG(LogLevel::Debug,
6090 : ("%p Network state changed to %s", this, gNetworkStateToString[aState]));
6091 0 : DDLOG(
6092 : DDLogCategory::Property, "network_state", gNetworkStateToString[aState]);
6093 :
6094 0 : if (oldState == NETWORK_LOADING) {
6095 : // Stop progress notification when exiting NETWORK_LOADING.
6096 0 : StopProgress();
6097 : }
6098 :
6099 0 : if (mNetworkState == NETWORK_LOADING) {
6100 : // Start progress notification when entering NETWORK_LOADING.
6101 0 : StartProgress();
6102 0 : } else if (mNetworkState == NETWORK_IDLE && !mErrorSink->mError) {
6103 : // Fire 'suspend' event when entering NETWORK_IDLE and no error presented.
6104 0 : DispatchAsyncEvent(NS_LITERAL_STRING("suspend"));
6105 : }
6106 :
6107 : // Changing mNetworkState affects AddRemoveSelfReference().
6108 0 : AddRemoveSelfReference();
6109 : }
6110 :
6111 : bool
6112 0 : HTMLMediaElement::CanActivateAutoplay()
6113 : {
6114 : // For stream inputs, we activate autoplay on HAVE_NOTHING because
6115 : // this element itself might be blocking the stream from making progress by
6116 : // being paused. We only check that it has data by checking its active state.
6117 : // We also activate autoplay when playing a media source since the data
6118 : // download is controlled by the script and there is no way to evaluate
6119 : // MediaDecoder::CanPlayThrough().
6120 :
6121 2 : if (!AutoplayPolicy::IsMediaElementAllowedToPlay(WrapNotNull(this))) {
6122 : return false;
6123 : }
6124 :
6125 0 : if (!HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) {
6126 : return false;
6127 : }
6128 :
6129 0 : if (!mAutoplaying) {
6130 : return false;
6131 : }
6132 :
6133 0 : if (IsEditable()) {
6134 : return false;
6135 : }
6136 :
6137 0 : if (!mPaused) {
6138 : return false;
6139 : }
6140 :
6141 0 : if (mPausedForInactiveDocumentOrChannel) {
6142 : return false;
6143 : }
6144 :
6145 : // Static document is used for print preview and printing, should not be
6146 : // autoplay
6147 0 : if (OwnerDoc()->IsStaticDocument()) {
6148 : return false;
6149 : }
6150 :
6151 0 : if (mAudioChannelWrapper) {
6152 : // Note: SUSPENDED_PAUSE and SUSPENDED_BLOCK will be merged into one single
6153 : // state.
6154 0 : if (mAudioChannelWrapper->GetSuspendType() ==
6155 0 : nsISuspendedTypes::SUSPENDED_PAUSE ||
6156 0 : mAudioChannelWrapper->GetSuspendType() ==
6157 0 : nsISuspendedTypes::SUSPENDED_BLOCK ||
6158 0 : mAudioChannelWrapper->IsPlaybackBlocked()) {
6159 : return false;
6160 : }
6161 : }
6162 :
6163 0 : bool hasData = (mDecoder && mReadyState >= HAVE_ENOUGH_DATA) ||
6164 0 : (mSrcStream && mSrcStream->Active());
6165 :
6166 : return hasData;
6167 : }
6168 :
6169 : void
6170 0 : HTMLMediaElement::CheckAutoplayDataReady()
6171 : {
6172 0 : if (!CanActivateAutoplay()) {
6173 : return;
6174 : }
6175 :
6176 0 : mPaused = false;
6177 : // We changed mPaused which can affect AddRemoveSelfReference
6178 0 : AddRemoveSelfReference();
6179 0 : UpdateSrcMediaStreamPlaying();
6180 0 : UpdateAudioChannelPlayingState();
6181 :
6182 0 : if (mDecoder) {
6183 0 : SetPlayedOrSeeked(true);
6184 0 : if (mCurrentPlayRangeStart == -1.0) {
6185 0 : mCurrentPlayRangeStart = CurrentTime();
6186 : }
6187 0 : mDecoder->Play();
6188 0 : } else if (mSrcStream) {
6189 0 : SetPlayedOrSeeked(true);
6190 : }
6191 :
6192 : // For blocked media, the event would be pending until it is resumed.
6193 0 : DispatchAsyncEvent(NS_LITERAL_STRING("play"));
6194 :
6195 0 : DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
6196 : }
6197 :
6198 : bool
6199 0 : HTMLMediaElement::IsActive() const
6200 : {
6201 0 : nsIDocument* ownerDoc = OwnerDoc();
6202 8 : return ownerDoc && ownerDoc->IsActive() && ownerDoc->IsVisible();
6203 : }
6204 :
6205 : bool
6206 0 : HTMLMediaElement::IsHidden() const
6207 : {
6208 : nsIDocument* ownerDoc;
6209 0 : return mUnboundFromTree || !(ownerDoc = OwnerDoc()) || ownerDoc->Hidden();
6210 : }
6211 :
6212 : VideoFrameContainer*
6213 0 : HTMLMediaElement::GetVideoFrameContainer()
6214 : {
6215 0 : if (mShuttingDown) {
6216 : return nullptr;
6217 : }
6218 :
6219 0 : if (mVideoFrameContainer)
6220 : return mVideoFrameContainer;
6221 :
6222 : // Only video frames need an image container.
6223 0 : if (!IsVideo()) {
6224 : return nullptr;
6225 : }
6226 :
6227 : mVideoFrameContainer = new VideoFrameContainer(
6228 0 : this, LayerManager::CreateImageContainer(ImageContainer::ASYNCHRONOUS));
6229 :
6230 0 : return mVideoFrameContainer;
6231 : }
6232 :
6233 : void
6234 0 : HTMLMediaElement::PrincipalChanged(DOMMediaStream* aStream)
6235 : {
6236 0 : LOG(LogLevel::Info, ("HTMLMediaElement %p Stream principal changed.", this));
6237 0 : nsContentUtils::CombineResourcePrincipals(&mSrcStreamVideoPrincipal,
6238 0 : aStream->GetVideoPrincipal());
6239 :
6240 0 : LOG(LogLevel::Debug,
6241 : ("HTMLMediaElement %p Stream video principal changed to "
6242 : "%p. Waiting for it to reach VideoFrameContainer before "
6243 : "setting.",
6244 : this,
6245 : aStream->GetVideoPrincipal()));
6246 0 : if (mVideoFrameContainer) {
6247 : UpdateSrcStreamVideoPrincipal(
6248 0 : mVideoFrameContainer->GetLastPrincipalHandle());
6249 : }
6250 0 : }
6251 :
6252 : void
6253 0 : HTMLMediaElement::UpdateSrcStreamVideoPrincipal(
6254 : const PrincipalHandle& aPrincipalHandle)
6255 : {
6256 0 : nsTArray<RefPtr<VideoStreamTrack>> videoTracks;
6257 0 : mSrcStream->GetVideoTracks(videoTracks);
6258 :
6259 0 : PrincipalHandle handle(aPrincipalHandle);
6260 0 : bool matchesTrackPrincipal = false;
6261 0 : for (const RefPtr<VideoStreamTrack>& track : videoTracks) {
6262 0 : if (PrincipalHandleMatches(handle, track->GetPrincipal()) &&
6263 0 : !track->Ended()) {
6264 : // When the PrincipalHandle for the VideoFrameContainer changes to that of
6265 : // a track in mSrcStream we know that a removed track was displayed but
6266 : // is no longer so.
6267 0 : matchesTrackPrincipal = true;
6268 0 : LOG(LogLevel::Debug,
6269 : ("HTMLMediaElement %p VideoFrameContainer's "
6270 : "PrincipalHandle matches track %p. That's all we "
6271 : "need.",
6272 : this,
6273 : track.get()));
6274 : break;
6275 : }
6276 : }
6277 :
6278 0 : if (matchesTrackPrincipal) {
6279 0 : mSrcStreamVideoPrincipal = mSrcStream->GetVideoPrincipal();
6280 : }
6281 0 : }
6282 :
6283 : void
6284 0 : HTMLMediaElement::PrincipalHandleChangedForVideoFrameContainer(
6285 : VideoFrameContainer* aContainer,
6286 : const PrincipalHandle& aNewPrincipalHandle)
6287 : {
6288 0 : MOZ_ASSERT(NS_IsMainThread());
6289 :
6290 0 : if (!mSrcStream) {
6291 : return;
6292 : }
6293 :
6294 0 : LOG(LogLevel::Debug,
6295 : ("HTMLMediaElement %p PrincipalHandle changed in "
6296 : "VideoFrameContainer.",
6297 : this));
6298 :
6299 0 : UpdateSrcStreamVideoPrincipal(aNewPrincipalHandle);
6300 : }
6301 :
6302 : nsresult
6303 0 : HTMLMediaElement::DispatchEvent(const nsAString& aName)
6304 : {
6305 0 : LOG_EVENT(
6306 : LogLevel::Debug,
6307 : ("%p Dispatching event %s", this, NS_ConvertUTF16toUTF8(aName).get()));
6308 :
6309 : // Save events that occur while in the bfcache. These will be dispatched
6310 : // if the page comes out of the bfcache.
6311 0 : if (mEventDeliveryPaused) {
6312 0 : mPendingEvents.AppendElement(aName);
6313 0 : return NS_OK;
6314 : }
6315 :
6316 0 : return nsContentUtils::DispatchTrustedEvent(
6317 0 : OwnerDoc(), static_cast<nsIContent*>(this), aName, false, false);
6318 : }
6319 :
6320 : void
6321 0 : HTMLMediaElement::DispatchAsyncEvent(const nsAString& aName)
6322 : {
6323 0 : LOG_EVENT(LogLevel::Debug,
6324 : ("%p Queuing event %s", this, NS_ConvertUTF16toUTF8(aName).get()));
6325 0 : DDLOG(DDLogCategory::Event,
6326 : "HTMLMediaElement",
6327 : nsCString(NS_ConvertUTF16toUTF8(aName)));
6328 :
6329 : // Save events that occur while in the bfcache. These will be dispatched
6330 : // if the page comes out of the bfcache.
6331 0 : if (mEventDeliveryPaused) {
6332 0 : mPendingEvents.AppendElement(aName);
6333 0 : return;
6334 : }
6335 :
6336 0 : nsCOMPtr<nsIRunnable> event;
6337 :
6338 0 : if (aName.EqualsLiteral("playing")) {
6339 0 : event = new nsNotifyAboutPlayingRunner(this, TakePendingPlayPromises());
6340 : } else {
6341 0 : event = new nsAsyncEventRunner(aName, this);
6342 : }
6343 :
6344 0 : mMainThreadEventTarget->Dispatch(event.forget());
6345 :
6346 0 : if ((aName.EqualsLiteral("play") || aName.EqualsLiteral("playing"))) {
6347 0 : mPlayTime.Start();
6348 0 : if (IsHidden()) {
6349 0 : HiddenVideoStart();
6350 : }
6351 0 : } else if (aName.EqualsLiteral("waiting")) {
6352 0 : mPlayTime.Pause();
6353 0 : HiddenVideoStop();
6354 0 : } else if (aName.EqualsLiteral("pause")) {
6355 0 : mPlayTime.Pause();
6356 0 : HiddenVideoStop();
6357 : }
6358 : }
6359 :
6360 : nsresult
6361 0 : HTMLMediaElement::DispatchPendingMediaEvents()
6362 : {
6363 0 : NS_ASSERTION(!mEventDeliveryPaused,
6364 : "Must not be in bfcache when dispatching pending media events");
6365 :
6366 0 : uint32_t count = mPendingEvents.Length();
6367 0 : for (uint32_t i = 0; i < count; ++i) {
6368 0 : DispatchAsyncEvent(mPendingEvents[i]);
6369 : }
6370 0 : mPendingEvents.Clear();
6371 :
6372 0 : return NS_OK;
6373 : }
6374 :
6375 : bool
6376 0 : HTMLMediaElement::IsPotentiallyPlaying() const
6377 : {
6378 : // TODO:
6379 : // playback has not stopped due to errors,
6380 : // and the element has not paused for user interaction
6381 0 : return !mPaused &&
6382 0 : (mReadyState == HAVE_ENOUGH_DATA || mReadyState == HAVE_FUTURE_DATA) &&
6383 0 : !IsPlaybackEnded();
6384 : }
6385 :
6386 : bool
6387 0 : HTMLMediaElement::IsPlaybackEnded() const
6388 : {
6389 : // TODO:
6390 : // the current playback position is equal to the effective end of the media
6391 : // resource. See bug 449157.
6392 0 : return mReadyState >= HAVE_METADATA && mDecoder && mDecoder->IsEnded();
6393 : }
6394 :
6395 : already_AddRefed<nsIPrincipal>
6396 0 : HTMLMediaElement::GetCurrentPrincipal()
6397 : {
6398 0 : if (mDecoder) {
6399 0 : return mDecoder->GetCurrentPrincipal();
6400 : }
6401 0 : if (mSrcStream) {
6402 0 : nsCOMPtr<nsIPrincipal> principal = mSrcStream->GetPrincipal();
6403 0 : return principal.forget();
6404 : }
6405 : return nullptr;
6406 : }
6407 :
6408 : already_AddRefed<nsIPrincipal>
6409 0 : HTMLMediaElement::GetCurrentVideoPrincipal()
6410 : {
6411 0 : if (mDecoder) {
6412 0 : return mDecoder->GetCurrentPrincipal();
6413 : }
6414 0 : if (mSrcStream) {
6415 0 : nsCOMPtr<nsIPrincipal> principal = mSrcStreamVideoPrincipal;
6416 0 : return principal.forget();
6417 : }
6418 : return nullptr;
6419 : }
6420 :
6421 : void
6422 0 : HTMLMediaElement::NotifyDecoderPrincipalChanged()
6423 : {
6424 0 : RefPtr<nsIPrincipal> principal = GetCurrentPrincipal();
6425 :
6426 0 : mDecoder->UpdateSameOriginStatus(!principal || IsCORSSameOrigin());
6427 :
6428 0 : for (DecoderPrincipalChangeObserver* observer :
6429 0 : mDecoderPrincipalChangeObservers) {
6430 0 : observer->NotifyDecoderPrincipalChanged();
6431 : }
6432 0 : }
6433 :
6434 : void
6435 0 : HTMLMediaElement::AddDecoderPrincipalChangeObserver(
6436 : DecoderPrincipalChangeObserver* aObserver)
6437 : {
6438 0 : mDecoderPrincipalChangeObservers.AppendElement(aObserver);
6439 0 : }
6440 :
6441 : bool
6442 0 : HTMLMediaElement::RemoveDecoderPrincipalChangeObserver(
6443 : DecoderPrincipalChangeObserver* aObserver)
6444 : {
6445 0 : return mDecoderPrincipalChangeObservers.RemoveElement(aObserver);
6446 : }
6447 :
6448 : void
6449 0 : HTMLMediaElement::Invalidate(bool aImageSizeChanged,
6450 : Maybe<nsIntSize>& aNewIntrinsicSize,
6451 : bool aForceInvalidate)
6452 : {
6453 0 : nsIFrame* frame = GetPrimaryFrame();
6454 0 : if (aNewIntrinsicSize) {
6455 0 : UpdateMediaSize(aNewIntrinsicSize.value());
6456 0 : if (frame) {
6457 0 : nsPresContext* presContext = frame->PresContext();
6458 0 : nsIPresShell* presShell = presContext->PresShell();
6459 : presShell->FrameNeedsReflow(
6460 0 : frame, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY);
6461 : }
6462 : }
6463 :
6464 0 : RefPtr<ImageContainer> imageContainer = GetImageContainer();
6465 : bool asyncInvalidate =
6466 0 : imageContainer && imageContainer->IsAsync() && !aForceInvalidate;
6467 0 : if (frame) {
6468 0 : if (aImageSizeChanged) {
6469 0 : frame->InvalidateFrame();
6470 : } else {
6471 0 : frame->InvalidateLayer(DisplayItemType::TYPE_VIDEO,
6472 : nullptr,
6473 : nullptr,
6474 0 : asyncInvalidate ? nsIFrame::UPDATE_IS_ASYNC : 0);
6475 : }
6476 : }
6477 :
6478 0 : SVGObserverUtils::InvalidateDirectRenderingObservers(this);
6479 0 : }
6480 :
6481 : void
6482 0 : HTMLMediaElement::UpdateMediaSize(const nsIntSize& aSize)
6483 : {
6484 0 : if (IsVideo() && mReadyState != HAVE_NOTHING &&
6485 0 : mMediaInfo.mVideo.mDisplay != aSize) {
6486 0 : DispatchAsyncEvent(NS_LITERAL_STRING("resize"));
6487 : }
6488 :
6489 0 : mMediaInfo.mVideo.mDisplay = aSize;
6490 0 : UpdateReadyStateInternal();
6491 0 : }
6492 :
6493 : void
6494 0 : HTMLMediaElement::UpdateInitialMediaSize(const nsIntSize& aSize)
6495 : {
6496 0 : if (!mMediaInfo.HasVideo()) {
6497 0 : UpdateMediaSize(aSize);
6498 : }
6499 :
6500 0 : if (!mMediaStreamSizeListener) {
6501 : return;
6502 : }
6503 :
6504 0 : if (!mSelectedVideoStreamTrack) {
6505 0 : MOZ_ASSERT(false);
6506 : return;
6507 : }
6508 :
6509 0 : mSelectedVideoStreamTrack->RemoveDirectListener(mMediaStreamSizeListener);
6510 0 : mMediaStreamSizeListener->Forget();
6511 0 : mMediaStreamSizeListener = nullptr;
6512 : }
6513 :
6514 : void
6515 2 : HTMLMediaElement::SuspendOrResumeElement(bool aPauseElement,
6516 : bool aSuspendEvents)
6517 : {
6518 0 : LOG(LogLevel::Debug,
6519 : ("%p SuspendOrResumeElement(pause=%d, suspendEvents=%d) hidden=%d",
6520 : this,
6521 : aPauseElement,
6522 : aSuspendEvents,
6523 : OwnerDoc()->Hidden()));
6524 :
6525 2 : if (aPauseElement != mPausedForInactiveDocumentOrChannel) {
6526 0 : mPausedForInactiveDocumentOrChannel = aPauseElement;
6527 0 : UpdateSrcMediaStreamPlaying();
6528 0 : UpdateAudioChannelPlayingState();
6529 0 : if (aPauseElement) {
6530 0 : ReportTelemetry();
6531 :
6532 : // For EME content, we may force destruction of the CDM client (and CDM
6533 : // instance if this is the last client for that CDM instance) and
6534 : // the CDM's decoder. This ensures the CDM gets reliable and prompt
6535 : // shutdown notifications, as it may have book-keeping it needs
6536 : // to do on shutdown.
6537 0 : if (mMediaKeys) {
6538 0 : nsAutoString keySystem;
6539 0 : mMediaKeys->GetKeySystem(keySystem);
6540 : }
6541 0 : if (mDecoder) {
6542 0 : mDecoder->Pause();
6543 0 : mDecoder->Suspend();
6544 : }
6545 0 : mEventDeliveryPaused = aSuspendEvents;
6546 : } else {
6547 0 : if (mDecoder) {
6548 0 : mDecoder->Resume();
6549 0 : if (!mPaused && !mDecoder->IsEnded()) {
6550 0 : mDecoder->Play();
6551 : }
6552 : }
6553 0 : if (mEventDeliveryPaused) {
6554 0 : mEventDeliveryPaused = false;
6555 0 : DispatchPendingMediaEvents();
6556 : }
6557 : }
6558 : }
6559 0 : }
6560 :
6561 : bool
6562 0 : HTMLMediaElement::IsBeingDestroyed()
6563 : {
6564 0 : nsIDocument* ownerDoc = OwnerDoc();
6565 0 : nsIDocShell* docShell = ownerDoc ? ownerDoc->GetDocShell() : nullptr;
6566 0 : bool isBeingDestroyed = false;
6567 0 : if (docShell) {
6568 0 : docShell->IsBeingDestroyed(&isBeingDestroyed);
6569 : }
6570 0 : return isBeingDestroyed;
6571 : }
6572 :
6573 : void
6574 2 : HTMLMediaElement::NotifyOwnerDocumentActivityChanged()
6575 : {
6576 0 : bool visible = !IsHidden();
6577 0 : if (visible) {
6578 : // Visible -> Just pause hidden play time (no-op if already paused).
6579 2 : HiddenVideoStop();
6580 0 : } else if (mPlayTime.IsStarted()) {
6581 : // Not visible, play time is running -> Start hidden play time if needed.
6582 0 : HiddenVideoStart();
6583 : }
6584 :
6585 0 : if (mDecoder && !IsBeingDestroyed()) {
6586 0 : NotifyDecoderActivityChanges();
6587 : }
6588 :
6589 0 : bool pauseElement = ShouldElementBePaused();
6590 0 : SuspendOrResumeElement(pauseElement, !IsActive());
6591 :
6592 : // If the owning document has become inactive we should shutdown the CDM.
6593 0 : if (!OwnerDoc()->IsCurrentActiveDocument() && mMediaKeys) {
6594 0 : mMediaKeys->Shutdown();
6595 0 : DDUNLINKCHILD(mMediaKeys.get());
6596 0 : mMediaKeys = nullptr;
6597 0 : if (mDecoder) {
6598 0 : ShutdownDecoder();
6599 : }
6600 : }
6601 :
6602 0 : AddRemoveSelfReference();
6603 0 : }
6604 :
6605 : void
6606 2 : HTMLMediaElement::AddRemoveSelfReference()
6607 : {
6608 : // XXX we could release earlier here in many situations if we examined
6609 : // which event listeners are attached. Right now we assume there is a
6610 : // potential listener for every event. We would also have to keep the
6611 : // element alive if it was playing and producing audio output --- right now
6612 : // that's covered by the !mPaused check.
6613 0 : nsIDocument* ownerDoc = OwnerDoc();
6614 :
6615 : // See the comment at the top of this file for the explanation of this
6616 : // boolean expression.
6617 : bool needSelfReference =
6618 0 : !mShuttingDown && ownerDoc->IsActive() &&
6619 0 : (mDelayingLoadEvent || (!mPaused && mDecoder && !mDecoder->IsEnded()) ||
6620 0 : (!mPaused && mSrcStream && !mSrcStream->IsFinished()) ||
6621 0 : (mDecoder && mDecoder->IsSeeking()) || CanActivateAutoplay() ||
6622 6 : (mMediaSource ? mProgressTimer : mNetworkState == NETWORK_LOADING));
6623 :
6624 2 : if (needSelfReference != mHasSelfReference) {
6625 0 : mHasSelfReference = needSelfReference;
6626 0 : if (needSelfReference) {
6627 : // The shutdown observer will hold a strong reference to us. This
6628 : // will do to keep us alive. We need to know about shutdown so that
6629 : // we can release our self-reference.
6630 0 : mShutdownObserver->AddRefMediaElement();
6631 : } else {
6632 : // Dispatch Release asynchronously so that we don't destroy this object
6633 : // inside a call stack of method calls on this object
6634 0 : mMainThreadEventTarget->Dispatch(
6635 0 : NewRunnableMethod("dom::HTMLMediaElement::DoRemoveSelfReference",
6636 : this,
6637 0 : &HTMLMediaElement::DoRemoveSelfReference));
6638 : }
6639 : }
6640 2 : }
6641 :
6642 : void
6643 0 : HTMLMediaElement::DoRemoveSelfReference()
6644 : {
6645 0 : mShutdownObserver->ReleaseMediaElement();
6646 0 : }
6647 :
6648 : void
6649 0 : HTMLMediaElement::NotifyShutdownEvent()
6650 : {
6651 0 : mShuttingDown = true;
6652 0 : ResetState();
6653 0 : AddRemoveSelfReference();
6654 0 : }
6655 :
6656 : void
6657 0 : HTMLMediaElement::DispatchAsyncSourceError(nsIContent* aSourceElement)
6658 : {
6659 0 : LOG_EVENT(LogLevel::Debug, ("%p Queuing simple source error event", this));
6660 :
6661 : nsCOMPtr<nsIRunnable> event =
6662 0 : new nsSourceErrorEventRunner(this, aSourceElement);
6663 0 : mMainThreadEventTarget->Dispatch(event.forget());
6664 0 : }
6665 :
6666 : void
6667 0 : HTMLMediaElement::NotifyAddedSource()
6668 : {
6669 : // If a source element is inserted as a child of a media element
6670 : // that has no src attribute and whose networkState has the value
6671 : // NETWORK_EMPTY, the user agent must invoke the media element's
6672 : // resource selection algorithm.
6673 0 : if (!HasAttr(kNameSpaceID_None, nsGkAtoms::src) &&
6674 0 : mNetworkState == NETWORK_EMPTY) {
6675 0 : AssertReadyStateIsNothing();
6676 0 : QueueSelectResourceTask();
6677 : }
6678 :
6679 : // A load was paused in the resource selection algorithm, waiting for
6680 : // a new source child to be added, resume the resource selection algorithm.
6681 0 : if (mLoadWaitStatus == WAITING_FOR_SOURCE) {
6682 : // Rest the flag so we don't queue multiple LoadFromSourceTask() when
6683 : // multiple <source> are attached in an event loop.
6684 0 : mLoadWaitStatus = NOT_WAITING;
6685 0 : QueueLoadFromSourceTask();
6686 : }
6687 0 : }
6688 :
6689 : Element*
6690 0 : HTMLMediaElement::GetNextSource()
6691 : {
6692 0 : mSourceLoadCandidate = nullptr;
6693 :
6694 : while (true) {
6695 0 : if (mSourcePointer == nsINode::GetLastChild()) {
6696 : return nullptr; // no more children
6697 : }
6698 :
6699 0 : if (!mSourcePointer) {
6700 0 : mSourcePointer = nsINode::GetFirstChild();
6701 : } else {
6702 0 : mSourcePointer = mSourcePointer->GetNextSibling();
6703 : }
6704 0 : nsIContent* child = mSourcePointer;
6705 :
6706 : // If child is a <source> element, it is the next candidate.
6707 0 : if (child && child->IsHTMLElement(nsGkAtoms::source)) {
6708 0 : mSourceLoadCandidate = child;
6709 0 : return child->AsElement();
6710 : }
6711 : }
6712 : NS_NOTREACHED("Execution should not reach here!");
6713 : return nullptr;
6714 : }
6715 :
6716 : void
6717 0 : HTMLMediaElement::ChangeDelayLoadStatus(bool aDelay)
6718 : {
6719 0 : if (mDelayingLoadEvent == aDelay)
6720 : return;
6721 :
6722 0 : mDelayingLoadEvent = aDelay;
6723 :
6724 0 : LOG(LogLevel::Debug,
6725 : ("%p ChangeDelayLoadStatus(%d) doc=0x%p",
6726 : this,
6727 : aDelay,
6728 : mLoadBlockedDoc.get()));
6729 0 : if (mDecoder) {
6730 0 : mDecoder->SetLoadInBackground(!aDelay);
6731 : }
6732 0 : if (aDelay) {
6733 0 : mLoadBlockedDoc = OwnerDoc();
6734 0 : mLoadBlockedDoc->BlockOnload();
6735 : } else {
6736 : // mLoadBlockedDoc might be null due to GC unlinking
6737 0 : if (mLoadBlockedDoc) {
6738 0 : mLoadBlockedDoc->UnblockOnload(false);
6739 0 : mLoadBlockedDoc = nullptr;
6740 : }
6741 : }
6742 :
6743 : // We changed mDelayingLoadEvent which can affect AddRemoveSelfReference
6744 0 : AddRemoveSelfReference();
6745 : }
6746 :
6747 : already_AddRefed<nsILoadGroup>
6748 0 : HTMLMediaElement::GetDocumentLoadGroup()
6749 : {
6750 0 : if (!OwnerDoc()->IsActive()) {
6751 0 : NS_WARNING("Load group requested for media element in inactive document.");
6752 : }
6753 0 : return OwnerDoc()->GetDocumentLoadGroup();
6754 : }
6755 :
6756 : nsresult
6757 0 : HTMLMediaElement::CopyInnerTo(Element* aDest, bool aPreallocateChildren)
6758 : {
6759 0 : nsresult rv = nsGenericHTMLElement::CopyInnerTo(aDest, aPreallocateChildren);
6760 0 : NS_ENSURE_SUCCESS(rv, rv);
6761 0 : if (aDest->OwnerDoc()->IsStaticDocument()) {
6762 0 : HTMLMediaElement* dest = static_cast<HTMLMediaElement*>(aDest);
6763 0 : dest->SetMediaInfo(mMediaInfo);
6764 : }
6765 : return rv;
6766 : }
6767 :
6768 : already_AddRefed<TimeRanges>
6769 0 : HTMLMediaElement::Buffered() const
6770 : {
6771 : media::TimeIntervals buffered =
6772 0 : mDecoder ? mDecoder->GetBuffered() : media::TimeIntervals();
6773 0 : RefPtr<TimeRanges> ranges = new TimeRanges(ToSupports(OwnerDoc()), buffered);
6774 0 : return ranges.forget();
6775 : }
6776 :
6777 : void
6778 0 : HTMLMediaElement::SetRequestHeaders(nsIHttpChannel* aChannel)
6779 : {
6780 : // Send Accept header for video and audio types only (Bug 489071)
6781 0 : SetAcceptHeader(aChannel);
6782 :
6783 : // Apache doesn't send Content-Length when gzip transfer encoding is used,
6784 : // which prevents us from estimating the video length (if explicit
6785 : // Content-Duration and a length spec in the container are not present either)
6786 : // and from seeking. So, disable the standard "Accept-Encoding: gzip,deflate"
6787 : // that we usually send. See bug 614760.
6788 0 : DebugOnly<nsresult> rv = aChannel->SetRequestHeader(
6789 0 : NS_LITERAL_CSTRING("Accept-Encoding"), EmptyCString(), false);
6790 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
6791 :
6792 : // Set the Referer header
6793 0 : rv = aChannel->SetReferrerWithPolicy(OwnerDoc()->GetDocumentURI(),
6794 0 : OwnerDoc()->GetReferrerPolicy());
6795 0 : MOZ_ASSERT(NS_SUCCEEDED(rv));
6796 0 : }
6797 :
6798 : void
6799 0 : HTMLMediaElement::FireTimeUpdate(bool aPeriodic)
6800 : {
6801 0 : NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
6802 :
6803 0 : TimeStamp now = TimeStamp::Now();
6804 0 : double time = CurrentTime();
6805 :
6806 : // Fire a timeupdate event if this is not a periodic update (i.e. it's a
6807 : // timeupdate event mandated by the spec), or if it's a periodic update
6808 : // and TIMEUPDATE_MS has passed since the last timeupdate event fired and
6809 : // the time has changed.
6810 0 : if (!aPeriodic || (mLastCurrentTime != time &&
6811 0 : (mTimeUpdateTime.IsNull() ||
6812 0 : now - mTimeUpdateTime >=
6813 0 : TimeDuration::FromMilliseconds(TIMEUPDATE_MS)))) {
6814 0 : DispatchAsyncEvent(NS_LITERAL_STRING("timeupdate"));
6815 0 : mTimeUpdateTime = now;
6816 0 : mLastCurrentTime = time;
6817 : }
6818 0 : if (mFragmentEnd >= 0.0 && time >= mFragmentEnd) {
6819 0 : Pause();
6820 0 : mFragmentEnd = -1.0;
6821 0 : mFragmentStart = -1.0;
6822 0 : mDecoder->SetFragmentEndTime(mFragmentEnd);
6823 : }
6824 :
6825 : // Update the cues displaying on the video.
6826 : // Here mTextTrackManager can be null if the cycle collector has unlinked
6827 : // us before our parent. In that case UnbindFromTree will call us
6828 : // when our parent is unlinked.
6829 0 : if (mTextTrackManager) {
6830 0 : mTextTrackManager->TimeMarchesOn();
6831 : }
6832 0 : }
6833 :
6834 : MediaStream*
6835 0 : HTMLMediaElement::GetSrcMediaStream() const
6836 : {
6837 0 : if (!mSrcStream) {
6838 : return nullptr;
6839 : }
6840 0 : return mSrcStream->GetPlaybackStream();
6841 : }
6842 :
6843 : MediaError*
6844 0 : HTMLMediaElement::GetError() const
6845 : {
6846 0 : return mErrorSink->mError;
6847 : }
6848 :
6849 : void
6850 0 : HTMLMediaElement::OpenUnsupportedMediaWithExternalAppIfNeeded() const
6851 : {
6852 0 : mErrorSink->MaybeOpenUnsupportedMediaForOwner();
6853 0 : }
6854 :
6855 : void
6856 0 : HTMLMediaElement::GetCurrentSpec(nsCString& aString)
6857 : {
6858 0 : if (mLoadingSrc) {
6859 0 : mLoadingSrc->GetSpec(aString);
6860 : } else {
6861 0 : aString.Truncate();
6862 : }
6863 0 : }
6864 :
6865 : double
6866 0 : HTMLMediaElement::MozFragmentEnd()
6867 : {
6868 0 : double duration = Duration();
6869 :
6870 : // If there is no end fragment, or the fragment end is greater than the
6871 : // duration, return the duration.
6872 0 : return (mFragmentEnd < 0.0 || mFragmentEnd > duration) ? duration
6873 0 : : mFragmentEnd;
6874 : }
6875 :
6876 : static double
6877 0 : ClampPlaybackRate(double aPlaybackRate)
6878 : {
6879 0 : MOZ_ASSERT(aPlaybackRate >= 0.0);
6880 :
6881 0 : if (aPlaybackRate == 0.0) {
6882 : return aPlaybackRate;
6883 : }
6884 0 : if (aPlaybackRate < MIN_PLAYBACKRATE) {
6885 : return MIN_PLAYBACKRATE;
6886 : }
6887 0 : if (aPlaybackRate > MAX_PLAYBACKRATE) {
6888 : return MAX_PLAYBACKRATE;
6889 : }
6890 0 : return aPlaybackRate;
6891 : }
6892 :
6893 : void
6894 0 : HTMLMediaElement::SetDefaultPlaybackRate(double aDefaultPlaybackRate,
6895 : ErrorResult& aRv)
6896 : {
6897 0 : if (aDefaultPlaybackRate < 0) {
6898 0 : aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
6899 0 : return;
6900 : }
6901 :
6902 0 : mDefaultPlaybackRate = ClampPlaybackRate(aDefaultPlaybackRate);
6903 0 : DispatchAsyncEvent(NS_LITERAL_STRING("ratechange"));
6904 : }
6905 :
6906 : void
6907 0 : HTMLMediaElement::SetPlaybackRate(double aPlaybackRate, ErrorResult& aRv)
6908 : {
6909 : // Changing the playback rate of a media that has more than two channels is
6910 : // not supported.
6911 0 : if (aPlaybackRate < 0) {
6912 0 : aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
6913 0 : return;
6914 : }
6915 :
6916 0 : if (mPlaybackRate == aPlaybackRate) {
6917 : return;
6918 : }
6919 :
6920 0 : mPlaybackRate = aPlaybackRate;
6921 :
6922 0 : if (mPlaybackRate != 0.0 &&
6923 0 : (mPlaybackRate > THRESHOLD_HIGH_PLAYBACKRATE_AUDIO ||
6924 : mPlaybackRate < THRESHOLD_LOW_PLAYBACKRATE_AUDIO)) {
6925 0 : SetMutedInternal(mMuted | MUTED_BY_INVALID_PLAYBACK_RATE);
6926 : } else {
6927 0 : SetMutedInternal(mMuted & ~MUTED_BY_INVALID_PLAYBACK_RATE);
6928 : }
6929 :
6930 0 : if (mDecoder) {
6931 0 : mDecoder->SetPlaybackRate(ClampPlaybackRate(mPlaybackRate));
6932 : }
6933 0 : DispatchAsyncEvent(NS_LITERAL_STRING("ratechange"));
6934 : }
6935 :
6936 : void
6937 0 : HTMLMediaElement::SetMozPreservesPitch(bool aPreservesPitch)
6938 : {
6939 0 : mPreservesPitch = aPreservesPitch;
6940 0 : if (mDecoder) {
6941 0 : mDecoder->SetPreservesPitch(mPreservesPitch);
6942 : }
6943 0 : }
6944 :
6945 : ImageContainer*
6946 0 : HTMLMediaElement::GetImageContainer()
6947 : {
6948 0 : VideoFrameContainer* container = GetVideoFrameContainer();
6949 0 : return container ? container->GetImageContainer() : nullptr;
6950 : }
6951 :
6952 : void
6953 0 : HTMLMediaElement::UpdateAudioChannelPlayingState(bool aForcePlaying)
6954 : {
6955 0 : if (mAudioChannelWrapper) {
6956 0 : mAudioChannelWrapper->UpdateAudioChannelPlayingState(aForcePlaying);
6957 : }
6958 0 : }
6959 :
6960 : bool
6961 0 : HTMLMediaElement::IsAllowedToPlay()
6962 : {
6963 0 : if (!AutoplayPolicy::IsMediaElementAllowedToPlay(WrapNotNull(this))) {
6964 : #if defined(MOZ_WIDGET_ANDROID)
6965 : nsContentUtils::DispatchTrustedEvent(
6966 : OwnerDoc(),
6967 : static_cast<nsIContent*>(this),
6968 : NS_LITERAL_STRING("MozAutoplayMediaBlocked"),
6969 : false,
6970 : false);
6971 : #endif
6972 0 : LOG(LogLevel::Debug,
6973 : ("%p %s AutoplayPolicy blocked autoplay.", this, __func__));
6974 : return false;
6975 : }
6976 :
6977 0 : LOG(LogLevel::Debug,
6978 : ("%p %s AutoplayPolicy did not block autoplay.", this, __func__));
6979 :
6980 : // Check our custom playback policy.
6981 0 : if (mAudioChannelWrapper) {
6982 : // Note: SUSPENDED_PAUSE and SUSPENDED_BLOCK will be merged into one single
6983 : // state.
6984 0 : if (mAudioChannelWrapper->GetSuspendType() ==
6985 0 : nsISuspendedTypes::SUSPENDED_PAUSE ||
6986 0 : mAudioChannelWrapper->GetSuspendType() ==
6987 : nsISuspendedTypes::SUSPENDED_BLOCK) {
6988 0 : LOG(LogLevel::Debug,
6989 : ("%p IsAllowedToPlay() returning false due to AudioChannelAgent.",
6990 : this));
6991 : return false;
6992 : }
6993 :
6994 0 : LOG(LogLevel::Debug, ("%p IsAllowedToPlay() returning true.", this));
6995 : return true;
6996 : }
6997 :
6998 : // If the mAudioChannelWrapper doesn't exist that means the CC happened.
6999 0 : LOG(LogLevel::Debug,
7000 : ("%p IsAllowedToPlay() returning false due to null AudioChannelAgent.",
7001 : this));
7002 : return false;
7003 : }
7004 :
7005 : static const char*
7006 0 : VisibilityString(Visibility aVisibility)
7007 : {
7008 0 : switch (aVisibility) {
7009 : case Visibility::UNTRACKED: {
7010 : return "UNTRACKED";
7011 : }
7012 : case Visibility::APPROXIMATELY_NONVISIBLE: {
7013 0 : return "APPROXIMATELY_NONVISIBLE";
7014 : }
7015 : case Visibility::APPROXIMATELY_VISIBLE: {
7016 0 : return "APPROXIMATELY_VISIBLE";
7017 : }
7018 : }
7019 :
7020 0 : return "NAN";
7021 : }
7022 :
7023 : void
7024 0 : HTMLMediaElement::OnVisibilityChange(Visibility aNewVisibility)
7025 : {
7026 0 : LOG(LogLevel::Debug,
7027 : ("OnVisibilityChange(): %s\n", VisibilityString(aNewVisibility)));
7028 :
7029 0 : mVisibilityState = aNewVisibility;
7030 :
7031 0 : if (!mDecoder) {
7032 : return;
7033 : }
7034 :
7035 0 : switch (aNewVisibility) {
7036 : case Visibility::UNTRACKED: {
7037 0 : MOZ_ASSERT_UNREACHABLE("Shouldn't notify for untracked visibility");
7038 : return;
7039 : }
7040 : case Visibility::APPROXIMATELY_NONVISIBLE: {
7041 0 : if (mPlayTime.IsStarted()) {
7042 : // Not visible, play time is running -> Start hidden play time if
7043 : // needed.
7044 0 : HiddenVideoStart();
7045 : }
7046 : break;
7047 : }
7048 : case Visibility::APPROXIMATELY_VISIBLE: {
7049 : // Visible -> Just pause hidden play time (no-op if already paused).
7050 0 : HiddenVideoStop();
7051 0 : break;
7052 : }
7053 : }
7054 :
7055 0 : NotifyDecoderActivityChanges();
7056 : }
7057 :
7058 : MediaKeys*
7059 0 : HTMLMediaElement::GetMediaKeys() const
7060 : {
7061 0 : return mMediaKeys;
7062 : }
7063 :
7064 : bool
7065 0 : HTMLMediaElement::ContainsRestrictedContent()
7066 : {
7067 0 : return GetMediaKeys() != nullptr;
7068 : }
7069 :
7070 : void
7071 0 : HTMLMediaElement::SetCDMProxyFailure(const MediaResult& aResult)
7072 : {
7073 0 : LOG(LogLevel::Debug, ("%s", __func__));
7074 0 : MOZ_ASSERT(mSetMediaKeysDOMPromise);
7075 :
7076 0 : ResetSetMediaKeysTempVariables();
7077 :
7078 0 : mSetMediaKeysDOMPromise->MaybeReject(aResult.Code(), aResult.Message());
7079 0 : }
7080 :
7081 : void
7082 0 : HTMLMediaElement::RemoveMediaKeys()
7083 : {
7084 0 : LOG(LogLevel::Debug, ("%s", __func__));
7085 : // 5.2.3 Stop using the CDM instance represented by the mediaKeys attribute
7086 : // to decrypt media data and remove the association with the media element.
7087 0 : if (mMediaKeys) {
7088 0 : mMediaKeys->Unbind();
7089 : }
7090 0 : mMediaKeys = nullptr;
7091 0 : }
7092 :
7093 : bool
7094 0 : HTMLMediaElement::TryRemoveMediaKeysAssociation()
7095 : {
7096 0 : MOZ_ASSERT(mMediaKeys);
7097 0 : LOG(LogLevel::Debug, ("%s", __func__));
7098 : // 5.2.1 If the user agent or CDM do not support removing the association,
7099 : // let this object's attaching media keys value be false and reject promise
7100 : // with a new DOMException whose name is NotSupportedError.
7101 : // 5.2.2 If the association cannot currently be removed, let this object's
7102 : // attaching media keys value be false and reject promise with a new
7103 : // DOMException whose name is InvalidStateError.
7104 0 : if (mDecoder) {
7105 0 : RefPtr<HTMLMediaElement> self = this;
7106 0 : mDecoder->SetCDMProxy(nullptr)
7107 0 : ->Then(mAbstractMainThread,
7108 : __func__,
7109 0 : [self]() {
7110 0 : self->mSetCDMRequest.Complete();
7111 :
7112 0 : self->RemoveMediaKeys();
7113 0 : if (self->AttachNewMediaKeys()) {
7114 : // No incoming MediaKeys object or MediaDecoder is not created
7115 : // yet.
7116 0 : self->MakeAssociationWithCDMResolved();
7117 : }
7118 0 : },
7119 0 : [self](const MediaResult& aResult) {
7120 0 : self->mSetCDMRequest.Complete();
7121 : // 5.2.4 If the preceding step failed, let this object's
7122 : // attaching media keys value be false and reject promise with a
7123 : // new DOMException whose name is the appropriate error name.
7124 0 : self->SetCDMProxyFailure(aResult);
7125 0 : })
7126 0 : ->Track(mSetCDMRequest);
7127 : return false;
7128 : }
7129 :
7130 0 : RemoveMediaKeys();
7131 0 : return true;
7132 : }
7133 :
7134 : bool
7135 0 : HTMLMediaElement::DetachExistingMediaKeys()
7136 : {
7137 0 : LOG(LogLevel::Debug, ("%s", __func__));
7138 0 : MOZ_ASSERT(mSetMediaKeysDOMPromise);
7139 : // 5.1 If mediaKeys is not null, CDM instance represented by mediaKeys is
7140 : // already in use by another media element, and the user agent is unable
7141 : // to use it with this element, let this object's attaching media keys
7142 : // value be false and reject promise with a new DOMException whose name
7143 : // is QuotaExceededError.
7144 0 : if (mIncomingMediaKeys && mIncomingMediaKeys->IsBoundToMediaElement()) {
7145 0 : SetCDMProxyFailure(MediaResult(
7146 : NS_ERROR_DOM_QUOTA_EXCEEDED_ERR,
7147 0 : "MediaKeys object is already bound to another HTMLMediaElement"));
7148 0 : return false;
7149 : }
7150 :
7151 : // 5.2 If the mediaKeys attribute is not null, run the following steps:
7152 0 : if (mMediaKeys) {
7153 0 : return TryRemoveMediaKeysAssociation();
7154 : }
7155 : return true;
7156 : }
7157 :
7158 : void
7159 0 : HTMLMediaElement::MakeAssociationWithCDMResolved()
7160 : {
7161 0 : LOG(LogLevel::Debug, ("%s", __func__));
7162 0 : MOZ_ASSERT(mSetMediaKeysDOMPromise);
7163 :
7164 : // 5.4 Set the mediaKeys attribute to mediaKeys.
7165 0 : mMediaKeys = mIncomingMediaKeys;
7166 : // 5.5 Let this object's attaching media keys value be false.
7167 0 : ResetSetMediaKeysTempVariables();
7168 : // 5.6 Resolve promise.
7169 0 : mSetMediaKeysDOMPromise->MaybeResolveWithUndefined();
7170 0 : mSetMediaKeysDOMPromise = nullptr;
7171 0 : }
7172 :
7173 : bool
7174 0 : HTMLMediaElement::TryMakeAssociationWithCDM(CDMProxy* aProxy)
7175 : {
7176 0 : LOG(LogLevel::Debug, ("%s", __func__));
7177 0 : MOZ_ASSERT(aProxy);
7178 :
7179 : // 5.3.3 Queue a task to run the "Attempt to Resume Playback If Necessary"
7180 : // algorithm on the media element.
7181 : // Note: Setting the CDMProxy on the MediaDecoder will unblock playback.
7182 0 : if (mDecoder) {
7183 : // CDMProxy is set asynchronously in MediaFormatReader, once it's done,
7184 : // HTMLMediaElement should resolve or reject the DOM promise.
7185 0 : RefPtr<HTMLMediaElement> self = this;
7186 0 : mDecoder->SetCDMProxy(aProxy)
7187 0 : ->Then(mAbstractMainThread,
7188 : __func__,
7189 0 : [self]() {
7190 0 : self->mSetCDMRequest.Complete();
7191 0 : self->MakeAssociationWithCDMResolved();
7192 0 : },
7193 0 : [self](const MediaResult& aResult) {
7194 0 : self->mSetCDMRequest.Complete();
7195 0 : self->SetCDMProxyFailure(aResult);
7196 0 : })
7197 0 : ->Track(mSetCDMRequest);
7198 : return false;
7199 : }
7200 : return true;
7201 : }
7202 :
7203 : bool
7204 0 : HTMLMediaElement::AttachNewMediaKeys()
7205 : {
7206 0 : LOG(LogLevel::Debug,
7207 : ("%s incoming MediaKeys(%p)", __func__, mIncomingMediaKeys.get()));
7208 0 : MOZ_ASSERT(mSetMediaKeysDOMPromise);
7209 :
7210 : // 5.3. If mediaKeys is not null, run the following steps:
7211 0 : if (mIncomingMediaKeys) {
7212 0 : auto cdmProxy = mIncomingMediaKeys->GetCDMProxy();
7213 0 : if (!cdmProxy) {
7214 0 : SetCDMProxyFailure(MediaResult(
7215 : NS_ERROR_DOM_INVALID_STATE_ERR,
7216 0 : "CDM crashed before binding MediaKeys object to HTMLMediaElement"));
7217 0 : return false;
7218 : }
7219 :
7220 : // 5.3.1 Associate the CDM instance represented by mediaKeys with the
7221 : // media element for decrypting media data.
7222 0 : if (NS_FAILED(mIncomingMediaKeys->Bind(this))) {
7223 : // 5.3.2 If the preceding step failed, run the following steps:
7224 :
7225 : // 5.3.2.1 Set the mediaKeys attribute to null.
7226 0 : mMediaKeys = nullptr;
7227 : // 5.3.2.2 Let this object's attaching media keys value be false.
7228 : // 5.3.2.3 Reject promise with a new DOMException whose name is
7229 : // the appropriate error name.
7230 : SetCDMProxyFailure(
7231 0 : MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
7232 0 : "Failed to bind MediaKeys object to HTMLMediaElement"));
7233 0 : return false;
7234 : }
7235 0 : return TryMakeAssociationWithCDM(cdmProxy);
7236 : }
7237 : return true;
7238 : }
7239 :
7240 : void
7241 0 : HTMLMediaElement::ResetSetMediaKeysTempVariables()
7242 : {
7243 0 : mAttachingMediaKey = false;
7244 0 : mIncomingMediaKeys = nullptr;
7245 0 : }
7246 :
7247 : already_AddRefed<Promise>
7248 0 : HTMLMediaElement::SetMediaKeys(mozilla::dom::MediaKeys* aMediaKeys,
7249 : ErrorResult& aRv)
7250 : {
7251 0 : LOG(LogLevel::Debug,
7252 : ("%p SetMediaKeys(%p) mMediaKeys=%p mDecoder=%p",
7253 : this,
7254 : aMediaKeys,
7255 : mMediaKeys.get(),
7256 : mDecoder.get()));
7257 :
7258 0 : if (MozAudioCaptured()) {
7259 0 : aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
7260 : return nullptr;
7261 : }
7262 :
7263 0 : nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
7264 0 : if (!win) {
7265 0 : aRv.Throw(NS_ERROR_UNEXPECTED);
7266 : return nullptr;
7267 : }
7268 0 : RefPtr<DetailedPromise> promise = DetailedPromise::Create(
7269 0 : win->AsGlobal(), aRv, NS_LITERAL_CSTRING("HTMLMediaElement.setMediaKeys"));
7270 0 : if (aRv.Failed()) {
7271 : return nullptr;
7272 : }
7273 :
7274 : // 1. If mediaKeys and the mediaKeys attribute are the same object,
7275 : // return a resolved promise.
7276 0 : if (mMediaKeys == aMediaKeys) {
7277 0 : promise->MaybeResolveWithUndefined();
7278 0 : return promise.forget();
7279 : }
7280 :
7281 : // 2. If this object's attaching media keys value is true, return a
7282 : // promise rejected with a new DOMException whose name is InvalidStateError.
7283 0 : if (mAttachingMediaKey) {
7284 0 : promise->MaybeReject(
7285 : NS_ERROR_DOM_INVALID_STATE_ERR,
7286 0 : NS_LITERAL_CSTRING("A MediaKeys object is in attaching operation."));
7287 0 : return promise.forget();
7288 : }
7289 :
7290 : // 3. Let this object's attaching media keys value be true.
7291 0 : mAttachingMediaKey = true;
7292 0 : mIncomingMediaKeys = aMediaKeys;
7293 :
7294 : // 4. Let promise be a new promise.
7295 0 : mSetMediaKeysDOMPromise = promise;
7296 :
7297 : // 5. Run the following steps in parallel:
7298 :
7299 : // 5.1 & 5.2 & 5.3
7300 0 : if (!DetachExistingMediaKeys() || !AttachNewMediaKeys()) {
7301 0 : return promise.forget();
7302 : }
7303 :
7304 : // 5.4, 5.5, 5.6
7305 0 : MakeAssociationWithCDMResolved();
7306 :
7307 : // 6. Return promise.
7308 0 : return promise.forget();
7309 : }
7310 :
7311 : EventHandlerNonNull*
7312 0 : HTMLMediaElement::GetOnencrypted()
7313 : {
7314 0 : return EventTarget::GetEventHandler(nsGkAtoms::onencrypted, EmptyString());
7315 : }
7316 :
7317 : void
7318 0 : HTMLMediaElement::SetOnencrypted(EventHandlerNonNull* aCallback)
7319 : {
7320 0 : EventTarget::SetEventHandler(
7321 0 : nsGkAtoms::onencrypted, EmptyString(), aCallback);
7322 0 : }
7323 :
7324 : EventHandlerNonNull*
7325 0 : HTMLMediaElement::GetOnwaitingforkey()
7326 : {
7327 0 : return EventTarget::GetEventHandler(nsGkAtoms::onwaitingforkey,
7328 0 : EmptyString());
7329 : }
7330 :
7331 : void
7332 0 : HTMLMediaElement::SetOnwaitingforkey(EventHandlerNonNull* aCallback)
7333 : {
7334 0 : EventTarget::SetEventHandler(
7335 0 : nsGkAtoms::onwaitingforkey, EmptyString(), aCallback);
7336 0 : }
7337 :
7338 : void
7339 0 : HTMLMediaElement::DispatchEncrypted(const nsTArray<uint8_t>& aInitData,
7340 : const nsAString& aInitDataType)
7341 : {
7342 0 : LOG(LogLevel::Debug,
7343 : ("%p DispatchEncrypted initDataType='%s'",
7344 : this,
7345 : NS_ConvertUTF16toUTF8(aInitDataType).get()));
7346 :
7347 0 : if (mReadyState == HAVE_NOTHING) {
7348 : // Ready state not HAVE_METADATA (yet), don't dispatch encrypted now.
7349 : // Queueing for later dispatch in MetadataLoaded.
7350 0 : mPendingEncryptedInitData.AddInitData(aInitDataType, aInitData);
7351 0 : return;
7352 : }
7353 :
7354 0 : RefPtr<MediaEncryptedEvent> event;
7355 0 : if (IsCORSSameOrigin()) {
7356 0 : event = MediaEncryptedEvent::Constructor(this, aInitDataType, aInitData);
7357 : } else {
7358 0 : event = MediaEncryptedEvent::Constructor(this);
7359 : }
7360 :
7361 : RefPtr<AsyncEventDispatcher> asyncDispatcher =
7362 0 : new AsyncEventDispatcher(this, event);
7363 0 : asyncDispatcher->PostDOMEvent();
7364 : }
7365 :
7366 : bool
7367 0 : HTMLMediaElement::IsEventAttributeNameInternal(nsAtom* aName)
7368 : {
7369 0 : return aName == nsGkAtoms::onencrypted ||
7370 0 : nsGenericHTMLElement::IsEventAttributeNameInternal(aName);
7371 : }
7372 :
7373 : already_AddRefed<nsIPrincipal>
7374 0 : HTMLMediaElement::GetTopLevelPrincipal()
7375 : {
7376 0 : RefPtr<nsIPrincipal> principal;
7377 0 : nsCOMPtr<nsPIDOMWindowInner> window = OwnerDoc()->GetInnerWindow();
7378 0 : if (!window) {
7379 : return nullptr;
7380 : }
7381 : // XXXkhuey better hope we always have an outer ...
7382 0 : nsCOMPtr<nsPIDOMWindowOuter> top = window->GetOuterWindow()->GetTop();
7383 0 : if (!top) {
7384 : return nullptr;
7385 : }
7386 0 : nsIDocument* doc = top->GetExtantDoc();
7387 0 : if (!doc) {
7388 : return nullptr;
7389 : }
7390 0 : principal = doc->NodePrincipal();
7391 : return principal.forget();
7392 : }
7393 :
7394 : void
7395 0 : HTMLMediaElement::NotifyWaitingForKey()
7396 : {
7397 0 : LOG(LogLevel::Debug, ("%p, NotifyWaitingForKey()", this));
7398 :
7399 : // http://w3c.github.io/encrypted-media/#wait-for-key
7400 : // 7.3.4 Queue a "waitingforkey" Event
7401 : // 1. Let the media element be the specified HTMLMediaElement object.
7402 : // 2. If the media element's waiting for key value is true, abort these steps.
7403 0 : if (mWaitingForKey == NOT_WAITING_FOR_KEY) {
7404 : // 3. Set the media element's waiting for key value to true.
7405 : // Note: algorithm continues in UpdateReadyStateInternal() when all decoded
7406 : // data enqueued in the MDSM is consumed.
7407 0 : mWaitingForKey = WAITING_FOR_KEY;
7408 0 : UpdateReadyStateInternal();
7409 : }
7410 0 : }
7411 :
7412 : AudioTrackList*
7413 0 : HTMLMediaElement::AudioTracks()
7414 : {
7415 0 : if (!mAudioTrackList) {
7416 : nsCOMPtr<nsPIDOMWindowInner> window =
7417 0 : do_QueryInterface(OwnerDoc()->GetParentObject());
7418 0 : mAudioTrackList = new AudioTrackList(window, this);
7419 : }
7420 0 : return mAudioTrackList;
7421 : }
7422 :
7423 : VideoTrackList*
7424 0 : HTMLMediaElement::VideoTracks()
7425 : {
7426 0 : if (!mVideoTrackList) {
7427 : nsCOMPtr<nsPIDOMWindowInner> window =
7428 0 : do_QueryInterface(OwnerDoc()->GetParentObject());
7429 0 : mVideoTrackList = new VideoTrackList(window, this);
7430 : }
7431 0 : return mVideoTrackList;
7432 : }
7433 :
7434 : TextTrackList*
7435 0 : HTMLMediaElement::GetTextTracks()
7436 : {
7437 0 : return GetOrCreateTextTrackManager()->GetTextTracks();
7438 : }
7439 :
7440 : already_AddRefed<TextTrack>
7441 0 : HTMLMediaElement::AddTextTrack(TextTrackKind aKind,
7442 : const nsAString& aLabel,
7443 : const nsAString& aLanguage)
7444 : {
7445 : return GetOrCreateTextTrackManager()->AddTextTrack(
7446 : aKind,
7447 : aLabel,
7448 : aLanguage,
7449 : TextTrackMode::Hidden,
7450 : TextTrackReadyState::Loaded,
7451 0 : TextTrackSource::AddTextTrack);
7452 : }
7453 :
7454 : void
7455 0 : HTMLMediaElement::PopulatePendingTextTrackList()
7456 : {
7457 0 : if (mTextTrackManager) {
7458 0 : mTextTrackManager->PopulatePendingList();
7459 : }
7460 0 : }
7461 :
7462 : TextTrackManager*
7463 0 : HTMLMediaElement::GetOrCreateTextTrackManager()
7464 : {
7465 0 : if (!mTextTrackManager) {
7466 0 : mTextTrackManager = new TextTrackManager(this);
7467 0 : mTextTrackManager->AddListeners();
7468 : }
7469 0 : return mTextTrackManager;
7470 : }
7471 :
7472 : MediaDecoderOwner::NextFrameStatus
7473 0 : HTMLMediaElement::NextFrameStatus()
7474 : {
7475 0 : if (mDecoder) {
7476 0 : return mDecoder->NextFrameStatus();
7477 0 : } else if (mMediaStreamListener) {
7478 0 : return mMediaStreamListener->NextFrameStatus();
7479 : }
7480 : return NEXT_FRAME_UNINITIALIZED;
7481 : }
7482 :
7483 : void
7484 0 : HTMLMediaElement::SetDecoder(MediaDecoder* aDecoder)
7485 : {
7486 0 : MOZ_ASSERT(aDecoder); // Use ShutdownDecoder() to clear.
7487 0 : if (mDecoder) {
7488 0 : ShutdownDecoder();
7489 : }
7490 0 : mDecoder = aDecoder;
7491 0 : DDLINKCHILD("decoder", mDecoder.get());
7492 0 : if (mDecoder && mForcedHidden) {
7493 0 : mDecoder->SetForcedHidden(mForcedHidden);
7494 : }
7495 0 : }
7496 :
7497 : float
7498 0 : HTMLMediaElement::ComputedVolume() const
7499 : {
7500 0 : return mMuted
7501 0 : ? 0.0f
7502 0 : : mAudioChannelWrapper ? mAudioChannelWrapper->GetEffectiveVolume()
7503 0 : : mVolume;
7504 : }
7505 :
7506 : bool
7507 0 : HTMLMediaElement::ComputedMuted() const
7508 : {
7509 0 : return (mMuted & MUTED_BY_AUDIO_CHANNEL);
7510 : }
7511 :
7512 : nsSuspendedTypes
7513 0 : HTMLMediaElement::ComputedSuspended() const
7514 : {
7515 0 : return mAudioChannelWrapper ? mAudioChannelWrapper->GetSuspendType()
7516 0 : : nsISuspendedTypes::NONE_SUSPENDED;
7517 : }
7518 :
7519 : bool
7520 0 : HTMLMediaElement::IsCurrentlyPlaying() const
7521 : {
7522 : // We have playable data, but we still need to check whether data is "real"
7523 : // current data.
7524 0 : return mReadyState >= HAVE_CURRENT_DATA && !IsPlaybackEnded();
7525 : }
7526 :
7527 : void
7528 0 : HTMLMediaElement::SetAudibleState(bool aAudible)
7529 : {
7530 0 : if (mIsAudioTrackAudible != aAudible) {
7531 0 : mIsAudioTrackAudible = aAudible;
7532 : NotifyAudioPlaybackChanged(
7533 0 : AudioChannelService::AudibleChangedReasons::eDataAudibleChanged);
7534 : }
7535 0 : }
7536 :
7537 : void
7538 0 : HTMLMediaElement::NotifyAudioPlaybackChanged(AudibleChangedReasons aReason)
7539 : {
7540 0 : if (mAudioChannelWrapper) {
7541 0 : mAudioChannelWrapper->NotifyAudioPlaybackChanged(aReason);
7542 : }
7543 : // only request wake lock for audible media.
7544 0 : UpdateWakeLock();
7545 0 : }
7546 :
7547 : bool
7548 2 : HTMLMediaElement::ShouldElementBePaused()
7549 : {
7550 : // Bfcached page or inactive document.
7551 0 : if (!IsActive()) {
7552 : return true;
7553 : }
7554 :
7555 2 : return false;
7556 : }
7557 :
7558 : void
7559 0 : HTMLMediaElement::SetMediaInfo(const MediaInfo& aInfo)
7560 : {
7561 0 : const bool oldHasAudio = mMediaInfo.HasAudio();
7562 0 : mMediaInfo = aInfo;
7563 0 : if (aInfo.HasAudio() != oldHasAudio) {
7564 0 : UpdateAudioChannelPlayingState();
7565 : NotifyAudioPlaybackChanged(
7566 0 : AudioChannelService::AudibleChangedReasons::eDataAudibleChanged);
7567 : }
7568 0 : if (mAudioChannelWrapper) {
7569 0 : mAudioChannelWrapper->AudioCaptureStreamChangeIfNeeded();
7570 : }
7571 0 : }
7572 :
7573 : void
7574 0 : HTMLMediaElement::AudioCaptureStreamChange(bool aCapture)
7575 : {
7576 : // No need to capture a silence media element.
7577 0 : if (!HasAudio()) {
7578 : return;
7579 : }
7580 :
7581 0 : if (aCapture && !mCaptureStreamPort) {
7582 0 : nsCOMPtr<nsPIDOMWindowInner> window = OwnerDoc()->GetInnerWindow();
7583 0 : if (!OwnerDoc()->GetInnerWindow()) {
7584 0 : return;
7585 : }
7586 :
7587 0 : uint64_t id = window->WindowID();
7588 0 : MediaStreamGraph* msg = MediaStreamGraph::GetInstance(
7589 : MediaStreamGraph::AUDIO_THREAD_DRIVER,
7590 : window,
7591 0 : MediaStreamGraph::REQUEST_DEFAULT_SAMPLE_RATE);
7592 :
7593 0 : if (GetSrcMediaStream()) {
7594 0 : mCaptureStreamPort = msg->ConnectToCaptureStream(id, GetSrcMediaStream());
7595 : } else {
7596 : RefPtr<DOMMediaStream> stream =
7597 0 : CaptureStreamInternal(StreamCaptureBehavior::CONTINUE_WHEN_ENDED,
7598 : StreamCaptureType::CAPTURE_AUDIO,
7599 0 : msg);
7600 : mCaptureStreamPort =
7601 0 : msg->ConnectToCaptureStream(id, stream->GetPlaybackStream());
7602 : }
7603 0 : } else if (!aCapture && mCaptureStreamPort) {
7604 0 : if (mDecoder) {
7605 : ProcessedMediaStream* ps =
7606 0 : mCaptureStreamPort->GetSource()->AsProcessedStream();
7607 0 : MOZ_ASSERT(ps);
7608 :
7609 0 : for (uint32_t i = 0; i < mOutputStreams.Length(); i++) {
7610 0 : if (mOutputStreams[i].mStream->GetPlaybackStream() == ps) {
7611 0 : mOutputStreams.RemoveElementAt(i);
7612 0 : break;
7613 : }
7614 : }
7615 0 : mDecoder->RemoveOutputStream(ps);
7616 : }
7617 0 : mCaptureStreamPort->Destroy();
7618 0 : mCaptureStreamPort = nullptr;
7619 : }
7620 : }
7621 :
7622 : void
7623 0 : HTMLMediaElement::NotifyCueDisplayStatesChanged()
7624 : {
7625 0 : if (!mTextTrackManager) {
7626 : return;
7627 : }
7628 :
7629 0 : mTextTrackManager->DispatchUpdateCueDisplay();
7630 : }
7631 :
7632 : void
7633 0 : HTMLMediaElement::MarkAsContentSource(CallerAPI aAPI)
7634 : {
7635 0 : const bool isVisible = mVisibilityState == Visibility::APPROXIMATELY_VISIBLE;
7636 :
7637 0 : if (isVisible) {
7638 : // 0 = ALL_VISIBLE
7639 0 : Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 0);
7640 : } else {
7641 : // 1 = ALL_INVISIBLE
7642 0 : Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 1);
7643 :
7644 0 : if (IsInUncomposedDoc()) {
7645 : // 0 = ALL_IN_TREE
7646 : Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT,
7647 0 : 0);
7648 : } else {
7649 : // 1 = ALL_NOT_IN_TREE
7650 : Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT,
7651 0 : 1);
7652 : }
7653 : }
7654 :
7655 0 : switch (aAPI) {
7656 : case CallerAPI::DRAW_IMAGE: {
7657 0 : if (isVisible) {
7658 : // 2 = drawImage_VISIBLE
7659 0 : Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 2);
7660 : } else {
7661 : // 3 = drawImage_INVISIBLE
7662 0 : Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 3);
7663 :
7664 0 : if (IsInUncomposedDoc()) {
7665 : // 2 = drawImage_IN_TREE
7666 : Telemetry::Accumulate(
7667 0 : Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 2);
7668 : } else {
7669 : // 3 = drawImage_NOT_IN_TREE
7670 : Telemetry::Accumulate(
7671 0 : Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 3);
7672 : }
7673 : }
7674 : break;
7675 : }
7676 : case CallerAPI::CREATE_PATTERN: {
7677 0 : if (isVisible) {
7678 : // 4 = createPattern_VISIBLE
7679 0 : Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 4);
7680 : } else {
7681 : // 5 = createPattern_INVISIBLE
7682 0 : Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 5);
7683 :
7684 0 : if (IsInUncomposedDoc()) {
7685 : // 4 = createPattern_IN_TREE
7686 : Telemetry::Accumulate(
7687 0 : Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 4);
7688 : } else {
7689 : // 5 = createPattern_NOT_IN_TREE
7690 : Telemetry::Accumulate(
7691 0 : Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 5);
7692 : }
7693 : }
7694 : break;
7695 : }
7696 : case CallerAPI::CREATE_IMAGEBITMAP: {
7697 0 : if (isVisible) {
7698 : // 6 = createImageBitmap_VISIBLE
7699 0 : Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 6);
7700 : } else {
7701 : // 7 = createImageBitmap_INVISIBLE
7702 0 : Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 7);
7703 :
7704 0 : if (IsInUncomposedDoc()) {
7705 : // 6 = createImageBitmap_IN_TREE
7706 : Telemetry::Accumulate(
7707 0 : Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 6);
7708 : } else {
7709 : // 7 = createImageBitmap_NOT_IN_TREE
7710 : Telemetry::Accumulate(
7711 0 : Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 7);
7712 : }
7713 : }
7714 : break;
7715 : }
7716 : case CallerAPI::CAPTURE_STREAM: {
7717 0 : if (isVisible) {
7718 : // 8 = captureStream_VISIBLE
7719 0 : Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 8);
7720 : } else {
7721 : // 9 = captureStream_INVISIBLE
7722 0 : Telemetry::Accumulate(Telemetry::VIDEO_AS_CONTENT_SOURCE, 9);
7723 :
7724 0 : if (IsInUncomposedDoc()) {
7725 : // 8 = captureStream_IN_TREE
7726 : Telemetry::Accumulate(
7727 0 : Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 8);
7728 : } else {
7729 : // 9 = captureStream_NOT_IN_TREE
7730 : Telemetry::Accumulate(
7731 0 : Telemetry::VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT, 9);
7732 : }
7733 : }
7734 : break;
7735 : }
7736 : }
7737 :
7738 0 : LOG(LogLevel::Debug,
7739 : ("%p Log VIDEO_AS_CONTENT_SOURCE: visibility = %u, API: '%d' and 'All'",
7740 : this,
7741 : isVisible,
7742 : static_cast<int>(aAPI)));
7743 :
7744 0 : if (!isVisible) {
7745 0 : LOG(LogLevel::Debug,
7746 : ("%p Log VIDEO_AS_CONTENT_SOURCE_IN_TREE_OR_NOT: inTree = %u, API: "
7747 : "'%d' and 'All'",
7748 : this,
7749 : IsInUncomposedDoc(),
7750 : static_cast<int>(aAPI)));
7751 : }
7752 0 : }
7753 :
7754 : void
7755 0 : HTMLMediaElement::UpdateCustomPolicyAfterPlayed()
7756 : {
7757 0 : OpenUnsupportedMediaWithExternalAppIfNeeded();
7758 0 : if (mAudioChannelWrapper) {
7759 0 : mAudioChannelWrapper->NotifyPlayStateChanged();
7760 : }
7761 0 : }
7762 :
7763 : AbstractThread*
7764 0 : HTMLMediaElement::AbstractMainThread() const
7765 : {
7766 0 : MOZ_ASSERT(mAbstractMainThread);
7767 :
7768 0 : return mAbstractMainThread;
7769 : }
7770 :
7771 : nsTArray<RefPtr<PlayPromise>>
7772 0 : HTMLMediaElement::TakePendingPlayPromises()
7773 : {
7774 0 : return std::move(mPendingPlayPromises);
7775 : }
7776 :
7777 : void
7778 0 : HTMLMediaElement::NotifyAboutPlaying()
7779 : {
7780 : // Stick to the DispatchAsyncEvent() call path for now because we want to
7781 : // trigger some telemetry-related codes in the DispatchAsyncEvent() method.
7782 0 : DispatchAsyncEvent(NS_LITERAL_STRING("playing"));
7783 0 : }
7784 :
7785 : already_AddRefed<PlayPromise>
7786 0 : HTMLMediaElement::CreatePlayPromise(ErrorResult& aRv) const
7787 : {
7788 0 : nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
7789 :
7790 0 : if (!win) {
7791 0 : aRv.Throw(NS_ERROR_UNEXPECTED);
7792 : return nullptr;
7793 : }
7794 :
7795 0 : RefPtr<PlayPromise> promise = PlayPromise::Create(win->AsGlobal(), aRv);
7796 0 : LOG(LogLevel::Debug, ("%p created PlayPromise %p", this, promise.get()));
7797 :
7798 0 : return promise.forget();
7799 : }
7800 :
7801 : already_AddRefed<Promise>
7802 0 : HTMLMediaElement::CreateDOMPromise(ErrorResult& aRv) const
7803 : {
7804 0 : nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow();
7805 :
7806 0 : if (!win) {
7807 0 : aRv.Throw(NS_ERROR_UNEXPECTED);
7808 : return nullptr;
7809 : }
7810 :
7811 0 : return Promise::Create(win->AsGlobal(), aRv);
7812 : }
7813 :
7814 : void
7815 0 : HTMLMediaElement::AsyncResolvePendingPlayPromises()
7816 : {
7817 0 : if (mShuttingDown) {
7818 0 : return;
7819 : }
7820 :
7821 : nsCOMPtr<nsIRunnable> event = new nsResolveOrRejectPendingPlayPromisesRunner(
7822 0 : this, TakePendingPlayPromises());
7823 :
7824 0 : mMainThreadEventTarget->Dispatch(event.forget());
7825 : }
7826 :
7827 : void
7828 0 : HTMLMediaElement::AsyncRejectPendingPlayPromises(nsresult aError)
7829 : {
7830 0 : if (mShuttingDown) {
7831 0 : return;
7832 : }
7833 :
7834 : nsCOMPtr<nsIRunnable> event = new nsResolveOrRejectPendingPlayPromisesRunner(
7835 0 : this, TakePendingPlayPromises(), aError);
7836 :
7837 0 : mMainThreadEventTarget->Dispatch(event.forget());
7838 : }
7839 :
7840 : void
7841 0 : HTMLMediaElement::GetEMEInfo(nsString& aEMEInfo)
7842 : {
7843 0 : if (!mMediaKeys) {
7844 0 : return;
7845 : }
7846 :
7847 0 : nsString keySystem;
7848 0 : mMediaKeys->GetKeySystem(keySystem);
7849 :
7850 0 : nsString sessionsInfo;
7851 0 : mMediaKeys->GetSessionsInfo(sessionsInfo);
7852 :
7853 0 : aEMEInfo.AppendLiteral("Key System=");
7854 0 : aEMEInfo.Append(keySystem);
7855 0 : aEMEInfo.AppendLiteral(" SessionsInfo=");
7856 0 : aEMEInfo.Append(sessionsInfo);
7857 : }
7858 :
7859 : void
7860 0 : HTMLMediaElement::NotifyDecoderActivityChanges() const
7861 : {
7862 2 : if (mDecoder) {
7863 0 : mDecoder->NotifyOwnerActivityChanged(
7864 0 : !IsHidden(), mVisibilityState, IsInUncomposedDoc());
7865 : }
7866 1 : }
7867 :
7868 : nsIDocument*
7869 0 : HTMLMediaElement::GetDocument() const
7870 : {
7871 0 : return OwnerDoc();
7872 : }
7873 :
7874 : void
7875 0 : HTMLMediaElement::ConstructMediaTracks(const MediaInfo* aInfo)
7876 : {
7877 0 : if (mMediaTracksConstructed || !aInfo) {
7878 : return;
7879 : }
7880 :
7881 0 : mMediaTracksConstructed = true;
7882 :
7883 0 : AudioTrackList* audioList = AudioTracks();
7884 0 : if (audioList && aInfo->HasAudio()) {
7885 0 : const TrackInfo& info = aInfo->mAudio;
7886 : RefPtr<AudioTrack> track =
7887 0 : MediaTrackList::CreateAudioTrack(audioList->GetOwnerGlobal(),
7888 : info.mId,
7889 : info.mKind,
7890 : info.mLabel,
7891 : info.mLanguage,
7892 0 : info.mEnabled);
7893 :
7894 0 : audioList->AddTrack(track);
7895 : }
7896 :
7897 0 : VideoTrackList* videoList = VideoTracks();
7898 0 : if (videoList && aInfo->HasVideo()) {
7899 0 : const TrackInfo& info = aInfo->mVideo;
7900 : RefPtr<VideoTrack> track =
7901 0 : MediaTrackList::CreateVideoTrack(videoList->GetOwnerGlobal(),
7902 : info.mId,
7903 : info.mKind,
7904 : info.mLabel,
7905 0 : info.mLanguage);
7906 :
7907 0 : videoList->AddTrack(track);
7908 0 : track->SetEnabledInternal(info.mEnabled, MediaTrack::FIRE_NO_EVENTS);
7909 : }
7910 : }
7911 :
7912 : void
7913 0 : HTMLMediaElement::RemoveMediaTracks()
7914 : {
7915 0 : if (mAudioTrackList) {
7916 0 : mAudioTrackList->RemoveTracks();
7917 : }
7918 :
7919 0 : if (mVideoTrackList) {
7920 0 : mVideoTrackList->RemoveTracks();
7921 : }
7922 :
7923 0 : mMediaTracksConstructed = false;
7924 :
7925 0 : for (OutputMediaStream& ms : mOutputStreams) {
7926 0 : if (!ms.mCapturingDecoder) {
7927 : continue;
7928 : }
7929 0 : for (RefPtr<MediaStreamTrack>& t : ms.mPreCreatedTracks) {
7930 0 : if (t->Ended()) {
7931 : continue;
7932 : }
7933 0 : mAbstractMainThread->Dispatch(NewRunnableMethod(
7934 : "dom::HTMLMediaElement::RemoveMediaTracks",
7935 0 : t, &MediaStreamTrack::OverrideEnded));
7936 : }
7937 0 : ms.mPreCreatedTracks.Clear();
7938 : }
7939 0 : }
7940 :
7941 0 : class MediaElementGMPCrashHelper : public GMPCrashHelper
7942 : {
7943 : public:
7944 0 : explicit MediaElementGMPCrashHelper(HTMLMediaElement* aElement)
7945 0 : : mElement(aElement)
7946 : {
7947 0 : MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
7948 0 : }
7949 0 : already_AddRefed<nsPIDOMWindowInner> GetPluginCrashedEventTarget() override
7950 : {
7951 0 : MOZ_ASSERT(NS_IsMainThread()); // WeakPtr isn't thread safe.
7952 0 : if (!mElement) {
7953 : return nullptr;
7954 : }
7955 0 : return do_AddRef(mElement->OwnerDoc()->GetInnerWindow());
7956 : }
7957 :
7958 : private:
7959 : WeakPtr<HTMLMediaElement> mElement;
7960 : };
7961 :
7962 : already_AddRefed<GMPCrashHelper>
7963 0 : HTMLMediaElement::CreateGMPCrashHelper()
7964 : {
7965 0 : return MakeAndAddRef<MediaElementGMPCrashHelper>(this);
7966 : }
7967 :
7968 : void
7969 0 : HTMLMediaElement::MarkAsTainted()
7970 : {
7971 0 : mHasSuspendTaint = true;
7972 :
7973 0 : if (mDecoder) {
7974 0 : mDecoder->SetSuspendTaint(true);
7975 : }
7976 0 : }
7977 :
7978 : bool
7979 0 : HasDebuggerOrTabsPrivilege(JSContext* aCx, JSObject* aObj)
7980 : {
7981 0 : return nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::debugger) ||
7982 0 : nsContentUtils::CallerHasPermission(aCx, nsGkAtoms::tabs);
7983 : }
7984 :
7985 : void
7986 0 : HTMLMediaElement::AsyncResolveSeekDOMPromiseIfExists()
7987 : {
7988 0 : MOZ_ASSERT(NS_IsMainThread());
7989 0 : if (mSeekDOMPromise) {
7990 0 : RefPtr<dom::Promise> promise = mSeekDOMPromise.forget();
7991 0 : nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
7992 : "dom::HTMLMediaElement::AsyncResolveSeekDOMPromiseIfExists",
7993 0 : [=]() { promise->MaybeResolveWithUndefined(); });
7994 0 : mAbstractMainThread->Dispatch(r.forget());
7995 0 : mSeekDOMPromise = nullptr;
7996 : }
7997 0 : }
7998 :
7999 : void
8000 0 : HTMLMediaElement::AsyncRejectSeekDOMPromiseIfExists()
8001 : {
8002 0 : MOZ_ASSERT(NS_IsMainThread());
8003 0 : if (mSeekDOMPromise) {
8004 0 : RefPtr<dom::Promise> promise = mSeekDOMPromise.forget();
8005 0 : nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction(
8006 : "dom::HTMLMediaElement::AsyncRejectSeekDOMPromiseIfExists",
8007 0 : [=]() { promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); });
8008 0 : mAbstractMainThread->Dispatch(r.forget());
8009 0 : mSeekDOMPromise = nullptr;
8010 : }
8011 0 : }
8012 :
8013 : void
8014 0 : HTMLMediaElement::ReportCanPlayTelemetry()
8015 : {
8016 0 : LOG(LogLevel::Debug, ("%s", __func__));
8017 :
8018 0 : RefPtr<nsIThread> thread;
8019 0 : nsresult rv = NS_NewNamedThread("MediaTelemetry", getter_AddRefs(thread));
8020 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
8021 0 : return;
8022 : }
8023 :
8024 0 : RefPtr<AbstractThread> abstractThread = mAbstractMainThread;
8025 :
8026 0 : thread->Dispatch(
8027 0 : NS_NewRunnableFunction(
8028 : "dom::HTMLMediaElement::ReportCanPlayTelemetry",
8029 0 : [thread, abstractThread]() {
8030 : #if XP_WIN
8031 : // Windows Media Foundation requires MSCOM to be inited.
8032 : DebugOnly<HRESULT> hr = CoInitializeEx(0, COINIT_MULTITHREADED);
8033 : MOZ_ASSERT(hr == S_OK);
8034 : #endif
8035 : bool aac = MP4Decoder::IsSupportedType(
8036 0 : MediaContainerType(MEDIAMIMETYPE(AUDIO_MP4)), nullptr);
8037 : bool h264 = MP4Decoder::IsSupportedType(
8038 0 : MediaContainerType(MEDIAMIMETYPE(VIDEO_MP4)), nullptr);
8039 : #if XP_WIN
8040 : CoUninitialize();
8041 : #endif
8042 0 : abstractThread->Dispatch(NS_NewRunnableFunction(
8043 : "dom::HTMLMediaElement::ReportCanPlayTelemetry",
8044 0 : [thread, aac, h264]() {
8045 0 : LOG(LogLevel::Debug, ("MediaTelemetry aac=%d h264=%d", aac, h264));
8046 0 : Telemetry::Accumulate(
8047 0 : Telemetry::HistogramID::VIDEO_CAN_CREATE_AAC_DECODER, aac);
8048 0 : Telemetry::Accumulate(
8049 0 : Telemetry::HistogramID::VIDEO_CAN_CREATE_H264_DECODER, h264);
8050 0 : thread->AsyncShutdown();
8051 0 : }));
8052 0 : }),
8053 0 : NS_DISPATCH_NORMAL);
8054 : }
8055 :
8056 : } // namespace dom
8057 : } // namespace mozilla
8058 :
8059 : #undef LOG
8060 : #undef LOG_EVENT
|