1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "MediaDecoder.h"
9 #include "AudioDeviceInfo.h"
10 #include "DOMMediaStream.h"
11 #include "DecoderBenchmark.h"
12 #include "ImageContainer.h"
13 #include "MediaDecoderStateMachineBase.h"
14 #include "MediaFormatReader.h"
15 #include "MediaResource.h"
16 #include "MediaShutdownManager.h"
17 #include "MediaTrackGraph.h"
18 #include "TelemetryProbesReporter.h"
19 #include "VideoFrameContainer.h"
20 #include "VideoUtils.h"
21 #include "mozilla/AbstractThread.h"
22 #include "mozilla/dom/DOMTypes.h"
23 #include "mozilla/FloatingPoint.h"
24 #include "mozilla/MathAlgorithms.h"
25 #include "mozilla/Preferences.h"
26 #include "mozilla/StaticPrefs_media.h"
27 #include "mozilla/StaticPtr.h"
28 #include "mozilla/Telemetry.h"
29 #include "mozilla/Unused.h"
30 #include "nsComponentManagerUtils.h"
31 #include "nsContentUtils.h"
33 #include "nsIMemoryReporter.h"
34 #include "nsPrintfCString.h"
35 #include "nsServiceManagerUtils.h"
37 #include "WindowRenderer.h"
42 using namespace mozilla::dom
;
43 using namespace mozilla::layers
;
44 using namespace mozilla::media
;
48 // avoid redefined macro in unified build
52 LazyLogModule
gMediaDecoderLog("MediaDecoder");
55 DDMOZ_LOG(gMediaDecoderLog, LogLevel::Debug, x, ##__VA_ARGS__)
57 #define DUMP(x, ...) printf_stderr(x "\n", ##__VA_ARGS__)
59 #define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
61 static const char* ToPlayStateStr(MediaDecoder::PlayState aState
) {
63 case MediaDecoder::PLAY_STATE_LOADING
:
65 case MediaDecoder::PLAY_STATE_PAUSED
:
67 case MediaDecoder::PLAY_STATE_PLAYING
:
69 case MediaDecoder::PLAY_STATE_ENDED
:
71 case MediaDecoder::PLAY_STATE_SHUTDOWN
:
74 MOZ_ASSERT_UNREACHABLE("Invalid playState.");
79 class MediaMemoryTracker
: public nsIMemoryReporter
{
80 virtual ~MediaMemoryTracker();
82 NS_DECL_THREADSAFE_ISUPPORTS
83 NS_DECL_NSIMEMORYREPORTER
85 MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf
);
88 void InitMemoryReporter();
90 static StaticRefPtr
<MediaMemoryTracker
> sUniqueInstance
;
92 static MediaMemoryTracker
* UniqueInstance() {
93 if (!sUniqueInstance
) {
94 sUniqueInstance
= new MediaMemoryTracker();
95 sUniqueInstance
->InitMemoryReporter();
97 return sUniqueInstance
;
100 using DecodersArray
= nsTArray
<MediaDecoder
*>;
101 static DecodersArray
& Decoders() { return UniqueInstance()->mDecoders
; }
103 DecodersArray mDecoders
;
106 static void AddMediaDecoder(MediaDecoder
* aDecoder
) {
107 Decoders().AppendElement(aDecoder
);
110 static void RemoveMediaDecoder(MediaDecoder
* aDecoder
) {
111 DecodersArray
& decoders
= Decoders();
112 decoders
.RemoveElement(aDecoder
);
113 if (decoders
.IsEmpty()) {
114 sUniqueInstance
= nullptr;
119 StaticRefPtr
<MediaMemoryTracker
> MediaMemoryTracker::sUniqueInstance
;
121 LazyLogModule
gMediaTimerLog("MediaTimer");
123 constexpr TimeUnit
MediaDecoder::DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED
;
125 void MediaDecoder::InitStatics() {
126 MOZ_ASSERT(NS_IsMainThread());
127 // Eagerly init gMediaDecoderLog to work around bug 1415441.
128 MOZ_LOG(gMediaDecoderLog
, LogLevel::Info
, ("MediaDecoder::InitStatics"));
130 #if defined(NIGHTLY_BUILD)
131 // Allow people to force a bit but try to warn them about filing bugs if audio
132 // decoding does not work on utility
133 static const bool allowLockPrefs
=
134 PR_GetEnv("MOZ_DONT_LOCK_UTILITY_PLZ_FILE_A_BUG") == nullptr;
135 if (XRE_IsParentProcess() && allowLockPrefs
) {
136 // Lock Utility process preferences so that people cannot opt-out of
138 Preferences::Lock("media.utility-process.enabled");
139 # if defined(MOZ_FFMPEG)
140 Preferences::Lock("media.utility-ffmpeg.enabled");
141 # endif // defined(MOZ_FFMPEG)
142 # if defined(MOZ_FFVPX)
143 Preferences::Lock("media.utility-ffvpx.enabled");
144 # endif // defined(MOZ_FFVPX)
145 # if defined(MOZ_WMF)
146 Preferences::Lock("media.utility-wmf.enabled");
147 # endif // defined(MOZ_WMF)
148 # if defined(MOZ_APPLEMEDIA)
149 Preferences::Lock("media.utility-applemedia.enabled");
150 # endif // defined(MOZ_APPLEMEDIA)
151 Preferences::Lock("media.utility-vorbis.enabled");
152 Preferences::Lock("media.utility-wav.enabled");
153 Preferences::Lock("media.utility-opus.enabled");
155 #endif // defined(NIGHTLY_BUILD)
158 NS_IMPL_ISUPPORTS(MediaMemoryTracker
, nsIMemoryReporter
)
160 void MediaDecoder::NotifyOwnerActivityChanged(bool aIsOwnerInvisible
,
161 bool aIsOwnerConnected
) {
162 MOZ_ASSERT(NS_IsMainThread());
163 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
164 SetElementVisibility(aIsOwnerInvisible
, aIsOwnerConnected
);
169 void MediaDecoder::Pause() {
170 MOZ_ASSERT(NS_IsMainThread());
171 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
173 if (mPlayState
== PLAY_STATE_LOADING
|| IsEnded()) {
174 mNextState
= PLAY_STATE_PAUSED
;
177 ChangeState(PLAY_STATE_PAUSED
);
180 void MediaDecoder::SetVolume(double aVolume
) {
181 MOZ_ASSERT(NS_IsMainThread());
185 RefPtr
<GenericPromise
> MediaDecoder::SetSink(AudioDeviceInfo
* aSinkDevice
) {
186 MOZ_ASSERT(NS_IsMainThread());
187 mSinkDevice
= aSinkDevice
;
188 return GetStateMachine()->InvokeSetSink(aSinkDevice
);
191 void MediaDecoder::SetOutputCaptureState(OutputCaptureState aState
,
192 SharedDummyTrack
* aDummyTrack
) {
193 MOZ_ASSERT(NS_IsMainThread());
194 MOZ_ASSERT(mDecoderStateMachine
, "Must be called after Load().");
195 MOZ_ASSERT_IF(aState
== OutputCaptureState::Capture
, aDummyTrack
);
196 mOutputCaptureState
= aState
;
197 if (mOutputDummyTrack
.Ref().get() != aDummyTrack
) {
198 mOutputDummyTrack
= nsMainThreadPtrHandle
<SharedDummyTrack
>(
199 MakeAndAddRef
<nsMainThreadPtrHolder
<SharedDummyTrack
>>(
200 "MediaDecoder::mOutputDummyTrack", aDummyTrack
));
204 void MediaDecoder::AddOutputTrack(RefPtr
<ProcessedMediaTrack
> aTrack
) {
205 MOZ_ASSERT(NS_IsMainThread());
206 MOZ_ASSERT(mDecoderStateMachine
, "Must be called after Load().");
207 CopyableTArray
<RefPtr
<ProcessedMediaTrack
>> tracks
= mOutputTracks
;
208 tracks
.AppendElement(std::move(aTrack
));
209 mOutputTracks
= tracks
;
212 void MediaDecoder::RemoveOutputTrack(
213 const RefPtr
<ProcessedMediaTrack
>& aTrack
) {
214 MOZ_ASSERT(NS_IsMainThread());
215 MOZ_ASSERT(mDecoderStateMachine
, "Must be called after Load().");
216 CopyableTArray
<RefPtr
<ProcessedMediaTrack
>> tracks
= mOutputTracks
;
217 if (tracks
.RemoveElement(aTrack
)) {
218 mOutputTracks
= tracks
;
222 void MediaDecoder::SetOutputTracksPrincipal(
223 const RefPtr
<nsIPrincipal
>& aPrincipal
) {
224 MOZ_ASSERT(NS_IsMainThread());
225 MOZ_ASSERT(mDecoderStateMachine
, "Must be called after Load().");
226 mOutputPrincipal
= MakePrincipalHandle(aPrincipal
);
229 double MediaDecoder::GetDuration() {
230 MOZ_ASSERT(NS_IsMainThread());
231 return ToMicrosecondResolution(mDuration
.match(DurationToDouble()));
234 bool MediaDecoder::IsInfinite() const {
235 MOZ_ASSERT(NS_IsMainThread());
236 return std::isinf(mDuration
.match(DurationToDouble()));
239 #define INIT_MIRROR(name, val) \
240 name(mOwner->AbstractMainThread(), val, "MediaDecoder::" #name " (Mirror)")
241 #define INIT_CANONICAL(name, val) \
242 name(mOwner->AbstractMainThread(), val, "MediaDecoder::" #name " (Canonical)")
244 MediaDecoder::MediaDecoder(MediaDecoderInit
& aInit
)
245 : mWatchManager(this, aInit
.mOwner
->AbstractMainThread()),
246 mLogicalPosition(0.0),
247 mDuration(TimeUnit::Invalid()),
248 mOwner(aInit
.mOwner
),
249 mAbstractMainThread(aInit
.mOwner
->AbstractMainThread()),
250 mFrameStats(new FrameStatistics()),
251 mDecoderBenchmark(new DecoderBenchmark()),
252 mVideoFrameContainer(aInit
.mOwner
->GetVideoFrameContainer()),
253 mMinimizePreroll(aInit
.mMinimizePreroll
),
254 mFiredMetadataLoaded(false),
255 mIsOwnerInvisible(false),
256 mIsOwnerConnected(false),
257 mForcedHidden(false),
258 mHasSuspendTaint(aInit
.mHasSuspendTaint
),
259 mShouldResistFingerprinting(
260 aInit
.mOwner
->ShouldResistFingerprinting(RFPTarget::AudioSampleRate
)),
261 mPlaybackRate(aInit
.mPlaybackRate
),
262 mLogicallySeeking(false, "MediaDecoder::mLogicallySeeking"),
263 INIT_MIRROR(mBuffered
, TimeIntervals()),
264 INIT_MIRROR(mCurrentPosition
, TimeUnit::Zero()),
265 INIT_MIRROR(mStateMachineDuration
, NullableTimeUnit()),
266 INIT_MIRROR(mIsAudioDataAudible
, false),
267 INIT_CANONICAL(mVolume
, aInit
.mVolume
),
268 INIT_CANONICAL(mPreservesPitch
, aInit
.mPreservesPitch
),
269 INIT_CANONICAL(mLooping
, aInit
.mLooping
),
270 INIT_CANONICAL(mStreamName
, aInit
.mStreamName
),
271 INIT_CANONICAL(mSinkDevice
, nullptr),
272 INIT_CANONICAL(mSecondaryVideoContainer
, nullptr),
273 INIT_CANONICAL(mOutputCaptureState
, OutputCaptureState::None
),
274 INIT_CANONICAL(mOutputDummyTrack
, nullptr),
275 INIT_CANONICAL(mOutputTracks
, nsTArray
<RefPtr
<ProcessedMediaTrack
>>()),
276 INIT_CANONICAL(mOutputPrincipal
, PRINCIPAL_HANDLE_NONE
),
277 INIT_CANONICAL(mPlayState
, PLAY_STATE_LOADING
),
278 mSameOriginMedia(false),
279 mVideoDecodingOberver(
280 new BackgroundVideoDecodingPermissionObserver(this)),
281 mIsBackgroundVideoDecodingAllowed(false),
282 mTelemetryReported(false),
283 mContainerType(aInit
.mContainerType
),
284 mTelemetryProbesReporter(
285 new TelemetryProbesReporter(aInit
.mReporterOwner
)) {
286 MOZ_ASSERT(NS_IsMainThread());
287 MOZ_ASSERT(mAbstractMainThread
);
288 MediaMemoryTracker::AddMediaDecoder(this);
291 // Initialize watchers.
295 mWatchManager
.Watch(mStateMachineDuration
, &MediaDecoder::DurationChanged
);
298 mWatchManager
.Watch(mPlayState
, &MediaDecoder::UpdateReadyState
);
299 // ReadyState computation depends on MediaDecoder::CanPlayThrough, which
300 // depends on the download rate.
301 mWatchManager
.Watch(mBuffered
, &MediaDecoder::UpdateReadyState
);
304 mWatchManager
.Watch(mCurrentPosition
, &MediaDecoder::UpdateLogicalPosition
);
305 mWatchManager
.Watch(mPlayState
, &MediaDecoder::UpdateLogicalPosition
);
306 mWatchManager
.Watch(mLogicallySeeking
, &MediaDecoder::UpdateLogicalPosition
);
308 mWatchManager
.Watch(mIsAudioDataAudible
,
309 &MediaDecoder::NotifyAudibleStateChanged
);
311 mWatchManager
.Watch(mVolume
, &MediaDecoder::NotifyVolumeChanged
);
313 mVideoDecodingOberver
->RegisterEvent();
317 #undef INIT_CANONICAL
319 void MediaDecoder::Shutdown() {
320 MOZ_ASSERT(NS_IsMainThread());
321 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
323 // Unwatch all watch targets to prevent further notifications.
324 mWatchManager
.Shutdown();
326 DiscardOngoingSeekIfExists();
328 // This changes the decoder state to SHUTDOWN and does other things
329 // necessary to unblock the state machine thread if it's blocked, so
330 // the asynchronous shutdown in nsDestroyStateMachine won't deadlock.
331 if (mDecoderStateMachine
) {
332 ShutdownStateMachine()->Then(mAbstractMainThread
, __func__
, this,
333 &MediaDecoder::FinishShutdown
,
334 &MediaDecoder::FinishShutdown
);
336 // Ensure we always unregister asynchronously in order not to disrupt
337 // the hashtable iterating in MediaShutdownManager::Shutdown().
338 RefPtr
<MediaDecoder
> self
= this;
339 nsCOMPtr
<nsIRunnable
> r
= NS_NewRunnableFunction(
340 "MediaDecoder::Shutdown", [self
]() { self
->ShutdownInternal(); });
341 mAbstractMainThread
->Dispatch(r
.forget());
344 ChangeState(PLAY_STATE_SHUTDOWN
);
345 mVideoDecodingOberver
->UnregisterEvent();
346 mVideoDecodingOberver
= nullptr;
350 void MediaDecoder::NotifyXPCOMShutdown() {
351 MOZ_ASSERT(NS_IsMainThread());
352 // NotifyXPCOMShutdown will clear its reference to mDecoder. So we must ensure
353 // that this MediaDecoder stays alive until completion.
354 RefPtr
<MediaDecoder
> kungFuDeathGrip
= this;
355 if (auto* owner
= GetOwner()) {
356 owner
->NotifyXPCOMShutdown();
357 } else if (!IsShutdown()) {
360 MOZ_DIAGNOSTIC_ASSERT(IsShutdown());
363 MediaDecoder::~MediaDecoder() {
364 MOZ_ASSERT(NS_IsMainThread());
365 MOZ_DIAGNOSTIC_ASSERT(IsShutdown());
366 MediaMemoryTracker::RemoveMediaDecoder(this);
369 void MediaDecoder::OnPlaybackEvent(MediaPlaybackEvent
&& aEvent
) {
370 switch (aEvent
.mType
) {
371 case MediaPlaybackEvent::PlaybackEnded
:
374 case MediaPlaybackEvent::SeekStarted
:
377 case MediaPlaybackEvent::Invalidate
:
380 case MediaPlaybackEvent::EnterVideoSuspend
:
381 GetOwner()->DispatchAsyncEvent(u
"mozentervideosuspend"_ns
);
382 mTelemetryProbesReporter
->OnDecodeSuspended();
383 mIsVideoDecodingSuspended
= true;
385 case MediaPlaybackEvent::ExitVideoSuspend
:
386 GetOwner()->DispatchAsyncEvent(u
"mozexitvideosuspend"_ns
);
387 mTelemetryProbesReporter
->OnDecodeResumed();
388 mIsVideoDecodingSuspended
= false;
390 case MediaPlaybackEvent::StartVideoSuspendTimer
:
391 GetOwner()->DispatchAsyncEvent(u
"mozstartvideosuspendtimer"_ns
);
393 case MediaPlaybackEvent::CancelVideoSuspendTimer
:
394 GetOwner()->DispatchAsyncEvent(u
"mozcancelvideosuspendtimer"_ns
);
396 case MediaPlaybackEvent::VideoOnlySeekBegin
:
397 GetOwner()->DispatchAsyncEvent(u
"mozvideoonlyseekbegin"_ns
);
399 case MediaPlaybackEvent::VideoOnlySeekCompleted
:
400 GetOwner()->DispatchAsyncEvent(u
"mozvideoonlyseekcompleted"_ns
);
407 bool MediaDecoder::IsVideoDecodingSuspended() const {
408 return mIsVideoDecodingSuspended
;
411 void MediaDecoder::OnPlaybackErrorEvent(const MediaResult
& aError
) {
412 MOZ_ASSERT(NS_IsMainThread());
413 #ifndef MOZ_WMF_MEDIA_ENGINE
416 if (aError
!= NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR
&&
417 aError
!= NS_ERROR_DOM_MEDIA_CDM_PROXY_NOT_SUPPORTED_ERR
) {
422 // Already in shutting down decoder, no need to create another state machine.
423 if (mPlayState
== PLAY_STATE_SHUTDOWN
) {
427 // External engine can't play the resource or we intentionally disable it, try
428 // to use our own state machine again. Here we will create a new state machine
429 // immediately and asynchrously shutdown the old one because we don't want to
430 // dispatch any task to the old state machine. Therefore, we will disconnect
431 // anything related with the old state machine, create a new state machine and
432 // setup events/mirror/etc, then shutdown the old one and release its
433 // reference once it finishes shutdown.
434 RefPtr
<MediaDecoderStateMachineBase
> discardStateMachine
=
435 mDecoderStateMachine
;
437 // Disconnect mirror and events first.
438 SetStateMachine(nullptr);
441 // Recreate a state machine and shutdown the old one.
442 bool needExternalEngine
= false;
443 if (aError
== NS_ERROR_DOM_MEDIA_CDM_PROXY_NOT_SUPPORTED_ERR
) {
445 if (aError
.GetCDMProxy()->AsWMFCDMProxy()) {
446 needExternalEngine
= true;
450 LOG("Need to create a new %s state machine",
451 needExternalEngine
? "external engine" : "normal");
453 nsresult rv
= CreateAndInitStateMachine(
454 false /* live stream */,
455 !needExternalEngine
/* disable external engine */);
456 if (NS_WARN_IF(NS_FAILED(rv
))) {
457 LOG("Failed to create a new state machine!");
460 // Some attributes might have been set on the destroyed state machine, and
461 // won't be reflected on the new MDSM by the state mirroring. We need to
462 // update them manually later, after MDSM finished reading the
463 // metadata because the MDSM might not be ready to perform the operations yet.
464 mPendingStatusUpdateForNewlyCreatedStateMachine
= true;
466 // If there is ongoing seek performed on the old MDSM, cancel it because we
467 // will perform seeking later again and don't want the old seeking affecting
469 DiscardOngoingSeekIfExists();
471 discardStateMachine
->BeginShutdown()->Then(
472 AbstractThread::MainThread(), __func__
, [discardStateMachine
] {});
476 void MediaDecoder::OnDecoderDoctorEvent(DecoderDoctorEvent aEvent
) {
477 MOZ_ASSERT(NS_IsMainThread());
478 // OnDecoderDoctorEvent is disconnected at shutdown time.
479 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
480 Document
* doc
= GetOwner()->GetDocument();
484 DecoderDoctorDiagnostics diags
;
485 diags
.StoreEvent(doc
, aEvent
, __func__
);
488 static const char* NextFrameStatusToStr(
489 MediaDecoderOwner::NextFrameStatus aStatus
) {
491 case MediaDecoderOwner::NEXT_FRAME_AVAILABLE
:
492 return "NEXT_FRAME_AVAILABLE";
493 case MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE
:
494 return "NEXT_FRAME_UNAVAILABLE";
495 case MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING
:
496 return "NEXT_FRAME_UNAVAILABLE_BUFFERING";
497 case MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING
:
498 return "NEXT_FRAME_UNAVAILABLE_SEEKING";
499 case MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED
:
500 return "NEXT_FRAME_UNINITIALIZED";
505 void MediaDecoder::OnNextFrameStatus(
506 MediaDecoderOwner::NextFrameStatus aStatus
) {
507 MOZ_ASSERT(NS_IsMainThread());
508 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
509 if (mNextFrameStatus
!= aStatus
) {
510 LOG("Changed mNextFrameStatus to %s", NextFrameStatusToStr(aStatus
));
511 mNextFrameStatus
= aStatus
;
516 void MediaDecoder::OnTrackInfoUpdated(const VideoInfo
& aVideoInfo
,
517 const AudioInfo
& aAudioInfo
) {
518 MOZ_ASSERT(NS_IsMainThread());
519 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
521 // Note that we don't check HasVideo() or HasAudio() here, because
522 // those are checks for existing validity. If we always set the values
523 // to what we receive, then we can go from not-video to video, for
525 mInfo
->mVideo
= aVideoInfo
;
526 mInfo
->mAudio
= aAudioInfo
;
530 EnsureTelemetryReported();
533 void MediaDecoder::OnSecondaryVideoContainerInstalled(
534 const RefPtr
<VideoFrameContainer
>& aSecondaryVideoContainer
) {
535 MOZ_ASSERT(NS_IsMainThread());
536 GetOwner()->OnSecondaryVideoContainerInstalled(aSecondaryVideoContainer
);
539 void MediaDecoder::OnStoreDecoderBenchmark(const VideoInfo
& aInfo
) {
540 MOZ_ASSERT(NS_IsMainThread());
542 int32_t videoFrameRate
= aInfo
.GetFrameRate().ref();
544 if (mFrameStats
&& videoFrameRate
) {
545 DecoderBenchmarkInfo benchmarkInfo
{
547 aInfo
.mDisplay
.width
,
548 aInfo
.mDisplay
.height
,
550 BitDepthForColorDepth(aInfo
.mColorDepth
),
553 LOG("Store benchmark: Video width=%d, height=%d, frameRate=%d, content "
555 benchmarkInfo
.mWidth
, benchmarkInfo
.mHeight
, benchmarkInfo
.mFrameRate
,
556 benchmarkInfo
.mContentType
.BeginReading());
558 mDecoderBenchmark
->Store(benchmarkInfo
, mFrameStats
);
562 void MediaDecoder::ShutdownInternal() {
563 MOZ_ASSERT(NS_IsMainThread());
564 mVideoFrameContainer
= nullptr;
565 mSecondaryVideoContainer
= nullptr;
566 MediaShutdownManager::Instance().Unregister(this);
569 void MediaDecoder::FinishShutdown() {
570 MOZ_ASSERT(NS_IsMainThread());
571 SetStateMachine(nullptr);
575 nsresult
MediaDecoder::CreateAndInitStateMachine(bool aIsLiveStream
,
576 bool aDisableExternalEngine
) {
577 MOZ_ASSERT(NS_IsMainThread());
578 SetStateMachine(CreateStateMachine(aDisableExternalEngine
));
580 NS_ENSURE_TRUE(GetStateMachine(), NS_ERROR_FAILURE
);
581 GetStateMachine()->DispatchIsLiveStream(aIsLiveStream
);
583 nsresult rv
= mDecoderStateMachine
->Init(this);
584 NS_ENSURE_SUCCESS(rv
, rv
);
586 // If some parameters got set before the state machine got created,
588 SetStateMachineParameters();
593 void MediaDecoder::SetStateMachineParameters() {
594 MOZ_ASSERT(NS_IsMainThread());
595 if (mPlaybackRate
!= 1 && mPlaybackRate
!= 0) {
596 mDecoderStateMachine
->DispatchSetPlaybackRate(mPlaybackRate
);
598 mTimedMetadataListener
= mDecoderStateMachine
->TimedMetadataEvent().Connect(
599 mAbstractMainThread
, this, &MediaDecoder::OnMetadataUpdate
);
600 mMetadataLoadedListener
= mDecoderStateMachine
->MetadataLoadedEvent().Connect(
601 mAbstractMainThread
, this, &MediaDecoder::MetadataLoaded
);
602 mFirstFrameLoadedListener
=
603 mDecoderStateMachine
->FirstFrameLoadedEvent().Connect(
604 mAbstractMainThread
, this, &MediaDecoder::FirstFrameLoaded
);
606 mOnPlaybackEvent
= mDecoderStateMachine
->OnPlaybackEvent().Connect(
607 mAbstractMainThread
, this, &MediaDecoder::OnPlaybackEvent
);
608 mOnPlaybackErrorEvent
= mDecoderStateMachine
->OnPlaybackErrorEvent().Connect(
609 mAbstractMainThread
, this, &MediaDecoder::OnPlaybackErrorEvent
);
610 mOnDecoderDoctorEvent
= mDecoderStateMachine
->OnDecoderDoctorEvent().Connect(
611 mAbstractMainThread
, this, &MediaDecoder::OnDecoderDoctorEvent
);
612 mOnMediaNotSeekable
= mDecoderStateMachine
->OnMediaNotSeekable().Connect(
613 mAbstractMainThread
, this, &MediaDecoder::OnMediaNotSeekable
);
614 mOnNextFrameStatus
= mDecoderStateMachine
->OnNextFrameStatus().Connect(
615 mAbstractMainThread
, this, &MediaDecoder::OnNextFrameStatus
);
616 mOnTrackInfoUpdated
= mDecoderStateMachine
->OnTrackInfoUpdatedEvent().Connect(
617 mAbstractMainThread
, this, &MediaDecoder::OnTrackInfoUpdated
);
618 mOnSecondaryVideoContainerInstalled
=
619 mDecoderStateMachine
->OnSecondaryVideoContainerInstalled().Connect(
620 mAbstractMainThread
, this,
621 &MediaDecoder::OnSecondaryVideoContainerInstalled
);
622 mOnStoreDecoderBenchmark
= mReader
->OnStoreDecoderBenchmark().Connect(
623 mAbstractMainThread
, this, &MediaDecoder::OnStoreDecoderBenchmark
);
625 mOnEncrypted
= mReader
->OnEncrypted().Connect(
626 mAbstractMainThread
, GetOwner(), &MediaDecoderOwner::DispatchEncrypted
);
627 mOnWaitingForKey
= mReader
->OnWaitingForKey().Connect(
628 mAbstractMainThread
, GetOwner(), &MediaDecoderOwner::NotifyWaitingForKey
);
629 mOnDecodeWarning
= mReader
->OnDecodeWarning().Connect(
630 mAbstractMainThread
, GetOwner(), &MediaDecoderOwner::DecodeWarning
);
633 void MediaDecoder::DisconnectEvents() {
634 MOZ_ASSERT(NS_IsMainThread());
635 mTimedMetadataListener
.Disconnect();
636 mMetadataLoadedListener
.Disconnect();
637 mFirstFrameLoadedListener
.Disconnect();
638 mOnPlaybackEvent
.Disconnect();
639 mOnPlaybackErrorEvent
.Disconnect();
640 mOnDecoderDoctorEvent
.Disconnect();
641 mOnMediaNotSeekable
.Disconnect();
642 mOnEncrypted
.Disconnect();
643 mOnWaitingForKey
.Disconnect();
644 mOnDecodeWarning
.Disconnect();
645 mOnNextFrameStatus
.Disconnect();
646 mOnTrackInfoUpdated
.Disconnect();
647 mOnSecondaryVideoContainerInstalled
.Disconnect();
648 mOnStoreDecoderBenchmark
.Disconnect();
651 RefPtr
<ShutdownPromise
> MediaDecoder::ShutdownStateMachine() {
652 MOZ_ASSERT(NS_IsMainThread());
653 MOZ_ASSERT(GetStateMachine());
655 return mDecoderStateMachine
->BeginShutdown();
658 void MediaDecoder::Play() {
659 MOZ_ASSERT(NS_IsMainThread());
661 NS_ASSERTION(mDecoderStateMachine
!= nullptr, "Should have state machine.");
663 if (mPlaybackRate
== 0) {
668 Seek(0, SeekTarget::PrevSyncPoint
);
672 if (mPlayState
== PLAY_STATE_LOADING
) {
673 mNextState
= PLAY_STATE_PLAYING
;
677 ChangeState(PLAY_STATE_PLAYING
);
680 void MediaDecoder::Seek(double aTime
, SeekTarget::Type aSeekType
) {
681 MOZ_ASSERT(NS_IsMainThread());
682 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
684 MOZ_ASSERT(aTime
>= 0.0, "Cannot seek to a negative value.");
687 auto time
= TimeUnit::FromSeconds(aTime
);
689 mLogicalPosition
= aTime
;
690 mLogicallySeeking
= true;
691 SeekTarget target
= SeekTarget(time
, aSeekType
);
694 if (mPlayState
== PLAY_STATE_ENDED
) {
695 ChangeState(GetOwner()->GetPaused() ? PLAY_STATE_PAUSED
696 : PLAY_STATE_PLAYING
);
700 void MediaDecoder::SetDelaySeekMode(bool aShouldDelaySeek
) {
701 MOZ_ASSERT(NS_IsMainThread());
702 LOG("SetDelaySeekMode, shouldDelaySeek=%d", aShouldDelaySeek
);
703 if (mShouldDelaySeek
== aShouldDelaySeek
) {
706 mShouldDelaySeek
= aShouldDelaySeek
;
707 if (!mShouldDelaySeek
&& mDelayedSeekTarget
) {
708 Seek(mDelayedSeekTarget
->GetTime().ToSeconds(),
709 mDelayedSeekTarget
->GetType());
710 mDelayedSeekTarget
.reset();
714 void MediaDecoder::DiscardOngoingSeekIfExists() {
715 MOZ_ASSERT(NS_IsMainThread());
716 mSeekRequest
.DisconnectIfExists();
719 void MediaDecoder::CallSeek(const SeekTarget
& aTarget
) {
720 MOZ_ASSERT(NS_IsMainThread());
721 if (mShouldDelaySeek
) {
722 LOG("Delay seek to %f and store it to delayed seek target",
723 mDelayedSeekTarget
->GetTime().ToSeconds());
724 mDelayedSeekTarget
= Some(aTarget
);
727 DiscardOngoingSeekIfExists();
728 mDecoderStateMachine
->InvokeSeek(aTarget
)
729 ->Then(mAbstractMainThread
, __func__
, this, &MediaDecoder::OnSeekResolved
,
730 &MediaDecoder::OnSeekRejected
)
731 ->Track(mSeekRequest
);
734 double MediaDecoder::GetCurrentTime() {
735 MOZ_ASSERT(NS_IsMainThread());
736 return mLogicalPosition
;
739 void MediaDecoder::OnMetadataUpdate(TimedMetadata
&& aMetadata
) {
740 MOZ_ASSERT(NS_IsMainThread());
741 MetadataLoaded(MakeUnique
<MediaInfo
>(*aMetadata
.mInfo
),
742 UniquePtr
<MetadataTags
>(std::move(aMetadata
.mTags
)),
743 MediaDecoderEventVisibility::Observable
);
744 FirstFrameLoaded(std::move(aMetadata
.mInfo
),
745 MediaDecoderEventVisibility::Observable
);
748 void MediaDecoder::MetadataLoaded(
749 UniquePtr
<MediaInfo
> aInfo
, UniquePtr
<MetadataTags
> aTags
,
750 MediaDecoderEventVisibility aEventVisibility
) {
751 MOZ_ASSERT(NS_IsMainThread());
752 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
754 LOG("MetadataLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d",
755 aInfo
->mAudio
.mChannels
, aInfo
->mAudio
.mRate
, aInfo
->HasAudio(),
758 mMediaSeekable
= aInfo
->mMediaSeekable
;
759 mMediaSeekableOnlyInBufferedRanges
=
760 aInfo
->mMediaSeekableOnlyInBufferedRanges
;
761 mInfo
= std::move(aInfo
);
763 mTelemetryProbesReporter
->OnMediaContentChanged(
764 TelemetryProbesReporter::MediaInfoToMediaContent(*mInfo
));
766 // Make sure the element and the frame (if any) are told about
768 if (aEventVisibility
!= MediaDecoderEventVisibility::Suppressed
) {
769 mFiredMetadataLoaded
= true;
770 GetOwner()->MetadataLoaded(mInfo
.get(), std::move(aTags
));
772 // Invalidate() will end up calling GetOwner()->UpdateMediaSize with the last
773 // dimensions retrieved from the video frame container. The video frame
774 // container contains more up to date dimensions than aInfo.
775 // So we call Invalidate() after calling GetOwner()->MetadataLoaded to ensure
776 // the media element has the latest dimensions.
779 #ifdef MOZ_WMF_MEDIA_ENGINE
780 if (mPendingStatusUpdateForNewlyCreatedStateMachine
) {
781 mPendingStatusUpdateForNewlyCreatedStateMachine
= false;
782 LOG("Set pending statuses if necessary (mLogicallySeeking=%d, "
783 "mLogicalPosition=%f, mPlaybackRate=%f)",
784 mLogicallySeeking
.Ref(), mLogicalPosition
, mPlaybackRate
);
785 if (mLogicalPosition
!= 0) {
786 Seek(mLogicalPosition
, SeekTarget::Accurate
);
788 if (mPlaybackRate
!= 0 && mPlaybackRate
!= 1.0) {
789 mDecoderStateMachine
->DispatchSetPlaybackRate(mPlaybackRate
);
794 EnsureTelemetryReported();
797 void MediaDecoder::EnsureTelemetryReported() {
798 MOZ_ASSERT(NS_IsMainThread());
800 if (mTelemetryReported
|| !mInfo
) {
801 // Note: sometimes we get multiple MetadataLoaded calls (for example
802 // for chained ogg). So we ensure we don't report duplicate results for
807 nsTArray
<nsCString
> codecs
;
808 if (mInfo
->HasAudio() &&
809 !mInfo
->mAudio
.GetAsAudioInfo()->mMimeType
.IsEmpty()) {
810 codecs
.AppendElement(mInfo
->mAudio
.GetAsAudioInfo()->mMimeType
);
812 if (mInfo
->HasVideo() &&
813 !mInfo
->mVideo
.GetAsVideoInfo()->mMimeType
.IsEmpty()) {
814 codecs
.AppendElement(mInfo
->mVideo
.GetAsVideoInfo()->mMimeType
);
816 if (codecs
.IsEmpty()) {
817 codecs
.AppendElement(nsPrintfCString(
818 "resource; %s", ContainerType().OriginalString().Data()));
820 for (const nsCString
& codec
: codecs
) {
821 LOG("Telemetry MEDIA_CODEC_USED= '%s'", codec
.get());
822 Telemetry::Accumulate(Telemetry::HistogramID::MEDIA_CODEC_USED
, codec
);
825 mTelemetryReported
= true;
828 const char* MediaDecoder::PlayStateStr() {
829 MOZ_ASSERT(NS_IsMainThread());
830 return ToPlayStateStr(mPlayState
);
833 void MediaDecoder::FirstFrameLoaded(
834 UniquePtr
<MediaInfo
> aInfo
, MediaDecoderEventVisibility aEventVisibility
) {
835 MOZ_ASSERT(NS_IsMainThread());
836 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
838 LOG("FirstFrameLoaded, channels=%u rate=%u hasAudio=%d hasVideo=%d "
839 "mPlayState=%s transportSeekable=%d",
840 aInfo
->mAudio
.mChannels
, aInfo
->mAudio
.mRate
, aInfo
->HasAudio(),
841 aInfo
->HasVideo(), PlayStateStr(), IsTransportSeekable());
843 mInfo
= std::move(aInfo
);
844 mTelemetryProbesReporter
->OnMediaContentChanged(
845 TelemetryProbesReporter::MediaInfoToMediaContent(*mInfo
));
849 // The element can run javascript via events
850 // before reaching here, so only change the
851 // state if we're still set to the original
853 if (mPlayState
== PLAY_STATE_LOADING
) {
854 ChangeState(mNextState
);
857 // GetOwner()->FirstFrameLoaded() might call us back. Put it at the bottom of
858 // this function to avoid unexpected shutdown from reentrant calls.
859 if (aEventVisibility
!= MediaDecoderEventVisibility::Suppressed
) {
860 GetOwner()->FirstFrameLoaded();
864 void MediaDecoder::NetworkError(const MediaResult
& aError
) {
865 MOZ_ASSERT(NS_IsMainThread());
866 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
867 GetOwner()->NetworkError(aError
);
870 void MediaDecoder::DecodeError(const MediaResult
& aError
) {
871 MOZ_ASSERT(NS_IsMainThread());
872 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
873 GetOwner()->DecodeError(aError
);
876 void MediaDecoder::UpdateSameOriginStatus(bool aSameOrigin
) {
877 MOZ_ASSERT(NS_IsMainThread());
878 mSameOriginMedia
= aSameOrigin
;
881 bool MediaDecoder::IsSeeking() const {
882 MOZ_ASSERT(NS_IsMainThread());
883 return mLogicallySeeking
;
886 bool MediaDecoder::OwnerHasError() const {
887 MOZ_ASSERT(NS_IsMainThread());
888 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
889 return GetOwner()->HasError();
892 bool MediaDecoder::IsEnded() const {
893 MOZ_ASSERT(NS_IsMainThread());
894 return mPlayState
== PLAY_STATE_ENDED
;
897 bool MediaDecoder::IsShutdown() const {
898 MOZ_ASSERT(NS_IsMainThread());
899 return mPlayState
== PLAY_STATE_SHUTDOWN
;
902 void MediaDecoder::PlaybackEnded() {
903 MOZ_ASSERT(NS_IsMainThread());
904 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
906 if (mLogicallySeeking
|| mPlayState
== PLAY_STATE_LOADING
||
907 mPlayState
== PLAY_STATE_ENDED
) {
908 LOG("MediaDecoder::PlaybackEnded bailed out, "
909 "mLogicallySeeking=%d mPlayState=%s",
910 mLogicallySeeking
.Ref(), ToPlayStateStr(mPlayState
));
914 LOG("MediaDecoder::PlaybackEnded");
916 ChangeState(PLAY_STATE_ENDED
);
917 InvalidateWithFlags(VideoFrameContainer::INVALIDATE_FORCE
);
918 GetOwner()->PlaybackEnded();
921 void MediaDecoder::NotifyPrincipalChanged() {
922 MOZ_ASSERT(NS_IsMainThread());
923 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
924 GetOwner()->NotifyDecoderPrincipalChanged();
927 void MediaDecoder::OnSeekResolved() {
928 MOZ_ASSERT(NS_IsMainThread());
929 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
930 LOG("MediaDecoder::OnSeekResolved");
931 mLogicallySeeking
= false;
933 // Ensure logical position is updated after seek.
934 UpdateLogicalPositionInternal();
935 mSeekRequest
.Complete();
937 GetOwner()->SeekCompleted();
940 void MediaDecoder::OnSeekRejected() {
941 MOZ_ASSERT(NS_IsMainThread());
942 LOG("MediaDecoder::OnSeekRejected");
943 mSeekRequest
.Complete();
944 mLogicallySeeking
= false;
946 GetOwner()->SeekAborted();
949 void MediaDecoder::SeekingStarted() {
950 MOZ_ASSERT(NS_IsMainThread());
951 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
952 GetOwner()->SeekStarted();
955 void MediaDecoder::ChangeState(PlayState aState
) {
956 MOZ_ASSERT(NS_IsMainThread());
957 MOZ_ASSERT(!IsShutdown(), "SHUTDOWN is the final state.");
959 if (mNextState
== aState
) {
960 mNextState
= PLAY_STATE_PAUSED
;
963 if (mPlayState
!= aState
) {
964 DDLOG(DDLogCategory::Property
, "play_state", ToPlayStateStr(aState
));
965 LOG("Play state changes from %s to %s", ToPlayStateStr(mPlayState
),
966 ToPlayStateStr(aState
));
968 UpdateTelemetryHelperBasedOnPlayState(aState
);
972 TelemetryProbesReporter::Visibility
MediaDecoder::OwnerVisibility() const {
973 return GetOwner()->IsActuallyInvisible() || mForcedHidden
974 ? TelemetryProbesReporter::Visibility::eInvisible
975 : TelemetryProbesReporter::Visibility::eVisible
;
978 void MediaDecoder::UpdateTelemetryHelperBasedOnPlayState(
979 PlayState aState
) const {
980 if (aState
== PlayState::PLAY_STATE_PLAYING
) {
981 mTelemetryProbesReporter
->OnPlay(
983 TelemetryProbesReporter::MediaInfoToMediaContent(*mInfo
),
985 } else if (aState
== PlayState::PLAY_STATE_PAUSED
||
986 aState
== PlayState::PLAY_STATE_ENDED
) {
987 mTelemetryProbesReporter
->OnPause(OwnerVisibility());
988 } else if (aState
== PLAY_STATE_SHUTDOWN
) {
989 mTelemetryProbesReporter
->OnShutdown();
993 MediaDecoder::PositionUpdate
MediaDecoder::GetPositionUpdateReason(
994 double aPrevPos
, const TimeUnit
& aCurPos
) const {
995 MOZ_ASSERT(NS_IsMainThread());
996 // If current position is earlier than previous position and we didn't do
997 // seek, that means we looped back to the start position.
998 const bool notSeeking
= !mSeekRequest
.Exists();
999 if (mLooping
&& notSeeking
&& aCurPos
.ToSeconds() < aPrevPos
) {
1000 return PositionUpdate::eSeamlessLoopingSeeking
;
1002 return aPrevPos
!= aCurPos
.ToSeconds() && notSeeking
1003 ? PositionUpdate::ePeriodicUpdate
1004 : PositionUpdate::eOther
;
1007 void MediaDecoder::UpdateLogicalPositionInternal() {
1008 MOZ_ASSERT(NS_IsMainThread());
1009 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
1011 TimeUnit currentPosition
= CurrentPosition();
1012 if (mPlayState
== PLAY_STATE_ENDED
) {
1014 std::max(currentPosition
, mDuration
.match(DurationToTimeUnit()));
1017 const PositionUpdate reason
=
1018 GetPositionUpdateReason(mLogicalPosition
, currentPosition
);
1020 case PositionUpdate::ePeriodicUpdate
:
1021 SetLogicalPosition(currentPosition
);
1022 // This is actually defined in `TimeMarchesOn`, but we do that in decoder.
1023 // https://html.spec.whatwg.org/multipage/media.html#playing-the-media-resource:event-media-timeupdate-7
1024 // TODO (bug 1688137): should we move it back to `TimeMarchesOn`?
1025 GetOwner()->MaybeQueueTimeupdateEvent();
1027 case PositionUpdate::eSeamlessLoopingSeeking
:
1028 // When seamless seeking occurs, seeking was performed on the demuxer so
1029 // the decoder doesn't know. That means decoder still thinks it's in
1030 // playing. Therefore, we have to manually call those methods to notify
1031 // the owner about seeking.
1032 GetOwner()->SeekStarted();
1033 SetLogicalPosition(currentPosition
);
1034 GetOwner()->SeekCompleted();
1037 MOZ_ASSERT(reason
== PositionUpdate::eOther
);
1038 SetLogicalPosition(currentPosition
);
1042 // Invalidate the frame so any video data is displayed.
1043 // Do this before the timeupdate event so that if that
1044 // event runs JavaScript that queries the media size, the
1045 // frame has reflowed and the size updated beforehand.
1049 void MediaDecoder::SetLogicalPosition(const TimeUnit
& aNewPosition
) {
1050 MOZ_ASSERT(NS_IsMainThread());
1051 if (TimeUnit::FromSeconds(mLogicalPosition
) == aNewPosition
||
1052 mLogicalPosition
== aNewPosition
.ToSeconds()) {
1055 mLogicalPosition
= aNewPosition
.ToSeconds();
1056 DDLOG(DDLogCategory::Property
, "currentTime", mLogicalPosition
);
1059 void MediaDecoder::DurationChanged() {
1060 MOZ_ASSERT(NS_IsMainThread());
1061 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
1063 Variant
<TimeUnit
, double> oldDuration
= mDuration
;
1065 // Use the explicit duration if we have one.
1066 // Otherwise use the duration mirrored from MDSM.
1067 if (mExplicitDuration
.isSome()) {
1068 mDuration
.emplace
<double>(mExplicitDuration
.ref());
1069 } else if (mStateMachineDuration
.Ref().isSome()) {
1070 MOZ_ASSERT(mStateMachineDuration
.Ref().ref().IsValid());
1071 mDuration
.emplace
<TimeUnit
>(mStateMachineDuration
.Ref().ref());
1074 LOG("New duration: %s",
1075 mDuration
.match(DurationToTimeUnit()).ToString().get());
1076 if (oldDuration
.is
<TimeUnit
>() && oldDuration
.as
<TimeUnit
>().IsValid()) {
1077 LOG("Old Duration %s",
1078 oldDuration
.match(DurationToTimeUnit()).ToString().get());
1081 if ((oldDuration
.is
<double>() || oldDuration
.as
<TimeUnit
>().IsValid())) {
1082 if (mDuration
.match(DurationToDouble()) ==
1083 oldDuration
.match(DurationToDouble())) {
1088 LOG("Duration changed to %s",
1089 mDuration
.match(DurationToTimeUnit()).ToString().get());
1091 // See https://www.w3.org/Bugs/Public/show_bug.cgi?id=28822 for a discussion
1092 // of whether we should fire durationchange on explicit infinity.
1093 if (mFiredMetadataLoaded
&&
1094 (!std::isinf(mDuration
.match(DurationToDouble())) ||
1095 mExplicitDuration
.isSome())) {
1096 GetOwner()->DispatchAsyncEvent(u
"durationchange"_ns
);
1099 if (CurrentPosition().ToSeconds() > mDuration
.match(DurationToDouble())) {
1100 Seek(mDuration
.match(DurationToDouble()), SeekTarget::Accurate
);
1104 already_AddRefed
<KnowsCompositor
> MediaDecoder::GetCompositor() {
1105 MediaDecoderOwner
* owner
= GetOwner();
1106 Document
* ownerDoc
= owner
? owner
->GetDocument() : nullptr;
1107 WindowRenderer
* renderer
=
1108 ownerDoc
? nsContentUtils::WindowRendererForDocument(ownerDoc
) : nullptr;
1109 RefPtr
<KnowsCompositor
> knows
=
1110 renderer
? renderer
->AsKnowsCompositor() : nullptr;
1111 return knows
? knows
->GetForMedia().forget() : nullptr;
1114 void MediaDecoder::NotifyCompositor() {
1115 RefPtr
<KnowsCompositor
> knowsCompositor
= GetCompositor();
1116 if (knowsCompositor
) {
1117 nsCOMPtr
<nsIRunnable
> r
=
1118 NewRunnableMethod
<already_AddRefed
<KnowsCompositor
>&&>(
1119 "MediaFormatReader::UpdateCompositor", mReader
,
1120 &MediaFormatReader::UpdateCompositor
, knowsCompositor
.forget());
1121 Unused
<< mReader
->OwnerThread()->Dispatch(r
.forget());
1125 void MediaDecoder::SetElementVisibility(bool aIsOwnerInvisible
,
1126 bool aIsOwnerConnected
) {
1127 MOZ_ASSERT(NS_IsMainThread());
1128 mIsOwnerInvisible
= aIsOwnerInvisible
;
1129 mIsOwnerConnected
= aIsOwnerConnected
;
1130 mTelemetryProbesReporter
->OnVisibilityChanged(OwnerVisibility());
1131 UpdateVideoDecodeMode();
1134 void MediaDecoder::SetForcedHidden(bool aForcedHidden
) {
1135 MOZ_ASSERT(NS_IsMainThread());
1136 mForcedHidden
= aForcedHidden
;
1137 mTelemetryProbesReporter
->OnVisibilityChanged(OwnerVisibility());
1138 UpdateVideoDecodeMode();
1141 void MediaDecoder::SetSuspendTaint(bool aTainted
) {
1142 MOZ_ASSERT(NS_IsMainThread());
1143 mHasSuspendTaint
= aTainted
;
1144 UpdateVideoDecodeMode();
1147 void MediaDecoder::UpdateVideoDecodeMode() {
1148 MOZ_ASSERT(NS_IsMainThread());
1150 // The MDSM may yet be set.
1151 if (!mDecoderStateMachine
) {
1152 LOG("UpdateVideoDecodeMode(), early return because we don't have MDSM.");
1156 // Seeking is required when leaving suspend mode.
1157 if (!mMediaSeekable
) {
1158 LOG("UpdateVideoDecodeMode(), set Normal because the media is not "
1160 mDecoderStateMachine
->SetVideoDecodeMode(VideoDecodeMode::Normal
);
1164 // If mHasSuspendTaint is set, never suspend the video decoder.
1165 if (mHasSuspendTaint
) {
1166 LOG("UpdateVideoDecodeMode(), set Normal because the element has been "
1168 mDecoderStateMachine
->SetVideoDecodeMode(VideoDecodeMode::Normal
);
1172 // If mSecondaryVideoContainer is set, never suspend the video decoder.
1173 if (mSecondaryVideoContainer
.Ref()) {
1174 LOG("UpdateVideoDecodeMode(), set Normal because the element is cloning "
1175 "itself visually to another video container.");
1176 mDecoderStateMachine
->SetVideoDecodeMode(VideoDecodeMode::Normal
);
1180 // Don't suspend elements that is not in a connected tree.
1181 if (!mIsOwnerConnected
) {
1182 LOG("UpdateVideoDecodeMode(), set Normal because the element is not in "
1184 mDecoderStateMachine
->SetVideoDecodeMode(VideoDecodeMode::Normal
);
1188 // If mForcedHidden is set, suspend the video decoder anyway.
1189 if (mForcedHidden
) {
1190 LOG("UpdateVideoDecodeMode(), set Suspend because the element is forced to "
1192 mDecoderStateMachine
->SetVideoDecodeMode(VideoDecodeMode::Suspend
);
1196 // Resume decoding in the advance, even the element is in the background.
1197 if (mIsBackgroundVideoDecodingAllowed
) {
1198 LOG("UpdateVideoDecodeMode(), set Normal because the tab is in background "
1200 mDecoderStateMachine
->SetVideoDecodeMode(VideoDecodeMode::Normal
);
1204 if (mIsOwnerInvisible
) {
1205 LOG("UpdateVideoDecodeMode(), set Suspend because of invisible element.");
1206 mDecoderStateMachine
->SetVideoDecodeMode(VideoDecodeMode::Suspend
);
1208 LOG("UpdateVideoDecodeMode(), set Normal because of visible element.");
1209 mDecoderStateMachine
->SetVideoDecodeMode(VideoDecodeMode::Normal
);
1213 void MediaDecoder::SetIsBackgroundVideoDecodingAllowed(bool aAllowed
) {
1214 mIsBackgroundVideoDecodingAllowed
= aAllowed
;
1215 UpdateVideoDecodeMode();
1218 bool MediaDecoder::HasSuspendTaint() const {
1219 MOZ_ASSERT(NS_IsMainThread());
1220 return mHasSuspendTaint
;
1223 void MediaDecoder::SetSecondaryVideoContainer(
1224 const RefPtr
<VideoFrameContainer
>& aSecondaryVideoContainer
) {
1225 MOZ_ASSERT(NS_IsMainThread());
1226 if (mSecondaryVideoContainer
.Ref() == aSecondaryVideoContainer
) {
1229 mSecondaryVideoContainer
= aSecondaryVideoContainer
;
1230 UpdateVideoDecodeMode();
1233 bool MediaDecoder::IsMediaSeekable() {
1234 MOZ_ASSERT(NS_IsMainThread());
1235 NS_ENSURE_TRUE(GetStateMachine(), false);
1236 return mMediaSeekable
;
1241 // Returns zero, either as a TimeUnit or as a double.
1242 template <typename T
>
1243 constexpr T
Zero() {
1244 if constexpr (std::is_same
<T
, double>::value
) {
1246 } else if constexpr (std::is_same
<T
, TimeUnit
>::value
) {
1247 return TimeUnit::Zero();
1249 MOZ_RELEASE_ASSERT(false);
1252 // Returns Infinity either as a TimeUnit or as a double.
1253 template <typename T
>
1254 constexpr T
Infinity() {
1255 if constexpr (std::is_same
<T
, double>::value
) {
1256 return std::numeric_limits
<double>::infinity();
1257 } else if constexpr (std::is_same
<T
, TimeUnit
>::value
) {
1258 return TimeUnit::FromInfinity();
1260 MOZ_RELEASE_ASSERT(false);
1265 // This method can be made to return either TimeIntervals, that is a set of
1266 // interval that are delimited with TimeUnit, or TimeRanges, that is a set of
1267 // intervals that are delimited by seconds, as doubles.
1268 // seekable often depends on the duration of a media, in the very common case
1269 // where the seekable range is [0, duration]. When playing a MediaSource, the
1270 // duration of a media element can be set as an arbitrary number, that are
1271 // 64-bits floating point values.
1272 // This allows returning an interval that is [0, duration], with duration being
1273 // a double that cannot be represented as a TimeUnit, either because it has too
1274 // many significant digits, or because it's outside of the int64_t range that
1275 // TimeUnit internally uses.
1276 template <typename IntervalType
>
1277 IntervalType
MediaDecoder::GetSeekableImpl() {
1278 MOZ_ASSERT(NS_IsMainThread());
1279 if (std::isnan(GetDuration())) {
1280 // We do not have a duration yet, we can't determine the seekable range.
1281 return IntervalType();
1284 // Compute [0, duration] -- When dealing with doubles, use ::GetDuration to
1285 // avoid rounding the value differently. When dealing with TimeUnit, it's
1286 // returned directly.
1287 typename
IntervalType::InnerType duration
;
1288 if constexpr (std::is_same
<typename
IntervalType::InnerType
, double>::value
) {
1289 duration
= GetDuration();
1291 duration
= mDuration
.as
<TimeUnit
>();
1293 typename
IntervalType::ElemType zeroToDuration
=
1294 typename
IntervalType::ElemType(
1295 Zero
<typename
IntervalType::InnerType
>(),
1296 IsInfinite() ? Infinity
<typename
IntervalType::InnerType
>()
1298 auto buffered
= IntervalType(GetBuffered());
1299 // Remove any negative range in the interval -- seeking to a non-positive
1300 // position isn't possible.
1301 auto positiveBuffered
= buffered
.Intersection(zeroToDuration
);
1303 // We can seek in buffered range if the media is seekable. Also, we can seek
1304 // in unbuffered ranges if the transport level is seekable (local file or the
1305 // server supports range requests, etc.) or in cue-less WebMs
1306 if (mMediaSeekableOnlyInBufferedRanges
) {
1307 return IntervalType(positiveBuffered
);
1309 if (!IsMediaSeekable()) {
1310 return IntervalType();
1312 if (!IsTransportSeekable()) {
1313 return IntervalType(positiveBuffered
);
1316 // Common case: seeking is possible at any point of the stream.
1317 return IntervalType(zeroToDuration
);
1320 media::TimeIntervals
MediaDecoder::GetSeekable() {
1321 return GetSeekableImpl
<media::TimeIntervals
>();
1324 media::TimeRanges
MediaDecoder::GetSeekableTimeRanges() {
1325 return GetSeekableImpl
<media::TimeRanges
>();
1328 void MediaDecoder::SetFragmentEndTime(double aTime
) {
1329 MOZ_ASSERT(NS_IsMainThread());
1330 if (mDecoderStateMachine
) {
1331 mDecoderStateMachine
->DispatchSetFragmentEndTime(
1332 TimeUnit::FromSeconds(aTime
));
1336 void MediaDecoder::SetPlaybackRate(double aPlaybackRate
) {
1337 MOZ_ASSERT(NS_IsMainThread());
1339 double oldRate
= mPlaybackRate
;
1340 mPlaybackRate
= aPlaybackRate
;
1341 if (aPlaybackRate
== 0) {
1346 if (oldRate
== 0 && !GetOwner()->GetPaused()) {
1347 // PlaybackRate is no longer null.
1348 // Restart the playback if the media was playing.
1352 if (mDecoderStateMachine
) {
1353 mDecoderStateMachine
->DispatchSetPlaybackRate(aPlaybackRate
);
1357 void MediaDecoder::SetPreservesPitch(bool aPreservesPitch
) {
1358 MOZ_ASSERT(NS_IsMainThread());
1359 mPreservesPitch
= aPreservesPitch
;
1362 void MediaDecoder::SetLooping(bool aLooping
) {
1363 MOZ_ASSERT(NS_IsMainThread());
1364 mLooping
= aLooping
;
1367 void MediaDecoder::SetStreamName(const nsAutoString
& aStreamName
) {
1368 MOZ_ASSERT(NS_IsMainThread());
1369 mStreamName
= aStreamName
;
1372 void MediaDecoder::ConnectMirrors(MediaDecoderStateMachineBase
* aObject
) {
1373 MOZ_ASSERT(NS_IsMainThread());
1374 MOZ_ASSERT(aObject
);
1375 mStateMachineDuration
.Connect(aObject
->CanonicalDuration());
1376 mBuffered
.Connect(aObject
->CanonicalBuffered());
1377 mCurrentPosition
.Connect(aObject
->CanonicalCurrentPosition());
1378 mIsAudioDataAudible
.Connect(aObject
->CanonicalIsAudioDataAudible());
1381 void MediaDecoder::DisconnectMirrors() {
1382 MOZ_ASSERT(NS_IsMainThread());
1383 mStateMachineDuration
.DisconnectIfConnected();
1384 mBuffered
.DisconnectIfConnected();
1385 mCurrentPosition
.DisconnectIfConnected();
1386 mIsAudioDataAudible
.DisconnectIfConnected();
1389 void MediaDecoder::SetStateMachine(
1390 MediaDecoderStateMachineBase
* aStateMachine
) {
1391 MOZ_ASSERT(NS_IsMainThread());
1392 MOZ_ASSERT_IF(aStateMachine
, !mDecoderStateMachine
);
1393 if (aStateMachine
) {
1394 mDecoderStateMachine
= aStateMachine
;
1395 LOG("set state machine %p", mDecoderStateMachine
.get());
1396 ConnectMirrors(aStateMachine
);
1397 UpdateVideoDecodeMode();
1398 } else if (mDecoderStateMachine
) {
1399 LOG("null out state machine %p", mDecoderStateMachine
.get());
1400 mDecoderStateMachine
= nullptr;
1401 DisconnectMirrors();
1405 ImageContainer
* MediaDecoder::GetImageContainer() {
1406 return mVideoFrameContainer
? mVideoFrameContainer
->GetImageContainer()
1410 void MediaDecoder::InvalidateWithFlags(uint32_t aFlags
) {
1411 if (mVideoFrameContainer
) {
1412 mVideoFrameContainer
->InvalidateWithFlags(aFlags
);
1416 void MediaDecoder::Invalidate() {
1417 if (mVideoFrameContainer
) {
1418 mVideoFrameContainer
->Invalidate();
1422 void MediaDecoder::Suspend() {
1423 MOZ_ASSERT(NS_IsMainThread());
1424 GetStateMachine()->InvokeSuspendMediaSink();
1427 void MediaDecoder::Resume() {
1428 MOZ_ASSERT(NS_IsMainThread());
1429 GetStateMachine()->InvokeResumeMediaSink();
1432 // Constructs the time ranges representing what segments of the media
1433 // are buffered and playable.
1434 media::TimeIntervals
MediaDecoder::GetBuffered() {
1435 MOZ_ASSERT(NS_IsMainThread());
1436 return mBuffered
.Ref();
1439 size_t MediaDecoder::SizeOfVideoQueue() {
1440 MOZ_ASSERT(NS_IsMainThread());
1441 if (mDecoderStateMachine
) {
1442 return mDecoderStateMachine
->SizeOfVideoQueue();
1447 size_t MediaDecoder::SizeOfAudioQueue() {
1448 MOZ_ASSERT(NS_IsMainThread());
1449 if (mDecoderStateMachine
) {
1450 return mDecoderStateMachine
->SizeOfAudioQueue();
1455 void MediaDecoder::NotifyReaderDataArrived() {
1456 MOZ_ASSERT(NS_IsMainThread());
1457 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
1459 nsresult rv
= mReader
->OwnerThread()->Dispatch(
1460 NewRunnableMethod("MediaFormatReader::NotifyDataArrived", mReader
.get(),
1461 &MediaFormatReader::NotifyDataArrived
));
1462 MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv
));
1466 // Provide access to the state machine object
1467 MediaDecoderStateMachineBase
* MediaDecoder::GetStateMachine() const {
1468 MOZ_ASSERT(NS_IsMainThread());
1469 return mDecoderStateMachine
;
1472 bool MediaDecoder::CanPlayThrough() {
1473 MOZ_ASSERT(NS_IsMainThread());
1474 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
1475 return CanPlayThroughImpl();
1478 RefPtr
<SetCDMPromise
> MediaDecoder::SetCDMProxy(CDMProxy
* aProxy
) {
1479 MOZ_ASSERT(NS_IsMainThread());
1480 #ifdef MOZ_WMF_MEDIA_ENGINE
1481 // Switch to another state machine if the current one doesn't support the
1483 if (aProxy
&& !GetStateMachine()->IsCDMProxySupported(aProxy
)) {
1484 LOG("CDM proxy not supported! Switch to another state machine.");
1485 OnPlaybackErrorEvent(
1486 MediaResult
{NS_ERROR_DOM_MEDIA_CDM_PROXY_NOT_SUPPORTED_ERR
, aProxy
});
1489 MOZ_DIAGNOSTIC_ASSERT_IF(aProxy
,
1490 GetStateMachine()->IsCDMProxySupported(aProxy
));
1491 return GetStateMachine()->SetCDMProxy(aProxy
);
1494 bool MediaDecoder::IsOpusEnabled() { return StaticPrefs::media_opus_enabled(); }
1496 bool MediaDecoder::IsOggEnabled() { return StaticPrefs::media_ogg_enabled(); }
1498 bool MediaDecoder::IsWaveEnabled() { return StaticPrefs::media_wave_enabled(); }
1500 bool MediaDecoder::IsWebMEnabled() { return StaticPrefs::media_webm_enabled(); }
1503 MediaMemoryTracker::CollectReports(nsIHandleReportCallback
* aHandleReport
,
1504 nsISupports
* aData
, bool aAnonymize
) {
1505 // NB: When resourceSizes' ref count goes to 0 the promise will report the
1506 // resources memory and finish the asynchronous memory report.
1507 RefPtr
<MediaDecoder::ResourceSizes
> resourceSizes
=
1508 new MediaDecoder::ResourceSizes(MediaMemoryTracker::MallocSizeOf
);
1510 nsCOMPtr
<nsIHandleReportCallback
> handleReport
= aHandleReport
;
1511 nsCOMPtr
<nsISupports
> data
= aData
;
1513 resourceSizes
->Promise()->Then(
1514 AbstractThread::MainThread(), __func__
,
1515 [handleReport
, data
](size_t size
) {
1516 handleReport
->Callback(
1517 ""_ns
, "explicit/media/resources"_ns
, KIND_HEAP
, UNITS_BYTES
,
1518 static_cast<int64_t>(size
),
1519 nsLiteralCString("Memory used by media resources including "
1520 "streaming buffers, caches, etc."),
1523 nsCOMPtr
<nsIMemoryReporterManager
> imgr
=
1524 do_GetService("@mozilla.org/memory-reporter-manager;1");
1530 [](size_t) { /* unused reject function */ });
1534 DecodersArray
& decoders
= Decoders();
1535 for (size_t i
= 0; i
< decoders
.Length(); ++i
) {
1536 MediaDecoder
* decoder
= decoders
[i
];
1537 video
+= static_cast<int64_t>(decoder
->SizeOfVideoQueue());
1538 audio
+= static_cast<int64_t>(decoder
->SizeOfAudioQueue());
1539 decoder
->AddSizeOfResources(resourceSizes
);
1542 MOZ_COLLECT_REPORT("explicit/media/decoded/video", KIND_HEAP
, UNITS_BYTES
,
1543 video
, "Memory used by decoded video frames.");
1545 MOZ_COLLECT_REPORT("explicit/media/decoded/audio", KIND_HEAP
, UNITS_BYTES
,
1546 audio
, "Memory used by decoded audio chunks.");
1551 MediaDecoderOwner
* MediaDecoder::GetOwner() const {
1552 MOZ_ASSERT(NS_IsMainThread());
1553 // mOwner is valid until shutdown.
1557 MediaDecoderOwner::NextFrameStatus
MediaDecoder::NextFrameBufferedStatus() {
1558 MOZ_ASSERT(NS_IsMainThread());
1559 // Next frame hasn't been decoded yet.
1560 // Use the buffered range to consider if we have the next frame available.
1561 auto currentPosition
= CurrentPosition();
1562 media::TimeInterval
interval(
1563 currentPosition
, currentPosition
+ DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED
);
1564 return GetBuffered().Contains(interval
)
1565 ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
1566 : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE
;
1569 void MediaDecoder::GetDebugInfo(dom::MediaDecoderDebugInfo
& aInfo
) {
1570 MOZ_ASSERT(NS_IsMainThread());
1571 CopyUTF8toUTF16(nsPrintfCString("%p", this), aInfo
.mInstance
);
1572 aInfo
.mChannels
= mInfo
? mInfo
->mAudio
.mChannels
: 0;
1573 aInfo
.mRate
= mInfo
? mInfo
->mAudio
.mRate
: 0;
1574 aInfo
.mHasAudio
= mInfo
? mInfo
->HasAudio() : false;
1575 aInfo
.mHasVideo
= mInfo
? mInfo
->HasVideo() : false;
1576 CopyUTF8toUTF16(MakeStringSpan(PlayStateStr()), aInfo
.mPlayState
);
1577 aInfo
.mContainerType
=
1578 NS_ConvertUTF8toUTF16(ContainerType().Type().AsString());
1581 RefPtr
<GenericPromise
> MediaDecoder::RequestDebugInfo(
1582 MediaDecoderDebugInfo
& aInfo
) {
1583 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
1584 if (!NS_IsMainThread()) {
1585 // Run the request on the main thread if it's not already.
1586 return InvokeAsync(AbstractThread::MainThread(), __func__
,
1587 [this, self
= RefPtr
{this}, &aInfo
]() {
1588 return RequestDebugInfo(aInfo
);
1591 GetDebugInfo(aInfo
);
1593 return mReader
->RequestDebugInfo(aInfo
.mReader
)
1594 ->Then(AbstractThread::MainThread(), __func__
,
1595 [this, self
= RefPtr
{this}, &aInfo
] {
1596 if (!GetStateMachine()) {
1597 return GenericPromise::CreateAndResolve(true, __func__
);
1599 return GetStateMachine()->RequestDebugInfo(aInfo
.mStateMachine
);
1603 void MediaDecoder::NotifyAudibleStateChanged() {
1604 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
1605 GetOwner()->SetAudibleState(mIsAudioDataAudible
);
1606 mTelemetryProbesReporter
->OnAudibleChanged(
1607 mIsAudioDataAudible
? TelemetryProbesReporter::AudibleState::eAudible
1608 : TelemetryProbesReporter::AudibleState::eNotAudible
);
1611 void MediaDecoder::NotifyVolumeChanged() {
1612 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
1613 mTelemetryProbesReporter
->OnMutedChanged(mVolume
== 0.f
);
1616 double MediaDecoder::GetTotalVideoPlayTimeInSeconds() const {
1617 return mTelemetryProbesReporter
->GetTotalVideoPlayTimeInSeconds();
1620 double MediaDecoder::GetTotalVideoHDRPlayTimeInSeconds() const {
1621 return mTelemetryProbesReporter
->GetTotalVideoHDRPlayTimeInSeconds();
1624 double MediaDecoder::GetVisibleVideoPlayTimeInSeconds() const {
1625 return mTelemetryProbesReporter
->GetVisibleVideoPlayTimeInSeconds();
1628 double MediaDecoder::GetInvisibleVideoPlayTimeInSeconds() const {
1629 return mTelemetryProbesReporter
->GetInvisibleVideoPlayTimeInSeconds();
1632 double MediaDecoder::GetVideoDecodeSuspendedTimeInSeconds() const {
1633 return mTelemetryProbesReporter
->GetVideoDecodeSuspendedTimeInSeconds();
1636 double MediaDecoder::GetTotalAudioPlayTimeInSeconds() const {
1637 return mTelemetryProbesReporter
->GetTotalAudioPlayTimeInSeconds();
1640 double MediaDecoder::GetAudiblePlayTimeInSeconds() const {
1641 return mTelemetryProbesReporter
->GetAudiblePlayTimeInSeconds();
1644 double MediaDecoder::GetInaudiblePlayTimeInSeconds() const {
1645 return mTelemetryProbesReporter
->GetInaudiblePlayTimeInSeconds();
1648 double MediaDecoder::GetMutedPlayTimeInSeconds() const {
1649 return mTelemetryProbesReporter
->GetMutedPlayTimeInSeconds();
1652 MediaMemoryTracker::MediaMemoryTracker() = default;
1654 void MediaMemoryTracker::InitMemoryReporter() {
1655 RegisterWeakAsyncMemoryReporter(this);
1658 MediaMemoryTracker::~MediaMemoryTracker() {
1659 UnregisterWeakMemoryReporter(this);
1662 } // namespace mozilla
1664 // avoid redefined macro in unified build
1667 #undef NS_DispatchToMainThread