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