Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
#ifdef XP_WIN
8
# include "objbase.h"
9
#endif
10
11
#include "mozilla/dom/HTMLMediaElement.h"
12
#include "AudioDeviceInfo.h"
13
#include "AudioStreamTrack.h"
14
#include "AutoplayPolicy.h"
15
#include "ChannelMediaDecoder.h"
16
#include "DOMMediaStream.h"
17
#include "DecoderDoctorDiagnostics.h"
18
#include "DecoderDoctorLogger.h"
19
#include "DecoderTraits.h"
20
#include "FrameStatistics.h"
21
#include "GMPCrashHelper.h"
22
#include "GVAutoplayPermissionRequest.h"
23
#ifdef MOZ_ANDROID_HLS_SUPPORT
24
# include "HLSDecoder.h"
25
#endif
26
#include "HTMLMediaElement.h"
27
#include "ImageContainer.h"
28
#include "Layers.h"
29
#include "MP4Decoder.h"
30
#include "MediaContainerType.h"
31
#include "MediaError.h"
32
#include "MediaManager.h"
33
#include "MediaMetadataManager.h"
34
#include "MediaResource.h"
35
#include "MediaShutdownManager.h"
36
#include "MediaSourceDecoder.h"
37
#include "MediaStreamError.h"
38
#include "MediaTrackGraphImpl.h"
39
#include "MediaTrackListener.h"
40
#include "MediaStreamWindowCapturer.h"
41
#include "MediaTrack.h"
42
#include "MediaTrackList.h"
43
#include "SVGObserverUtils.h"
44
#include "TimeRanges.h"
45
#include "VideoFrameContainer.h"
46
#include "VideoOutput.h"
47
#include "VideoStreamTrack.h"
48
#include "base/basictypes.h"
49
#include "jsapi.h"
50
#include "mozilla/ArrayUtils.h"
51
#include "mozilla/AsyncEventDispatcher.h"
52
#include "mozilla/EMEUtils.h"
53
#include "mozilla/EventDispatcher.h"
54
#include "mozilla/FloatingPoint.h"
55
#include "mozilla/MathAlgorithms.h"
56
#include "mozilla/NotNull.h"
57
#include "mozilla/Preferences.h"
58
#include "mozilla/PresShell.h"
59
#include "mozilla/Sprintf.h"
60
#include "mozilla/StaticPrefs_media.h"
61
#include "mozilla/Telemetry.h"
62
#include "mozilla/dom/AudioTrack.h"
63
#include "mozilla/dom/AudioTrackList.h"
64
#include "mozilla/dom/BlobURLProtocolHandler.h"
65
#include "mozilla/dom/ContentMediaController.h"
66
#include "mozilla/dom/ElementInlines.h"
67
#include "mozilla/dom/HTMLAudioElement.h"
68
#include "mozilla/dom/HTMLInputElement.h"
69
#include "mozilla/dom/HTMLMediaElementBinding.h"
70
#include "mozilla/dom/HTMLSourceElement.h"
71
#include "mozilla/dom/HTMLVideoElement.h"
72
#include "mozilla/dom/MediaControlUtils.h"
73
#include "mozilla/dom/MediaEncryptedEvent.h"
74
#include "mozilla/dom/MediaErrorBinding.h"
75
#include "mozilla/dom/MediaSource.h"
76
#include "mozilla/dom/PlayPromise.h"
77
#include "mozilla/dom/Promise.h"
78
#include "mozilla/dom/TextTrack.h"
79
#include "mozilla/dom/UserActivation.h"
80
#include "mozilla/dom/VideoPlaybackQuality.h"
81
#include "mozilla/dom/VideoTrack.h"
82
#include "mozilla/dom/VideoTrackList.h"
83
#include "mozilla/dom/WakeLock.h"
84
#include "mozilla/dom/power/PowerManagerService.h"
85
#include "mozilla/net/UrlClassifierFeatureFactory.h"
86
#include "nsAttrValueInlines.h"
87
#include "nsContentPolicyUtils.h"
88
#include "nsContentUtils.h"
89
#include "nsCycleCollectionParticipant.h"
90
#include "nsDisplayList.h"
91
#include "nsDocShell.h"
92
#include "nsError.h"
93
#include "nsGenericHTMLElement.h"
94
#include "nsGkAtoms.h"
95
#include "nsIAsyncVerifyRedirectCallback.h"
96
#include "nsICachingChannel.h"
97
#include "nsIClassOfService.h"
98
#include "nsIContentPolicy.h"
99
#include "nsIDocShell.h"
100
#include "mozilla/dom/Document.h"
101
#include "nsIFrame.h"
102
#include "nsIObserverService.h"
103
#include "nsIRequest.h"
104
#include "nsIScriptError.h"
105
#include "nsISupportsPrimitives.h"
106
#include "nsITimer.h"
107
#include "nsJSUtils.h"
108
#include "nsLayoutUtils.h"
109
#include "nsMediaFragmentURIParser.h"
110
#include "nsMimeTypes.h"
111
#include "nsNetUtil.h"
112
#include "nsNodeInfoManager.h"
113
#include "nsPresContext.h"
114
#include "nsQueryObject.h"
115
#include "nsRange.h"
116
#include "nsSize.h"
117
#include "nsThreadUtils.h"
118
#include "nsURIHashKey.h"
119
#include "nsVideoFrame.h"
120
#include "ReferrerInfo.h"
121
#include "xpcpublic.h"
122
#include <algorithm>
123
#include <cmath>
124
#include <limits>
125
126
mozilla::LazyLogModule gMediaElementLog("nsMediaElement");
127
static mozilla::LazyLogModule gMediaElementEventsLog("nsMediaElementEvents");
128
129
extern mozilla::LazyLogModule gAutoplayPermissionLog;
130
#define AUTOPLAY_LOG(msg, ...) \
131
MOZ_LOG(gAutoplayPermissionLog, LogLevel::Debug, (msg, ##__VA_ARGS__))
132
133
// avoid redefined macro in unified build
134
#undef MEDIACONTROL_LOG
135
#define MEDIACONTROL_LOG(msg, ...) \
136
MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
137
("HTMLMediaElement=%p, " msg, this, ##__VA_ARGS__))
138
139
#define LOG(type, msg) MOZ_LOG(gMediaElementLog, type, msg)
140
#define LOG_EVENT(type, msg) MOZ_LOG(gMediaElementEventsLog, type, msg)
141
142
using namespace mozilla::layers;
143
using mozilla::net::nsMediaFragmentURIParser;
144
using namespace mozilla::dom::HTMLMediaElement_Binding;
145
146
namespace mozilla {
147
namespace dom {
148
149
using AudibleState = AudioChannelService::AudibleState;
150
151
// Number of milliseconds between progress events as defined by spec
152
static const uint32_t PROGRESS_MS = 350;
153
154
// Number of milliseconds of no data before a stall event is fired as defined by
155
// spec
156
static const uint32_t STALL_MS = 3000;
157
158
// Used by AudioChannel for suppresssing the volume to this ratio.
159
#define FADED_VOLUME_RATIO 0.25
160
161
// These constants are arbitrary
162
// Minimum playbackRate for a media
163
static const double MIN_PLAYBACKRATE = 1.0 / 16;
164
// Maximum playbackRate for a media
165
static const double MAX_PLAYBACKRATE = 16.0;
166
// These are the limits beyonds which SoundTouch does not perform too well and
167
// when speech is hard to understand anyway. Threshold above which audio is
168
// muted
169
static const double THRESHOLD_HIGH_PLAYBACKRATE_AUDIO = 4.0;
170
// Threshold under which audio is muted
171
static const double THRESHOLD_LOW_PLAYBACKRATE_AUDIO = 0.25;
172
173
static double ClampPlaybackRate(double aPlaybackRate) {
174
MOZ_ASSERT(aPlaybackRate >= 0.0);
175
176
if (aPlaybackRate == 0.0) {
177
return aPlaybackRate;
178
}
179
if (aPlaybackRate < MIN_PLAYBACKRATE) {
180
return MIN_PLAYBACKRATE;
181
}
182
if (aPlaybackRate > MAX_PLAYBACKRATE) {
183
return MAX_PLAYBACKRATE;
184
}
185
return aPlaybackRate;
186
}
187
188
// Media error values. These need to match the ones in MediaError.webidl.
189
static const unsigned short MEDIA_ERR_ABORTED = 1;
190
static const unsigned short MEDIA_ERR_NETWORK = 2;
191
static const unsigned short MEDIA_ERR_DECODE = 3;
192
static const unsigned short MEDIA_ERR_SRC_NOT_SUPPORTED = 4;
193
194
static void ResolvePromisesWithUndefined(
195
const nsTArray<RefPtr<PlayPromise>>& aPromises) {
196
for (auto& promise : aPromises) {
197
promise->MaybeResolveWithUndefined();
198
}
199
}
200
201
static void RejectPromises(const nsTArray<RefPtr<PlayPromise>>& aPromises,
202
nsresult aError) {
203
for (auto& promise : aPromises) {
204
promise->MaybeReject(aError);
205
}
206
}
207
208
// Under certain conditions there may be no-one holding references to
209
// a media element from script, DOM parent, etc, but the element may still
210
// fire meaningful events in the future so we can't destroy it yet:
211
// 1) If the element is delaying the load event (or would be, if it were
212
// in a document), then events up to loadeddata or error could be fired,
213
// so we need to stay alive.
214
// 2) If the element is not paused and playback has not ended, then
215
// we will (or might) play, sending timeupdate and ended events and possibly
216
// audio output, so we need to stay alive.
217
// 3) if the element is seeking then we will fire seeking events and possibly
218
// start playing afterward, so we need to stay alive.
219
// 4) If autoplay could start playback in this element (if we got enough data),
220
// then we need to stay alive.
221
// 5) if the element is currently loading, not suspended, and its source is
222
// not a MediaSource, then script might be waiting for progress events or a
223
// 'stalled' or 'suspend' event, so we need to stay alive.
224
// If we're already suspended then (all other conditions being met),
225
// it's OK to just disappear without firing any more events,
226
// since we have the freedom to remain suspended indefinitely. Note
227
// that we could use this 'suspended' loophole to garbage-collect a suspended
228
// element in case 4 even if it had 'autoplay' set, but we choose not to.
229
// If someone throws away all references to a loading 'autoplay' element
230
// sound should still eventually play.
231
// 6) If the source is a MediaSource, most loading events will not fire unless
232
// appendBuffer() is called on a SourceBuffer, in which case something is
233
// already referencing the SourceBuffer, which keeps the associated media
234
// element alive. Further, a MediaSource will never time out the resource
235
// fetch, and so should not keep the media element alive if it is
236
// unreferenced. A pending 'stalled' event keeps the media element alive.
237
//
238
// Media elements owned by inactive documents (i.e. documents not contained in
239
// any document viewer) should never hold a self-reference because none of the
240
// above conditions are allowed: the element will stop loading and playing
241
// and never resume loading or playing unless its owner document changes to
242
// an active document (which can only happen if there is an external reference
243
// to the element).
244
// Media elements with no owner doc should be able to hold a self-reference.
245
// Something native must have created the element and may expect it to
246
// stay alive to play.
247
248
// It's very important that any change in state which could change the value of
249
// needSelfReference in AddRemoveSelfReference be followed by a call to
250
// AddRemoveSelfReference before this element could die!
251
// It's especially important if needSelfReference would change to 'true',
252
// since if we neglect to add a self-reference, this element might be
253
// garbage collected while there are still event listeners that should
254
// receive events. If we neglect to remove the self-reference then the element
255
// just lives longer than it needs to.
256
257
class nsMediaEvent : public Runnable {
258
public:
259
explicit nsMediaEvent(const char* aName, HTMLMediaElement* aElement)
260
: Runnable(aName),
261
mElement(aElement),
262
mLoadID(mElement->GetCurrentLoadID()) {}
263
~nsMediaEvent() = default;
264
265
NS_IMETHOD Run() override = 0;
266
267
protected:
268
bool IsCancelled() { return mElement->GetCurrentLoadID() != mLoadID; }
269
270
RefPtr<HTMLMediaElement> mElement;
271
uint32_t mLoadID;
272
};
273
274
class HTMLMediaElement::nsAsyncEventRunner : public nsMediaEvent {
275
private:
276
nsString mName;
277
278
public:
279
nsAsyncEventRunner(const nsAString& aName, HTMLMediaElement* aElement)
280
: nsMediaEvent("HTMLMediaElement::nsAsyncEventRunner", aElement),
281
mName(aName) {}
282
283
NS_IMETHOD Run() override {
284
// Silently cancel if our load has been cancelled.
285
if (IsCancelled()) return NS_OK;
286
287
return mElement->DispatchEvent(mName);
288
}
289
};
290
291
/*
292
* If no error is passed while constructing an instance, the instance will
293
* resolve the passed promises with undefined; otherwise, the instance will
294
* reject the passed promises with the passed error.
295
*
296
* The constructor appends the constructed instance into the passed media
297
* element's mPendingPlayPromisesRunners member and once the the runner is run
298
* (whether fulfilled or canceled), it removes itself from
299
* mPendingPlayPromisesRunners.
300
*/
301
class HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner
302
: public nsMediaEvent {
303
nsTArray<RefPtr<PlayPromise>> mPromises;
304
nsresult mError;
305
306
public:
307
nsResolveOrRejectPendingPlayPromisesRunner(
308
HTMLMediaElement* aElement, nsTArray<RefPtr<PlayPromise>>&& aPromises,
309
nsresult aError = NS_OK)
310
: nsMediaEvent(
311
"HTMLMediaElement::nsResolveOrRejectPendingPlayPromisesRunner",
312
aElement),
313
mPromises(std::move(aPromises)),
314
mError(aError) {
315
mElement->mPendingPlayPromisesRunners.AppendElement(this);
316
}
317
318
void ResolveOrReject() {
319
if (NS_SUCCEEDED(mError)) {
320
ResolvePromisesWithUndefined(mPromises);
321
} else {
322
RejectPromises(mPromises, mError);
323
}
324
}
325
326
NS_IMETHOD Run() override {
327
if (!IsCancelled()) {
328
ResolveOrReject();
329
}
330
331
mElement->mPendingPlayPromisesRunners.RemoveElement(this);
332
return NS_OK;
333
}
334
};
335
336
class HTMLMediaElement::nsNotifyAboutPlayingRunner
337
: public nsResolveOrRejectPendingPlayPromisesRunner {
338
public:
339
nsNotifyAboutPlayingRunner(
340
HTMLMediaElement* aElement,
341
nsTArray<RefPtr<PlayPromise>>&& aPendingPlayPromises)
342
: nsResolveOrRejectPendingPlayPromisesRunner(
343
aElement, std::move(aPendingPlayPromises)) {}
344
345
NS_IMETHOD Run() override {
346
if (IsCancelled()) {
347
mElement->mPendingPlayPromisesRunners.RemoveElement(this);
348
return NS_OK;
349
}
350
351
mElement->DispatchEvent(NS_LITERAL_STRING("playing"));
352
return nsResolveOrRejectPendingPlayPromisesRunner::Run();
353
}
354
};
355
356
class nsSourceErrorEventRunner : public nsMediaEvent {
357
private:
358
nsCOMPtr<nsIContent> mSource;
359
360
public:
361
nsSourceErrorEventRunner(HTMLMediaElement* aElement, nsIContent* aSource)
362
: nsMediaEvent("dom::nsSourceErrorEventRunner", aElement),
363
mSource(aSource) {}
364
365
NS_IMETHOD Run() override {
366
// Silently cancel if our load has been cancelled.
367
if (IsCancelled()) return NS_OK;
368
LOG_EVENT(LogLevel::Debug,
369
("%p Dispatching simple event source error", mElement.get()));
370
return nsContentUtils::DispatchTrustedEvent(
371
mElement->OwnerDoc(), mSource, NS_LITERAL_STRING("error"),
372
CanBubble::eNo, Cancelable::eNo);
373
}
374
};
375
376
/**
377
* We use MediaControlEventListener to listen MediaControlKeysEvent in order to
378
* play and pause media element when user press media control keys and update
379
* media's playback and audible state to the media controller.
380
*
381
* Use `Start()` to start listening event and use `Stop()` to stop listening
382
* event. In addition, notifying any change to media controller MUST be done
383
* after successfully calling `Start()`.
384
*/
385
class HTMLMediaElement::MediaControlEventListener final
386
: public MediaControlKeysEventListener {
387
public:
388
NS_INLINE_DECL_REFCOUNTING(MediaControlEventListener, override)
389
390
MOZ_INIT_OUTSIDE_CTOR explicit MediaControlEventListener(
391
HTMLMediaElement* aElement)
392
: mElement(aElement) {
393
MOZ_ASSERT(NS_IsMainThread());
394
MOZ_ASSERT(aElement);
395
}
396
397
void Start() {
398
MOZ_ASSERT(NS_IsMainThread());
399
if (IsStarted()) {
400
// We have already been started, do not notify start twice.
401
return;
402
}
403
404
// Fail to init media agent, we are not able to notify the media controller
405
// any update and also are not able to receive media control key events.
406
if (!InitMediaAgent()) {
407
MEDIACONTROL_LOG("Fail to init content media agent!");
408
return;
409
}
410
411
NotifyMediaStateChanged(ControlledMediaState::eStarted);
412
}
413
414
void Stop() {
415
MOZ_ASSERT(NS_IsMainThread());
416
if (!IsStarted()) {
417
// We have already been stopped, do not notify stop twice.
418
return;
419
}
420
NotifyMediaStateChanged(ControlledMediaState::eStopped);
421
422
// Remove ourselves from media agent, which would stop receiving event.
423
mControlAgent->RemoveListener(this);
424
mControlAgent = nullptr;
425
}
426
427
void NotifyMediaStartedPlaying() {
428
MOZ_ASSERT(NS_IsMainThread());
429
MOZ_ASSERT(IsStarted());
430
if (mState == ControlledMediaState::eStarted ||
431
mState == ControlledMediaState::ePaused) {
432
NotifyMediaStateChanged(ControlledMediaState::ePlayed);
433
NotifyAudibleStateChanged();
434
}
435
}
436
437
void NotifyMediaStoppedPlaying() {
438
MOZ_ASSERT(NS_IsMainThread());
439
MOZ_ASSERT(IsStarted());
440
if (mState == ControlledMediaState::ePlayed) {
441
NotifyMediaStateChanged(ControlledMediaState::ePaused);
442
}
443
}
444
445
void UpdateMediaAudibleState(bool aIsOwnerAudible) {
446
MOZ_ASSERT(NS_IsMainThread());
447
if (mIsOwnerAudible == aIsOwnerAudible) {
448
return;
449
}
450
mIsOwnerAudible = aIsOwnerAudible;
451
// If media hasn't started playing, it doesn't make sense to update media
452
// audible state. Therefore, in that case we would noitfy the audible state
453
// when media starts playing.
454
if (mState == ControlledMediaState::ePlayed) {
455
NotifyAudibleStateChanged();
456
}
457
}
458
459
void OnKeyPressed(MediaControlKeysEvent aEvent) override {
460
MOZ_ASSERT(NS_IsMainThread());
461
MOZ_ASSERT(IsStarted());
462
MEDIACONTROL_LOG("OnKeyPressed '%s'", ToMediaControlKeysEventStr(aEvent));
463
if (aEvent == MediaControlKeysEvent::ePlay && Owner()->Paused()) {
464
Owner()->Play();
465
} else if ((aEvent == MediaControlKeysEvent::ePause ||
466
aEvent == MediaControlKeysEvent::eStop) &&
467
!Owner()->Paused()) {
468
Owner()->Pause();
469
}
470
}
471
472
bool IsStarted() const { return mState != ControlledMediaState::eStopped; }
473
474
private:
475
~MediaControlEventListener() = default;
476
477
bool InitMediaAgent() {
478
MOZ_ASSERT(NS_IsMainThread());
479
nsPIDOMWindowInner* window = Owner()->OwnerDoc()->GetInnerWindow();
480
if (!window) {
481
return false;
482
}
483
484
mControlAgent = ContentMediaAgent::Get(window->GetBrowsingContext());
485
if (!mControlAgent) {
486
return false;
487
}
488
mControlAgent->AddListener(this);
489
return true;
490
}
491
492
HTMLMediaElement* Owner() const { return mElement.get(); }
493
494
void NotifyMediaStateChanged(ControlledMediaState aState) {
495
MOZ_ASSERT(NS_IsMainThread());
496
MOZ_ASSERT(mControlAgent);
497
MEDIACONTROL_LOG("NotifyMediaState from state='%s' to state='%s'",
498
ToControlledMediaStateStr(mState),
499
ToControlledMediaStateStr(aState));
500
MOZ_ASSERT(mState != aState, "Should not notify same state again!");
501
mState = aState;
502
mControlAgent->NotifyMediaStateChanged(this, mState);
503
}
504
505
void NotifyAudibleStateChanged() {
506
MOZ_ASSERT(NS_IsMainThread());
507
MOZ_ASSERT(IsStarted());
508
mControlAgent->NotifyAudibleStateChanged(this, mIsOwnerAudible);
509
}
510
511
ControlledMediaState mState = ControlledMediaState::eStopped;
512
WeakPtr<HTMLMediaElement> mElement;
513
RefPtr<ContentMediaAgent> mControlAgent;
514
bool mIsOwnerAudible = false;
515
};
516
517
class HTMLMediaElement::MediaStreamTrackListener
518
: public DOMMediaStream::TrackListener {
519
public:
520
explicit MediaStreamTrackListener(HTMLMediaElement* aElement)
521
: mElement(aElement) {}
522
523
void NotifyTrackAdded(const RefPtr<MediaStreamTrack>& aTrack) override {
524
if (!mElement) {
525
return;
526
}
527
mElement->NotifyMediaStreamTrackAdded(aTrack);
528
}
529
530
void NotifyTrackRemoved(const RefPtr<MediaStreamTrack>& aTrack) override {
531
if (!mElement) {
532
return;
533
}
534
mElement->NotifyMediaStreamTrackRemoved(aTrack);
535
}
536
537
void OnActive() {
538
MOZ_ASSERT(mElement);
539
540
// mediacapture-main says:
541
// Note that once ended equals true the HTMLVideoElement will not play media
542
// even if new MediaStreamTracks are added to the MediaStream (causing it to
543
// return to the active state) unless autoplay is true or the web
544
// application restarts the element, e.g., by calling play().
545
//
546
// This is vague on exactly how to go from becoming active to playing, when
547
// autoplaying. However, per the media element spec, to play an autoplaying
548
// media element, we must load the source and reach readyState
549
// HAVE_ENOUGH_DATA [1]. Hence, a MediaStream being assigned to a media
550
// element and becoming active runs the load algorithm, so that it can
551
// eventually be played.
552
//
553
// [1]
555
556
LOG(LogLevel::Debug, ("%p, mSrcStream %p became active, checking if we "
557
"need to run the load algorithm",
558
mElement.get(), mElement->mSrcStream.get()));
559
if (!mElement->IsPlaybackEnded()) {
560
return;
561
}
562
if (!mElement->Autoplay()) {
563
return;
564
}
565
LOG(LogLevel::Info, ("%p, mSrcStream %p became active on autoplaying, "
566
"ended element. Reloading.",
567
mElement.get(), mElement->mSrcStream.get()));
568
mElement->DoLoad();
569
}
570
571
void NotifyActive() override {
572
if (!mElement) {
573
return;
574
}
575
576
if (!mElement->IsVideo()) {
577
// Audio elements use NotifyAudible().
578
return;
579
}
580
581
OnActive();
582
}
583
584
void NotifyAudible() override {
585
if (!mElement) {
586
return;
587
}
588
589
if (mElement->IsVideo()) {
590
// Video elements use NotifyActive().
591
return;
592
}
593
594
OnActive();
595
}
596
597
void OnInactive() {
598
MOZ_ASSERT(mElement);
599
600
if (mElement->IsPlaybackEnded()) {
601
return;
602
}
603
LOG(LogLevel::Debug, ("%p, mSrcStream %p became inactive", mElement.get(),
604
mElement->mSrcStream.get()));
605
606
mElement->PlaybackEnded();
607
}
608
609
void NotifyInactive() override {
610
if (!mElement) {
611
return;
612
}
613
614
if (!mElement->IsVideo()) {
615
// Audio elements use NotifyInaudible().
616
return;
617
}
618
619
OnInactive();
620
}
621
622
void NotifyInaudible() override {
623
if (!mElement) {
624
return;
625
}
626
627
if (mElement->IsVideo()) {
628
// Video elements use NotifyInactive().
629
return;
630
}
631
632
OnInactive();
633
}
634
635
protected:
636
const WeakPtr<HTMLMediaElement> mElement;
637
};
638
639
/**
640
* This listener observes the first video frame to arrive with a non-empty size,
641
* and renders it to its VideoFrameContainer.
642
*/
643
class HTMLMediaElement::FirstFrameListener : public VideoOutput {
644
public:
645
FirstFrameListener(VideoFrameContainer* aContainer,
646
AbstractThread* aMainThread)
647
: VideoOutput(aContainer, aMainThread) {
648
MOZ_ASSERT(NS_IsMainThread());
649
}
650
651
// NB that this overrides VideoOutput::NotifyRealtimeTrackData, so we can
652
// filter out all frames but the first one with a real size. This allows us to
653
// later re-use the logic in VideoOutput for rendering that frame.
654
void NotifyRealtimeTrackData(MediaTrackGraph* aGraph, TrackTime aTrackOffset,
655
const MediaSegment& aMedia) override {
656
MOZ_ASSERT(aMedia.GetType() == MediaSegment::VIDEO);
657
658
if (mInitialSizeFound) {
659
return;
660
}
661
662
const VideoSegment& video = static_cast<const VideoSegment&>(aMedia);
663
for (VideoSegment::ConstChunkIterator c(video); !c.IsEnded(); c.Next()) {
664
if (c->mFrame.GetIntrinsicSize() != gfx::IntSize(0, 0)) {
665
mInitialSizeFound = true;
666
667
// Pick the first frame and run it through the rendering code.
668
VideoSegment segment;
669
segment.AppendFrame(do_AddRef(c->mFrame.GetImage()),
670
c->mFrame.GetIntrinsicSize(),
671
c->mFrame.GetPrincipalHandle(),
672
c->mFrame.GetForceBlack(), c->mTimeStamp);
673
VideoOutput::NotifyRealtimeTrackData(aGraph, aTrackOffset, segment);
674
return;
675
}
676
}
677
}
678
679
private:
680
// Whether a frame with a concrete size has been received. May only be
681
// accessed on the MTG's appending thread. (this is a direct listener so we
682
// get called by whoever is producing this track's data)
683
bool mInitialSizeFound = false;
684
};
685
686
/**
687
* Helper class that manages audio and video outputs for all enabled tracks in a
688
* media element. It also manages calculating the current time when playing a
689
* MediaStream.
690
*/
691
class HTMLMediaElement::MediaStreamRenderer
692
: public DOMMediaStream::TrackListener {
693
public:
694
NS_INLINE_DECL_REFCOUNTING(MediaStreamRenderer)
695
696
MediaStreamRenderer(AbstractThread* aMainThread,
697
VideoFrameContainer* aVideoContainer,
698
void* aAudioOutputKey)
699
: mVideoContainer(aVideoContainer),
700
mAudioOutputKey(aAudioOutputKey),
701
mWatchManager(this, aMainThread) {}
702
703
void Shutdown() {
704
for (const auto& t : nsTArray<WeakPtr<MediaStreamTrack>>(mAudioTracks)) {
705
if (t) {
706
RemoveTrack(t->AsAudioStreamTrack());
707
}
708
}
709
if (mVideoTrack) {
710
RemoveTrack(mVideoTrack->AsVideoStreamTrack());
711
}
712
mWatchManager.Shutdown();
713
}
714
715
void UpdateGraphTime() {
716
mGraphTime =
717
mGraphTimeDummy->mTrack->Graph()->CurrentTime() - *mGraphTimeOffset;
718
}
719
720
void SetProgressingCurrentTime(bool aProgress) {
721
if (aProgress == mProgressingCurrentTime) {
722
return;
723
}
724
725
MOZ_DIAGNOSTIC_ASSERT(mGraphTimeDummy);
726
mProgressingCurrentTime = aProgress;
727
MediaTrackGraph* graph = mGraphTimeDummy->mTrack->Graph();
728
if (mProgressingCurrentTime) {
729
mGraphTimeOffset = Some(graph->CurrentTime().Ref() - mGraphTime);
730
mWatchManager.Watch(graph->CurrentTime(),
731
&MediaStreamRenderer::UpdateGraphTime);
732
} else {
733
mWatchManager.Unwatch(graph->CurrentTime(),
734
&MediaStreamRenderer::UpdateGraphTime);
735
}
736
}
737
738
void Start() {
739
if (mRendering) {
740
return;
741
}
742
743
mRendering = true;
744
745
if (!mGraphTimeDummy) {
746
return;
747
}
748
749
for (const auto& t : mAudioTracks) {
750
if (t) {
751
t->AsAudioStreamTrack()->AddAudioOutput(mAudioOutputKey);
752
t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey,
753
mAudioOutputVolume);
754
}
755
}
756
757
if (mVideoTrack) {
758
mVideoTrack->AsVideoStreamTrack()->AddVideoOutput(mVideoContainer);
759
}
760
}
761
762
void Stop() {
763
if (!mRendering) {
764
return;
765
}
766
767
mRendering = false;
768
769
if (!mGraphTimeDummy) {
770
return;
771
}
772
773
for (const auto& t : mAudioTracks) {
774
if (t) {
775
t->AsAudioStreamTrack()->RemoveAudioOutput(mAudioOutputKey);
776
}
777
}
778
779
if (mVideoTrack) {
780
mVideoTrack->AsVideoStreamTrack()->RemoveVideoOutput(mVideoContainer);
781
}
782
}
783
784
void SetAudioOutputVolume(float aVolume) {
785
if (mAudioOutputVolume == aVolume) {
786
return;
787
}
788
mAudioOutputVolume = aVolume;
789
if (!mRendering) {
790
return;
791
}
792
for (const auto& t : mAudioTracks) {
793
if (t) {
794
t->AsAudioStreamTrack()->SetAudioOutputVolume(mAudioOutputKey,
795
mAudioOutputVolume);
796
}
797
}
798
}
799
800
void AddTrack(AudioStreamTrack* aTrack) {
801
MOZ_DIAGNOSTIC_ASSERT(!mAudioTracks.Contains(aTrack));
802
mAudioTracks.AppendElement(aTrack);
803
EnsureGraphTimeDummy();
804
if (mRendering) {
805
aTrack->AddAudioOutput(mAudioOutputKey);
806
aTrack->SetAudioOutputVolume(mAudioOutputKey, mAudioOutputVolume);
807
}
808
}
809
void AddTrack(VideoStreamTrack* aTrack) {
810
MOZ_DIAGNOSTIC_ASSERT(!mVideoTrack);
811
if (!mVideoContainer) {
812
return;
813
}
814
mVideoTrack = aTrack;
815
EnsureGraphTimeDummy();
816
if (mRendering) {
817
aTrack->AddVideoOutput(mVideoContainer);
818
}
819
}
820
821
void RemoveTrack(AudioStreamTrack* aTrack) {
822
MOZ_DIAGNOSTIC_ASSERT(mAudioTracks.Contains(aTrack));
823
if (mRendering) {
824
aTrack->RemoveAudioOutput(mAudioOutputKey);
825
}
826
mAudioTracks.RemoveElement(aTrack);
827
}
828
void RemoveTrack(VideoStreamTrack* aTrack) {
829
MOZ_DIAGNOSTIC_ASSERT(mVideoTrack == aTrack);
830
if (!mVideoContainer) {
831
return;
832
}
833
if (mRendering) {
834
aTrack->RemoveVideoOutput(mVideoContainer);
835
}
836
mVideoTrack = nullptr;
837
}
838
839
double CurrentTime() const {
840
if (!mGraphTimeDummy) {
841
return 0.0;
842
}
843
844
return mGraphTimeDummy->mTrack->GraphImpl()->MediaTimeToSeconds(mGraphTime);
845
}
846
847
Watchable<GraphTime>& CurrentGraphTime() { return mGraphTime; }
848
849
// Set if we're rendering video.
850
const RefPtr<VideoFrameContainer> mVideoContainer;
851
852
// Set if we're rendering audio, nullptr otherwise.
853
void* const mAudioOutputKey;
854
855
private:
856
~MediaStreamRenderer() { Shutdown(); }
857
858
void EnsureGraphTimeDummy() {
859
if (mGraphTimeDummy) {
860
return;
861
}
862
863
MediaTrackGraph* graph = nullptr;
864
for (const auto& t : mAudioTracks) {
865
if (t && !t->Ended()) {
866
graph = t->Graph();
867
break;
868
}
869
}
870
871
if (!graph && mVideoTrack && !mVideoTrack->Ended()) {
872
graph = mVideoTrack->Graph();
873
}
874
875
if (!graph) {
876
return;
877
}
878
879
// This dummy keeps `graph` alive and ensures access to it.
880
mGraphTimeDummy = MakeRefPtr<SharedDummyTrack>(
881
graph->CreateSourceTrack(MediaSegment::AUDIO));
882
}
883
884
// True when all tracks are being rendered, i.e., when the media element is
885
// playing.
886
bool mRendering = false;
887
888
// True while we're progressing mGraphTime. False otherwise.
889
bool mProgressingCurrentTime = false;
890
891
// The audio output volume for all audio tracks.
892
float mAudioOutputVolume = 1.0f;
893
894
// WatchManager for mGraphTime.
895
WatchManager<MediaStreamRenderer> mWatchManager;
896
897
// A dummy MediaTrack to guarantee a MediaTrackGraph is kept alive while
898
// we're actively rendering, so we can track the graph's current time. Set
899
// when the first track is added, never unset.
900
RefPtr<SharedDummyTrack> mGraphTimeDummy;
901
902
// Watchable that relays the graph's currentTime updates to the media element
903
// only while we're rendering. This is the current time of the rendering in
904
// GraphTime units.
905
Watchable<GraphTime> mGraphTime = {0, "MediaStreamRenderer::mGraphTime"};
906
907
// Nothing until a track has been added. Then, the current GraphTime at the
908
// time when we were last Start()ed.
909
Maybe<GraphTime> mGraphTimeOffset;
910
911
// Currently enabled (and rendered) audio tracks.
912
nsTArray<WeakPtr<MediaStreamTrack>> mAudioTracks;
913
914
// Currently selected (and rendered) video track.
915
WeakPtr<MediaStreamTrack> mVideoTrack;
916
};
917
918
class HTMLMediaElement::MediaElementTrackSource
919
: public MediaStreamTrackSource,
920
public MediaStreamTrackSource::Sink,
921
public MediaStreamTrackConsumer {
922
public:
923
NS_DECL_ISUPPORTS_INHERITED
924
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaElementTrackSource,
925
MediaStreamTrackSource)
926
927
/* MediaDecoder track source */
928
MediaElementTrackSource(nsISerialEventTarget* aMainThreadEventTarget,
929
ProcessedMediaTrack* aTrack, nsIPrincipal* aPrincipal,
930
OutputMuteState aMuteState)
931
: MediaStreamTrackSource(aPrincipal, nsString()),
932
mMainThreadEventTarget(aMainThreadEventTarget),
933
mTrack(aTrack),
934
mIntendedElementMuteState(aMuteState),
935
mElementMuteState(aMuteState) {
936
MOZ_ASSERT(mTrack);
937
}
938
939
/* MediaStream track source */
940
MediaElementTrackSource(nsISerialEventTarget* aMainThreadEventTarget,
941
MediaStreamTrack* aCapturedTrack,
942
MediaStreamTrackSource* aCapturedTrackSource,
943
ProcessedMediaTrack* aTrack, MediaInputPort* aPort,
944
OutputMuteState aMuteState)
945
: MediaStreamTrackSource(aCapturedTrackSource->GetPrincipal(),
946
nsString()),
947
mMainThreadEventTarget(aMainThreadEventTarget),
948
mCapturedTrack(aCapturedTrack),
949
mCapturedTrackSource(aCapturedTrackSource),
950
mTrack(aTrack),
951
mPort(aPort),
952
mIntendedElementMuteState(aMuteState),
953
mElementMuteState(aMuteState) {
954
MOZ_ASSERT(mTrack);
955
MOZ_ASSERT(mCapturedTrack);
956
MOZ_ASSERT(mCapturedTrackSource);
957
MOZ_ASSERT(mPort);
958
959
mCapturedTrack->AddConsumer(this);
960
mCapturedTrackSource->RegisterSink(this);
961
}
962
963
void SetEnabled(bool aEnabled) {
964
if (!mTrack) {
965
return;
966
}
967
mTrack->SetEnabled(aEnabled ? DisabledTrackMode::ENABLED
968
: DisabledTrackMode::SILENCE_FREEZE);
969
}
970
971
void SetPrincipal(RefPtr<nsIPrincipal> aPrincipal) {
972
mPrincipal = std::move(aPrincipal);
973
MediaStreamTrackSource::PrincipalChanged();
974
}
975
976
void SetMutedByElement(OutputMuteState aMuteState) {
977
if (mIntendedElementMuteState == aMuteState) {
978
return;
979
}
980
mIntendedElementMuteState = aMuteState;
981
mMainThreadEventTarget->Dispatch(NS_NewRunnableFunction(
982
"MediaElementTrackSource::SetMutedByElement",
983
[self = RefPtr<MediaElementTrackSource>(this), this, aMuteState] {
984
mElementMuteState = aMuteState;
985
MediaStreamTrackSource::MutedChanged(Muted());
986
}));
987
}
988
989
void Destroy() override {
990
if (mCapturedTrack) {
991
mCapturedTrack->RemoveConsumer(this);
992
mCapturedTrack = nullptr;
993
}
994
if (mCapturedTrackSource) {
995
mCapturedTrackSource->UnregisterSink(this);
996
mCapturedTrackSource = nullptr;
997
}
998
if (mTrack && !mTrack->IsDestroyed()) {
999
mTrack->Destroy();
1000
}
1001
if (mPort) {
1002
mPort->Destroy();
1003
mPort = nullptr;
1004
}
1005
}
1006
1007
MediaSourceEnum GetMediaSource() const override {
1008
return MediaSourceEnum::Other;
1009
}
1010
1011
void Stop() override {
1012
// Do nothing. There may appear new output streams
1013
// that need tracks sourced from this source, so we
1014
// cannot destroy things yet.
1015
}
1016
1017
/**
1018
* Do not keep the track source alive. The source lifetime is controlled by
1019
* its associated tracks.
1020
*/
1021
bool KeepsSourceAlive() const override { return false; }
1022
1023
/**
1024
* Do not keep the track source on. It is controlled by its associated tracks.
1025
*/
1026
bool Enabled() const override { return false; }
1027
1028
void Disable() override {}
1029
1030
void Enable() override {}
1031
1032
void PrincipalChanged() override {
1033
if (!mCapturedTrackSource) {
1034
// This could happen during shutdown.
1035
return;
1036
}
1037
1038
SetPrincipal(mCapturedTrackSource->GetPrincipal());
1039
}
1040
1041
void MutedChanged(bool aNewState) override {
1042
MediaStreamTrackSource::MutedChanged(Muted());
1043
}
1044
1045
void OverrideEnded() override {
1046
Destroy();
1047
MediaStreamTrackSource::OverrideEnded();
1048
}
1049
1050
void NotifyEnabledChanged(MediaStreamTrack* aTrack, bool aEnabled) override {
1051
MediaStreamTrackSource::MutedChanged(Muted());
1052
}
1053
1054
bool Muted() const {
1055
return mElementMuteState == OutputMuteState::Muted ||
1056
(mCapturedTrack &&
1057
(mCapturedTrack->Muted() || !mCapturedTrack->Enabled()));
1058
}
1059
1060
ProcessedMediaTrack* Track() const { return mTrack; }
1061
1062
private:
1063
virtual ~MediaElementTrackSource() { Destroy(); };
1064
1065
const RefPtr<nsISerialEventTarget> mMainThreadEventTarget;
1066
RefPtr<MediaStreamTrack> mCapturedTrack;
1067
RefPtr<MediaStreamTrackSource> mCapturedTrackSource;
1068
const RefPtr<ProcessedMediaTrack> mTrack;
1069
RefPtr<MediaInputPort> mPort;
1070
// The mute state as intended by the media element.
1071
OutputMuteState mIntendedElementMuteState;
1072
// The mute state as applied to this track source. It is applied async, so
1073
// needs to be tracked separately from the intended state.
1074
OutputMuteState mElementMuteState;
1075
};
1076
1077
HTMLMediaElement::OutputMediaStream::OutputMediaStream(
1078
RefPtr<DOMMediaStream> aStream, bool aCapturingAudioOnly,
1079
bool aFinishWhenEnded)
1080
: mStream(std::move(aStream)),
1081
mCapturingAudioOnly(aCapturingAudioOnly),
1082
mFinishWhenEnded(aFinishWhenEnded) {}
1083
HTMLMediaElement::OutputMediaStream::~OutputMediaStream() = default;
1084
1085
void ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback,
1086
HTMLMediaElement::OutputMediaStream& aField,
1087
const char* aName, uint32_t aFlags) {
1088
ImplCycleCollectionTraverse(aCallback, aField.mStream, "mStream", aFlags);
1089
ImplCycleCollectionTraverse(aCallback, aField.mLiveTracks, "mLiveTracks",
1090
aFlags);
1091
ImplCycleCollectionTraverse(aCallback, aField.mFinishWhenEndedLoadingSrc,
1092
"mFinishWhenEndedLoadingSrc", aFlags);
1093
ImplCycleCollectionTraverse(aCallback, aField.mFinishWhenEndedAttrStream,
1094
"mFinishWhenEndedAttrStream", aFlags);
1095
}
1096
1097
void ImplCycleCollectionUnlink(HTMLMediaElement::OutputMediaStream& aField) {
1098
ImplCycleCollectionUnlink(aField.mStream);
1099
ImplCycleCollectionUnlink(aField.mLiveTracks);
1100
ImplCycleCollectionUnlink(aField.mFinishWhenEndedLoadingSrc);
1101
ImplCycleCollectionUnlink(aField.mFinishWhenEndedAttrStream);
1102
}
1103
1104
NS_IMPL_ADDREF_INHERITED(HTMLMediaElement::MediaElementTrackSource,
1105
MediaStreamTrackSource)
1106
NS_IMPL_RELEASE_INHERITED(HTMLMediaElement::MediaElementTrackSource,
1107
MediaStreamTrackSource)
1108
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
1109
HTMLMediaElement::MediaElementTrackSource)
1110
NS_INTERFACE_MAP_END_INHERITING(MediaStreamTrackSource)
1111
NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::MediaElementTrackSource)
1112
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(
1113
HTMLMediaElement::MediaElementTrackSource, MediaStreamTrackSource)
1114
tmp->Destroy();
1115
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCapturedTrack)
1116
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCapturedTrackSource)
1117
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1118
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(
1119
HTMLMediaElement::MediaElementTrackSource, MediaStreamTrackSource)
1120
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCapturedTrack)
1121
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCapturedTrackSource)
1122
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1123
1124
/**
1125
* There is a reference cycle involving this class: MediaLoadListener
1126
* holds a reference to the HTMLMediaElement, which holds a reference
1127
* to an nsIChannel, which holds a reference to this listener.
1128
* We break the reference cycle in OnStartRequest by clearing mElement.
1129
*/
1130
class HTMLMediaElement::MediaLoadListener final
1131
: public nsIStreamListener,
1132
public nsIChannelEventSink,
1133
public nsIInterfaceRequestor,
1134
public nsIObserver,
1135
public nsIThreadRetargetableStreamListener {
1136
~MediaLoadListener() = default;
1137
1138
NS_DECL_THREADSAFE_ISUPPORTS
1139
NS_DECL_NSIREQUESTOBSERVER
1140
NS_DECL_NSISTREAMLISTENER
1141
NS_DECL_NSICHANNELEVENTSINK
1142
NS_DECL_NSIOBSERVER
1143
NS_DECL_NSIINTERFACEREQUESTOR
1144
NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
1145
1146
public:
1147
explicit MediaLoadListener(HTMLMediaElement* aElement)
1148
: mElement(aElement), mLoadID(aElement->GetCurrentLoadID()) {
1149
MOZ_ASSERT(mElement, "Must pass an element to call back");
1150
}
1151
1152
private:
1153
RefPtr<HTMLMediaElement> mElement;
1154
nsCOMPtr<nsIStreamListener> mNextListener;
1155
const uint32_t mLoadID;
1156
};
1157
1158
NS_IMPL_ISUPPORTS(HTMLMediaElement::MediaLoadListener, nsIRequestObserver,
1159
nsIStreamListener, nsIChannelEventSink, nsIInterfaceRequestor,
1160
nsIObserver, nsIThreadRetargetableStreamListener)
1161
1162
NS_IMETHODIMP
1163
HTMLMediaElement::MediaLoadListener::Observe(nsISupports* aSubject,
1164
const char* aTopic,
1165
const char16_t* aData) {
1166
nsContentUtils::UnregisterShutdownObserver(this);
1167
1168
// Clear mElement to break cycle so we don't leak on shutdown
1169
mElement = nullptr;
1170
return NS_OK;
1171
}
1172
1173
NS_IMETHODIMP
1174
HTMLMediaElement::MediaLoadListener::OnStartRequest(nsIRequest* aRequest) {
1175
nsContentUtils::UnregisterShutdownObserver(this);
1176
1177
if (!mElement) {
1178
// We've been notified by the shutdown observer, and are shutting down.
1179
return NS_BINDING_ABORTED;
1180
}
1181
1182
// Media element playback is not currently supported when recording or
1183
// replaying. See bug 1304146.
1184
if (recordreplay::IsRecordingOrReplaying()) {
1185
mElement->ReportLoadError("Media elements not available when recording");
1186
return NS_ERROR_NOT_AVAILABLE;
1187
}
1188
1189
// The element is only needed until we've had a chance to call
1190
// InitializeDecoderForChannel. So make sure mElement is cleared here.
1191
RefPtr<HTMLMediaElement> element;
1192
element.swap(mElement);
1193
1194
AbstractThread::AutoEnter context(element->AbstractMainThread());
1195
1196
if (mLoadID != element->GetCurrentLoadID()) {
1197
// The channel has been cancelled before we had a chance to create
1198
// a decoder. Abort, don't dispatch an "error" event, as the new load
1199
// may not be in an error state.
1200
return NS_BINDING_ABORTED;
1201
}
1202
1203
// Don't continue to load if the request failed or has been canceled.
1204
nsresult status;
1205
nsresult rv = aRequest->GetStatus(&status);
1206
NS_ENSURE_SUCCESS(rv, rv);
1207
if (NS_FAILED(status)) {
1208
if (element) {
1209
// Handle media not loading error because source was a tracking URL (or
1210
// fingerprinting, cryptomining, etc).
1211
// We make a note of this media node by including it in a dedicated
1212
// array of blocked tracking nodes under its parent document.
1213
if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode(
1214
status)) {
1215
element->OwnerDoc()->AddBlockedNodeByClassifier(element);
1216
}
1217
element->NotifyLoadError(
1218
nsPrintfCString("%u: %s", uint32_t(status), "Request failed"));
1219
}
1220
return status;
1221
}
1222
1223
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(aRequest);
1224
bool succeeded;
1225
if (hc && NS_SUCCEEDED(hc->GetRequestSucceeded(&succeeded)) && !succeeded) {
1226
uint32_t responseStatus = 0;
1227
Unused << hc->GetResponseStatus(&responseStatus);
1228
nsAutoCString statusText;
1229
Unused << hc->GetResponseStatusText(statusText);
1230
element->NotifyLoadError(
1231
nsPrintfCString("%u: %s", responseStatus, statusText.get()));
1232
1233
nsAutoString code;
1234
code.AppendInt(responseStatus);
1235
nsAutoString src;
1236
element->GetCurrentSrc(src);
1237
AutoTArray<nsString, 2> params = {code, src};
1238
element->ReportLoadError("MediaLoadHttpError", params);
1239
return NS_BINDING_ABORTED;
1240
}
1241
1242
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
1243
if (channel &&
1244
NS_SUCCEEDED(rv = element->InitializeDecoderForChannel(
1245
channel, getter_AddRefs(mNextListener))) &&
1246
mNextListener) {
1247
rv = mNextListener->OnStartRequest(aRequest);
1248
} else {
1249
// If InitializeDecoderForChannel() returned an error, fire a network error.
1250
if (NS_FAILED(rv) && !mNextListener) {
1251
// Load failed, attempt to load the next candidate resource. If there
1252
// are none, this will trigger a MEDIA_ERR_SRC_NOT_SUPPORTED error.
1253
element->NotifyLoadError(NS_LITERAL_CSTRING("Failed to init decoder"));
1254
}
1255
// If InitializeDecoderForChannel did not return a listener (but may
1256
// have otherwise succeeded), we abort the connection since we aren't
1257
// interested in keeping the channel alive ourselves.
1258
rv = NS_BINDING_ABORTED;
1259
}
1260
1261
return rv;
1262
}
1263
1264
NS_IMETHODIMP
1265
HTMLMediaElement::MediaLoadListener::OnStopRequest(nsIRequest* aRequest,
1266
nsresult aStatus) {
1267
if (mNextListener) {
1268
return mNextListener->OnStopRequest(aRequest, aStatus);
1269
}
1270
return NS_OK;
1271
}
1272
1273
NS_IMETHODIMP
1274
HTMLMediaElement::MediaLoadListener::OnDataAvailable(nsIRequest* aRequest,
1275
nsIInputStream* aStream,
1276
uint64_t aOffset,
1277
uint32_t aCount) {
1278
if (!mNextListener) {
1279
NS_ERROR(
1280
"Must have a chained listener; OnStartRequest should have "
1281
"canceled this request");
1282
return NS_BINDING_ABORTED;
1283
}
1284
return mNextListener->OnDataAvailable(aRequest, aStream, aOffset, aCount);
1285
}
1286
1287
NS_IMETHODIMP
1288
HTMLMediaElement::MediaLoadListener::AsyncOnChannelRedirect(
1289
nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aFlags,
1290
nsIAsyncVerifyRedirectCallback* cb) {
1291
// TODO is this really correct?? See bug #579329.
1292
if (mElement) {
1293
mElement->OnChannelRedirect(aOldChannel, aNewChannel, aFlags);
1294
}
1295
nsCOMPtr<nsIChannelEventSink> sink = do_QueryInterface(mNextListener);
1296
if (sink) {
1297
return sink->AsyncOnChannelRedirect(aOldChannel, aNewChannel, aFlags, cb);
1298
}
1299
cb->OnRedirectVerifyCallback(NS_OK);
1300
return NS_OK;
1301
}
1302
1303
NS_IMETHODIMP
1304
HTMLMediaElement::MediaLoadListener::CheckListenerChain() {
1305
MOZ_ASSERT(mNextListener);
1306
nsCOMPtr<nsIThreadRetargetableStreamListener> retargetable =
1307
do_QueryInterface(mNextListener);
1308
if (retargetable) {
1309
return retargetable->CheckListenerChain();
1310
}
1311
return NS_ERROR_NO_INTERFACE;
1312
}
1313
1314
NS_IMETHODIMP
1315
HTMLMediaElement::MediaLoadListener::GetInterface(const nsIID& aIID,
1316
void** aResult) {
1317
return QueryInterface(aIID, aResult);
1318
}
1319
1320
void HTMLMediaElement::ReportLoadError(const char* aMsg,
1321
const nsTArray<nsString>& aParams) {
1322
ReportToConsole(nsIScriptError::warningFlag, aMsg, aParams);
1323
}
1324
1325
void HTMLMediaElement::ReportToConsole(
1326
uint32_t aErrorFlags, const char* aMsg,
1327
const nsTArray<nsString>& aParams) const {
1328
nsContentUtils::ReportToConsole(aErrorFlags, NS_LITERAL_CSTRING("Media"),
1329
OwnerDoc(), nsContentUtils::eDOM_PROPERTIES,
1330
aMsg, aParams);
1331
}
1332
1333
class HTMLMediaElement::AudioChannelAgentCallback final
1334
: public nsIAudioChannelAgentCallback {
1335
public:
1336
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
1337
NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgentCallback)
1338
1339
explicit AudioChannelAgentCallback(HTMLMediaElement* aOwner)
1340
: mOwner(aOwner),
1341
mAudioChannelVolume(1.0),
1342
mPlayingThroughTheAudioChannel(false),
1343
mSuspended(nsISuspendedTypes::NONE_SUSPENDED),
1344
mIsOwnerAudible(IsOwnerAudible()),
1345
mIsShutDown(false) {
1346
MOZ_ASSERT(mOwner);
1347
MaybeCreateAudioChannelAgent();
1348
}
1349
1350
void UpdateAudioChannelPlayingState() {
1351
MOZ_ASSERT(!mIsShutDown);
1352
bool playingThroughTheAudioChannel = IsPlayingThroughTheAudioChannel();
1353
1354
if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
1355
if (!MaybeCreateAudioChannelAgent()) {
1356
return;
1357
}
1358
1359
mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
1360
if (mPlayingThroughTheAudioChannel) {
1361
StartAudioChannelAgent();
1362
} else {
1363
StopAudioChanelAgent();
1364
}
1365
}
1366
}
1367
1368
bool ShouldResetSuspend() const {
1369
// The disposable-pause should be clear after media starts playing.
1370
return !mOwner->Paused() &&
1371
mSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE;
1372
}
1373
1374
void NotifyPlayStateChanged() {
1375
MOZ_ASSERT(!mIsShutDown);
1376
if (ShouldResetSuspend()) {
1377
SetSuspended(nsISuspendedTypes::NONE_SUSPENDED);
1378
NotifyAudioPlaybackChanged(
1379
AudioChannelService::AudibleChangedReasons::ePauseStateChanged);
1380
}
1381
UpdateAudioChannelPlayingState();
1382
}
1383
1384
NS_IMETHODIMP WindowVolumeChanged(float aVolume, bool aMuted) override {
1385
MOZ_ASSERT(mAudioChannelAgent);
1386
1387
MOZ_LOG(
1388
AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1389
("HTMLMediaElement::AudioChannelAgentCallback, WindowVolumeChanged, "
1390
"this = %p, aVolume = %f, aMuted = %s\n",
1391
this, aVolume, aMuted ? "true" : "false"));
1392
1393
if (mAudioChannelVolume != aVolume) {
1394
mAudioChannelVolume = aVolume;
1395
mOwner->SetVolumeInternal();
1396
}
1397
1398
const uint32_t muted = mOwner->mMuted;
1399
if (aMuted && !mOwner->ComputedMuted()) {
1400
mOwner->SetMutedInternal(muted | MUTED_BY_AUDIO_CHANNEL);
1401
} else if (!aMuted && mOwner->ComputedMuted()) {
1402
mOwner->SetMutedInternal(muted & ~MUTED_BY_AUDIO_CHANNEL);
1403
}
1404
1405
return NS_OK;
1406
}
1407
1408
NS_IMETHODIMP WindowSuspendChanged(SuspendTypes aSuspend) override {
1409
MOZ_ASSERT(mAudioChannelAgent);
1410
1411
MOZ_LOG(
1412
AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1413
("HTMLMediaElement::AudioChannelAgentCallback, WindowSuspendChanged, "
1414
"this = %p, aSuspend = %s\n",
1415
this, SuspendTypeToStr(aSuspend)));
1416
1417
switch (aSuspend) {
1418
case nsISuspendedTypes::NONE_SUSPENDED:
1419
Resume();
1420
break;
1421
case nsISuspendedTypes::SUSPENDED_PAUSE:
1422
case nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE:
1423
Suspend(aSuspend);
1424
break;
1425
case nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE:
1426
Stop();
1427
break;
1428
default:
1429
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1430
("HTMLMediaElement::AudioChannelAgentCallback, "
1431
"WindowSuspendChanged, "
1432
"this = %p, Error : unknown suspended type!\n",
1433
this));
1434
}
1435
return NS_OK;
1436
}
1437
1438
NS_IMETHODIMP WindowAudioCaptureChanged(bool aCapture) override {
1439
MOZ_ASSERT(mAudioChannelAgent);
1440
AudioCaptureTrackChangeIfNeeded();
1441
return NS_OK;
1442
}
1443
1444
void AudioCaptureTrackChangeIfNeeded() {
1445
MOZ_ASSERT(!mIsShutDown);
1446
if (!IsPlayingStarted()) {
1447
return;
1448
}
1449
1450
MOZ_ASSERT(mAudioChannelAgent);
1451
bool isCapturing = mAudioChannelAgent->IsWindowAudioCapturingEnabled();
1452
mOwner->AudioCaptureTrackChange(isCapturing);
1453
}
1454
1455
void NotifyAudioPlaybackChanged(AudibleChangedReasons aReason) {
1456
MOZ_ASSERT(!mIsShutDown);
1457
if (!IsPlayingStarted()) {
1458
return;
1459
}
1460
1461
AudibleState newAudibleState = IsOwnerAudible();
1462
if (mIsOwnerAudible == newAudibleState) {
1463
return;
1464
}
1465
1466
mIsOwnerAudible = newAudibleState;
1467
mAudioChannelAgent->NotifyStartedAudible(mIsOwnerAudible, aReason);
1468
}
1469
1470
void Shutdown() {
1471
MOZ_ASSERT(!mIsShutDown);
1472
if (mAudioChannelAgent && mAudioChannelAgent->IsPlayingStarted()) {
1473
StopAudioChanelAgent();
1474
}
1475
mAudioChannelAgent = nullptr;
1476
mIsShutDown = true;
1477
}
1478
1479
float GetEffectiveVolume() const {
1480
MOZ_ASSERT(!mIsShutDown);
1481
return mOwner->Volume() * mAudioChannelVolume;
1482
}
1483
1484
SuspendTypes GetSuspendType() const {
1485
MOZ_ASSERT(!mIsShutDown);
1486
return mSuspended;
1487
}
1488
1489
private:
1490
~AudioChannelAgentCallback() { MOZ_ASSERT(mIsShutDown); };
1491
1492
bool MaybeCreateAudioChannelAgent() {
1493
if (mAudioChannelAgent) {
1494
return true;
1495
}
1496
1497
mAudioChannelAgent = new AudioChannelAgent();
1498
nsresult rv =
1499
mAudioChannelAgent->Init(mOwner->OwnerDoc()->GetInnerWindow(), this);
1500
if (NS_WARN_IF(NS_FAILED(rv))) {
1501
mAudioChannelAgent = nullptr;
1502
MOZ_LOG(
1503
AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1504
("HTMLMediaElement::AudioChannelAgentCallback, Fail to initialize "
1505
"the audio channel agent, this = %p\n",
1506
this));
1507
return false;
1508
}
1509
1510
return true;
1511
}
1512
1513
void StartAudioChannelAgent() {
1514
MOZ_ASSERT(mAudioChannelAgent);
1515
MOZ_ASSERT(!mAudioChannelAgent->IsPlayingStarted());
1516
if (NS_WARN_IF(NS_FAILED(
1517
mAudioChannelAgent->NotifyStartedPlaying(IsOwnerAudible())))) {
1518
return;
1519
}
1520
mAudioChannelAgent->PullInitialUpdate();
1521
}
1522
1523
void StopAudioChanelAgent() {
1524
MOZ_ASSERT(mAudioChannelAgent);
1525
MOZ_ASSERT(mAudioChannelAgent->IsPlayingStarted());
1526
mAudioChannelAgent->NotifyStoppedPlaying();
1527
// If we have started audio capturing before, we have to tell media element
1528
// to clear the output capturing track.
1529
mOwner->AudioCaptureTrackChange(false);
1530
}
1531
1532
void SetSuspended(SuspendTypes aSuspend) {
1533
if (mSuspended == aSuspend) {
1534
return;
1535
}
1536
1537
MaybeNotifyMediaResumed(aSuspend);
1538
mSuspended = aSuspend;
1539
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1540
("HTMLMediaElement::AudioChannelAgentCallback, "
1541
"SetAudioChannelSuspended, "
1542
"this = %p, aSuspend = %s\n",
1543
this, SuspendTypeToStr(aSuspend)));
1544
}
1545
1546
void Resume() {
1547
if (!IsSuspended()) {
1548
MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
1549
("HTMLMediaElement::AudioChannelAgentCallback, "
1550
"ResumeFromAudioChannel, "
1551
"this = %p, don't need to be resumed!\n",
1552
this));
1553
return;
1554
}
1555
1556
SetSuspended(nsISuspendedTypes::NONE_SUSPENDED);
1557
IgnoredErrorResult rv;
1558
RefPtr<Promise> toBeIgnored = mOwner->Play(rv);
1559
MOZ_ASSERT_IF(
1560
toBeIgnored && toBeIgnored->State() == Promise::PromiseState::Rejected,
1561
rv.Failed());
1562
if (rv.Failed()) {
1563
NS_WARNING("Not able to resume from AudioChannel.");
1564
}
1565
1566
NotifyAudioPlaybackChanged(
1567
AudioChannelService::AudibleChangedReasons::ePauseStateChanged);
1568
}
1569
1570
void Suspend(SuspendTypes aSuspend) {
1571
if (IsSuspended()) {
1572
return;
1573
}
1574
1575
SetSuspended(aSuspend);
1576
if (aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE ||
1577
aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE) {
1578
IgnoredErrorResult rv;
1579
mOwner->Pause(rv);
1580
if (NS_WARN_IF(rv.Failed())) {
1581
return;
1582
}
1583
}
1584
NotifyAudioPlaybackChanged(
1585
AudioChannelService::AudibleChangedReasons::ePauseStateChanged);
1586
}
1587
1588
void Stop() {
1589
SetSuspended(nsISuspendedTypes::NONE_SUSPENDED);
1590
mOwner->Pause();
1591
}
1592
1593
bool IsPlayingStarted() {
1594
if (MaybeCreateAudioChannelAgent()) {
1595
return mAudioChannelAgent->IsPlayingStarted();
1596
}
1597
return false;
1598
}
1599
1600
void MaybeNotifyMediaResumed(SuspendTypes aSuspend) {
1601
// In fennec, we should send the notification when media is resumed from the
1602
// pause-disposable which was called by media control.
1603
if (mSuspended != nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE &&
1604
aSuspend != nsISuspendedTypes::NONE_SUSPENDED) {
1605
return;
1606
}
1607
1608
if (!IsPlayingStarted()) {
1609
return;
1610
}
1611
1612
uint64_t windowID = mAudioChannelAgent->WindowID();
1613
mOwner->MainThreadEventTarget()->Dispatch(NS_NewRunnableFunction(
1614
"dom::HTMLMediaElement::AudioChannelAgentCallback::"
1615
"MaybeNotifyMediaResumed",
1616
[windowID]() -> void {
1617
nsCOMPtr<nsIObserverService> observerService =
1618
services::GetObserverService();
1619
if (NS_WARN_IF(!observerService)) {
1620
return;
1621
}
1622
1623
nsCOMPtr<nsISupportsPRUint64> wrapper =
1624
do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID);
1625
if (NS_WARN_IF(!wrapper)) {
1626
return;
1627
}
1628
1629
wrapper->SetData(windowID);
1630
observerService->NotifyObservers(wrapper, "media-playback-resumed",
1631
u"active");
1632
}));
1633
}
1634
1635
bool IsSuspended() const {
1636
return (mSuspended == nsISuspendedTypes::SUSPENDED_PAUSE ||
1637
mSuspended == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE);
1638
}
1639
1640
AudibleState IsOwnerAudible() const {
1641
// Suspended or paused media doesn't produce any sound.
1642
if (mSuspended != nsISuspendedTypes::NONE_SUSPENDED || mOwner->mPaused) {
1643
return AudibleState::eNotAudible;
1644
}
1645
return mOwner->IsAudible() ? AudibleState::eAudible
1646
: AudibleState::eNotAudible;
1647
}
1648
1649
bool IsPlayingThroughTheAudioChannel() const {
1650
// If we have an error, we are not playing.
1651
if (mOwner->GetError()) {
1652
return false;
1653
}
1654
1655
// We should consider any bfcached page or inactive document as non-playing.
1656
if (!mOwner->IsActive()) {
1657
return false;
1658
}
1659
1660
// It might be resumed from remote, we should keep the audio channel agent.
1661
if (IsSuspended()) {
1662
return true;
1663
}
1664
1665
// Are we paused
1666
if (mOwner->mPaused) {
1667
return false;
1668
}
1669
1670
// No audio track
1671
if (!mOwner->HasAudio()) {
1672
return false;
1673
}
1674
1675
// A loop always is playing
1676
if (mOwner->HasAttr(kNameSpaceID_None, nsGkAtoms::loop)) {
1677
return true;
1678
}
1679
1680
// If we are actually playing...
1681
if (mOwner->IsCurrentlyPlaying()) {
1682
return true;
1683
}
1684
1685
// If we are playing an external stream.
1686
if (mOwner->mSrcAttrStream) {
1687
return true;
1688
}
1689
1690
return false;
1691
}
1692
1693
RefPtr<AudioChannelAgent> mAudioChannelAgent;
1694
HTMLMediaElement* mOwner;
1695
1696
// The audio channel volume
1697
float mAudioChannelVolume;
1698
// Is this media element playing?
1699
bool mPlayingThroughTheAudioChannel;
1700
// We have different kinds of suspended cases,
1701
// - SUSPENDED_PAUSE
1702
// It's used when we temporary lost platform audio focus. MediaElement can
1703
// only be resumed when we gain the audio focus again.
1704
// - SUSPENDED_PAUSE_DISPOSABLE
1705
// It's used when user press the pause button on the remote media-control.
1706
// MediaElement can be resumed by remote media-control or via play().
1707
// - SUSPENDED_STOP_DISPOSABLE
1708
// When we permanently lost platform audio focus, we should stop playing
1709
// and stop the audio channel agent. MediaElement can only be restarted by
1710
// play().
1711
SuspendTypes mSuspended;
1712
// Indicate whether media element is audible for users.
1713
AudibleState mIsOwnerAudible;
1714
bool mIsShutDown;
1715
};
1716
1717
NS_IMPL_CYCLE_COLLECTION_CLASS(HTMLMediaElement::AudioChannelAgentCallback)
1718
1719
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
1720
HTMLMediaElement::AudioChannelAgentCallback)
1721
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAudioChannelAgent)
1722
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1723
1724
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(
1725
HTMLMediaElement::AudioChannelAgentCallback)
1726
NS_IMPL_CYCLE_COLLECTION_UNLINK(mAudioChannelAgent)
1727
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1728
1729
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
1730
HTMLMediaElement::AudioChannelAgentCallback)
1731
NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
1732
NS_INTERFACE_MAP_END
1733
1734
NS_IMPL_CYCLE_COLLECTING_ADDREF(HTMLMediaElement::AudioChannelAgentCallback)
1735
NS_IMPL_CYCLE_COLLECTING_RELEASE(HTMLMediaElement::AudioChannelAgentCallback)
1736
1737
class HTMLMediaElement::ChannelLoader final {
1738
public:
1739
NS_INLINE_DECL_REFCOUNTING(ChannelLoader);
1740
1741
void LoadInternal(HTMLMediaElement* aElement) {
1742
if (mCancelled) {
1743
return;
1744
}
1745
1746
// determine what security checks need to be performed in AsyncOpen().
1747
nsSecurityFlags securityFlags =
1748
aElement->ShouldCheckAllowOrigin()
1749
? nsILoadInfo::SEC_REQUIRE_CORS_DATA_INHERITS
1750
: nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
1751
1752
if (aElement->GetCORSMode() == CORS_USE_CREDENTIALS) {
1753
securityFlags |= nsILoadInfo::SEC_COOKIES_INCLUDE;
1754
}
1755
1756
MOZ_ASSERT(
1757
aElement->IsAnyOfHTMLElements(nsGkAtoms::audio, nsGkAtoms::video));
1758
nsContentPolicyType contentPolicyType =
1759
aElement->IsHTMLElement(nsGkAtoms::audio)
1760
? nsIContentPolicy::TYPE_INTERNAL_AUDIO
1761
: nsIContentPolicy::TYPE_INTERNAL_VIDEO;
1762
1763
// If aElement has 'triggeringprincipal' attribute, we will use the value as
1764
// triggeringPrincipal for the channel, otherwise it will default to use
1765
// aElement->NodePrincipal().
1766
// This function returns true when aElement has 'triggeringprincipal', so if
1767
// setAttrs is true we will override the origin attributes on the channel
1768
// later.
1769
nsCOMPtr<nsIPrincipal> triggeringPrincipal;
1770
bool setAttrs = nsContentUtils::QueryTriggeringPrincipal(
1771
aElement, aElement->mLoadingSrcTriggeringPrincipal,
1772
getter_AddRefs(triggeringPrincipal));
1773
1774
nsCOMPtr<nsILoadGroup> loadGroup = aElement->GetDocumentLoadGroup();
1775
nsCOMPtr<nsIChannel> channel;
1776
nsresult rv = NS_NewChannelWithTriggeringPrincipal(
1777
getter_AddRefs(channel), aElement->mLoadingSrc,
1778
static_cast<Element*>(aElement), triggeringPrincipal, securityFlags,
1779
contentPolicyType,
1780
nullptr, // aPerformanceStorage
1781
loadGroup,
1782
nullptr, // aCallbacks
1783
nsICachingChannel::LOAD_BYPASS_LOCAL_CACHE_IF_BUSY |
1784
nsIChannel::LOAD_MEDIA_SNIFFER_OVERRIDES_CONTENT_TYPE |
1785
nsIChannel::LOAD_CALL_CONTENT_SNIFFERS);
1786
1787
if (NS_FAILED(rv)) {
1788
// Notify load error so the element will try next resource candidate.
1789
aElement->NotifyLoadError(NS_LITERAL_CSTRING("Fail to create channel"));
1790
return;
1791
}
1792
1793
if (setAttrs) {
1794
nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo();
1795
// The function simply returns NS_OK, so we ignore the return value.
1796
Unused << loadInfo->SetOriginAttributes(
1797
triggeringPrincipal->OriginAttributesRef());
1798
}
1799
1800
nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel));
1801
if (cos) {
1802
if (aElement->mUseUrgentStartForChannel) {
1803
cos->AddClassFlags(nsIClassOfService::UrgentStart);
1804
1805
// Reset the flag to avoid loading again without initiated by user
1806
// interaction.
1807
aElement->mUseUrgentStartForChannel = false;
1808
}
1809
1810
// Unconditionally disable throttling since we want the media to fluently
1811
// play even when we switch the tab to background.
1812
cos->AddClassFlags(nsIClassOfService::DontThrottle);
1813
}
1814
1815
// The listener holds a strong reference to us. This creates a
1816
// reference cycle, once we've set mChannel, which is manually broken
1817
// in the listener's OnStartRequest method after it is finished with
1818
// the element. The cycle will also be broken if we get a shutdown
1819
// notification before OnStartRequest fires. Necko guarantees that
1820
// OnStartRequest will eventually fire if we don't shut down first.
1821
RefPtr<MediaLoadListener> loadListener = new MediaLoadListener(aElement);
1822
1823
channel->SetNotificationCallbacks(loadListener);
1824
1825
nsCOMPtr<nsIHttpChannel> hc = do_QueryInterface(channel);
1826
if (hc) {
1827
// Use a byte range request from the start of the resource.
1828
// This enables us to detect if the stream supports byte range
1829
// requests, and therefore seeking, early.
1830
rv = hc->SetRequestHeader(NS_LITERAL_CSTRING("Range"),
1831
NS_LITERAL_CSTRING("bytes=0-"), false);
1832
MOZ_ASSERT(NS_SUCCEEDED(rv));
1833
aElement->SetRequestHeaders(hc);
1834
}
1835
1836
rv = channel->AsyncOpen(loadListener);
1837
if (NS_FAILED(rv)) {
1838
// Notify load error so the element will try next resource candidate.
1839
aElement->NotifyLoadError(NS_LITERAL_CSTRING("Failed to open channel"));
1840
return;
1841
}
1842
1843
// Else the channel must be open and starting to download. If it encounters
1844
// a non-catastrophic failure, it will set a new task to continue loading
1845
// another candidate. It's safe to set it as mChannel now.
1846
mChannel = channel;
1847
1848
// loadListener will be unregistered either on shutdown or when
1849
// OnStartRequest for the channel we just opened fires.
1850
nsContentUtils::RegisterShutdownObserver(loadListener);
1851
}
1852
1853
nsresult Load(HTMLMediaElement* aElement) {
1854
MOZ_ASSERT(aElement);
1855
// Per bug 1235183 comment 8, we can't spin the event loop from stable
1856
// state. Defer NS_NewChannel() to a new regular runnable.
1857
return aElement->MainThreadEventTarget()->Dispatch(
1858
NewRunnableMethod<HTMLMediaElement*>("ChannelLoader::LoadInternal",
1859
this, &ChannelLoader::LoadInternal,
1860
aElement));
1861
}
1862
1863
void Cancel() {
1864
mCancelled =