no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / media / MediaDecoder.cpp
blob84836a87798a9e6ec9de48d3df90647cc19fdd56
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"
32 #include "nsError.h"
33 #include "nsIMemoryReporter.h"
34 #include "nsPrintfCString.h"
35 #include "nsServiceManagerUtils.h"
36 #include "nsTArray.h"
37 #include "WindowRenderer.h"
38 #include <algorithm>
39 #include <cmath>
40 #include <limits>
42 using namespace mozilla::dom;
43 using namespace mozilla::layers;
44 using namespace mozilla::media;
46 namespace mozilla {
48 // avoid redefined macro in unified build
49 #undef LOG
50 #undef DUMP
52 LazyLogModule gMediaDecoderLog("MediaDecoder");
54 #define LOG(x, ...) \
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) {
62 switch (aState) {
63 case MediaDecoder::PLAY_STATE_LOADING:
64 return "LOADING";
65 case MediaDecoder::PLAY_STATE_PAUSED:
66 return "PAUSED";
67 case MediaDecoder::PLAY_STATE_PLAYING:
68 return "PLAYING";
69 case MediaDecoder::PLAY_STATE_ENDED:
70 return "ENDED";
71 case MediaDecoder::PLAY_STATE_SHUTDOWN:
72 return "SHUTDOWN";
73 default:
74 MOZ_ASSERT_UNREACHABLE("Invalid playState.");
76 return "UNKNOWN";
79 class MediaMemoryTracker : public nsIMemoryReporter {
80 virtual ~MediaMemoryTracker();
82 NS_DECL_THREADSAFE_ISUPPORTS
83 NS_DECL_NSIMEMORYREPORTER
85 MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf);
87 MediaMemoryTracker();
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;
105 public:
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
137 // Utility process
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);
166 NotifyCompositor();
169 void MediaDecoder::Pause() {
170 MOZ_ASSERT(NS_IsMainThread());
171 MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
172 LOG("Pause");
173 if (mPlayState == PLAY_STATE_LOADING || IsEnded()) {
174 mNextState = PLAY_STATE_PAUSED;
175 return;
177 ChangeState(PLAY_STATE_PAUSED);
180 void MediaDecoder::SetVolume(double aVolume) {
181 MOZ_ASSERT(NS_IsMainThread());
182 mVolume = aVolume;
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.
294 // mDuration
295 mWatchManager.Watch(mStateMachineDuration, &MediaDecoder::DurationChanged);
297 // readyState
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);
303 // mLogicalPosition
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();
316 #undef INIT_MIRROR
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);
335 } else {
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;
347 mOwner = 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()) {
358 Shutdown();
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:
372 PlaybackEnded();
373 break;
374 case MediaPlaybackEvent::SeekStarted:
375 SeekingStarted();
376 break;
377 case MediaPlaybackEvent::Invalidate:
378 Invalidate();
379 break;
380 case MediaPlaybackEvent::EnterVideoSuspend:
381 GetOwner()->DispatchAsyncEvent(u"mozentervideosuspend"_ns);
382 mTelemetryProbesReporter->OnDecodeSuspended();
383 mIsVideoDecodingSuspended = true;
384 break;
385 case MediaPlaybackEvent::ExitVideoSuspend:
386 GetOwner()->DispatchAsyncEvent(u"mozexitvideosuspend"_ns);
387 mTelemetryProbesReporter->OnDecodeResumed();
388 mIsVideoDecodingSuspended = false;
389 break;
390 case MediaPlaybackEvent::StartVideoSuspendTimer:
391 GetOwner()->DispatchAsyncEvent(u"mozstartvideosuspendtimer"_ns);
392 break;
393 case MediaPlaybackEvent::CancelVideoSuspendTimer:
394 GetOwner()->DispatchAsyncEvent(u"mozcancelvideosuspendtimer"_ns);
395 break;
396 case MediaPlaybackEvent::VideoOnlySeekBegin:
397 GetOwner()->DispatchAsyncEvent(u"mozvideoonlyseekbegin"_ns);
398 break;
399 case MediaPlaybackEvent::VideoOnlySeekCompleted:
400 GetOwner()->DispatchAsyncEvent(u"mozvideoonlyseekcompleted"_ns);
401 break;
402 default:
403 break;
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
414 DecodeError(aError);
415 #else
416 if (aError != NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR &&
417 aError != NS_ERROR_DOM_MEDIA_CDM_PROXY_NOT_SUPPORTED_ERR) {
418 DecodeError(aError);
419 return;
422 // Already in shutting down decoder, no need to create another state machine.
423 if (mPlayState == PLAY_STATE_SHUTDOWN) {
424 return;
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);
439 DisconnectEvents();
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) {
444 # ifdef MOZ_WMF_CDM
445 if (aError.GetCDMProxy()->AsWMFCDMProxy()) {
446 needExternalEngine = true;
448 # endif
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
468 // us.
469 DiscardOngoingSeekIfExists();
471 discardStateMachine->BeginShutdown()->Then(
472 AbstractThread::MainThread(), __func__, [discardStateMachine] {});
473 #endif
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();
481 if (!doc) {
482 return;
484 DecoderDoctorDiagnostics diags;
485 diags.StoreEvent(doc, aEvent, __func__);
488 static const char* NextFrameStatusToStr(
489 MediaDecoderOwner::NextFrameStatus aStatus) {
490 switch (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";
502 return "UNKNOWN";
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;
512 UpdateReadyState();
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
524 // example.
525 mInfo->mVideo = aVideoInfo;
526 mInfo->mAudio = aAudioInfo;
528 Invalidate();
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{
546 aInfo.mMimeType,
547 aInfo.mDisplay.width,
548 aInfo.mDisplay.height,
549 videoFrameRate,
550 BitDepthForColorDepth(aInfo.mColorDepth),
553 LOG("Store benchmark: Video width=%d, height=%d, frameRate=%d, content "
554 "type = %s\n",
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);
572 ShutdownInternal();
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,
587 // set them now
588 SetStateMachineParameters();
590 return NS_OK;
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());
654 DisconnectEvents();
655 return mDecoderStateMachine->BeginShutdown();
658 void MediaDecoder::Play() {
659 MOZ_ASSERT(NS_IsMainThread());
661 NS_ASSERTION(mDecoderStateMachine != nullptr, "Should have state machine.");
662 LOG("Play");
663 if (mPlaybackRate == 0) {
664 return;
667 if (IsEnded()) {
668 Seek(0, SeekTarget::PrevSyncPoint);
669 return;
672 if (mPlayState == PLAY_STATE_LOADING) {
673 mNextState = PLAY_STATE_PLAYING;
674 return;
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.");
686 LOG("Seek");
687 auto time = TimeUnit::FromSeconds(aTime);
689 mLogicalPosition = aTime;
690 mLogicallySeeking = true;
691 SeekTarget target = SeekTarget(time, aSeekType);
692 CallSeek(target);
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) {
704 return;
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);
725 return;
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(),
756 aInfo->HasVideo());
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
767 // our new size.
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.
777 Invalidate();
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);
792 #endif
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
803 // these resources.
804 return;
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));
847 Invalidate();
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
852 // loading state.
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));
911 return;
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));
967 mPlayState = 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(
982 OwnerVisibility(),
983 TelemetryProbesReporter::MediaInfoToMediaContent(*mInfo),
984 mVolume == 0.f);
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) {
1013 currentPosition =
1014 std::max(currentPosition, mDuration.match(DurationToTimeUnit()));
1017 const PositionUpdate reason =
1018 GetPositionUpdateReason(mLogicalPosition, currentPosition);
1019 switch (reason) {
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();
1026 break;
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();
1035 break;
1036 default:
1037 MOZ_ASSERT(reason == PositionUpdate::eOther);
1038 SetLogicalPosition(currentPosition);
1039 break;
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.
1046 Invalidate();
1049 void MediaDecoder::SetLogicalPosition(const TimeUnit& aNewPosition) {
1050 MOZ_ASSERT(NS_IsMainThread());
1051 if (TimeUnit::FromSeconds(mLogicalPosition) == aNewPosition ||
1052 mLogicalPosition == aNewPosition.ToSeconds()) {
1053 return;
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())) {
1084 return;
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.");
1153 return;
1156 // Seeking is required when leaving suspend mode.
1157 if (!mMediaSeekable) {
1158 LOG("UpdateVideoDecodeMode(), set Normal because the media is not "
1159 "seekable");
1160 mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Normal);
1161 return;
1164 // If mHasSuspendTaint is set, never suspend the video decoder.
1165 if (mHasSuspendTaint) {
1166 LOG("UpdateVideoDecodeMode(), set Normal because the element has been "
1167 "tainted.");
1168 mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Normal);
1169 return;
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);
1177 return;
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 "
1183 "tree.");
1184 mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Normal);
1185 return;
1188 // If mForcedHidden is set, suspend the video decoder anyway.
1189 if (mForcedHidden) {
1190 LOG("UpdateVideoDecodeMode(), set Suspend because the element is forced to "
1191 "be suspended.");
1192 mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Suspend);
1193 return;
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 "
1199 "and hovered.");
1200 mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Normal);
1201 return;
1204 if (mIsOwnerInvisible) {
1205 LOG("UpdateVideoDecodeMode(), set Suspend because of invisible element.");
1206 mDecoderStateMachine->SetVideoDecodeMode(VideoDecodeMode::Suspend);
1207 } else {
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) {
1227 return;
1229 mSecondaryVideoContainer = aSecondaryVideoContainer;
1230 UpdateVideoDecodeMode();
1233 bool MediaDecoder::IsMediaSeekable() {
1234 MOZ_ASSERT(NS_IsMainThread());
1235 NS_ENSURE_TRUE(GetStateMachine(), false);
1236 return mMediaSeekable;
1239 namespace {
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) {
1245 return 0.0;
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);
1263 }; // namespace
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();
1290 } else {
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>()
1297 : duration);
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) {
1342 Pause();
1343 return;
1346 if (oldRate == 0 && !GetOwner()->GetPaused()) {
1347 // PlaybackRate is no longer null.
1348 // Restart the playback if the media was playing.
1349 Play();
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()
1407 : nullptr;
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();
1444 return 0;
1447 size_t MediaDecoder::SizeOfAudioQueue() {
1448 MOZ_ASSERT(NS_IsMainThread());
1449 if (mDecoderStateMachine) {
1450 return mDecoderStateMachine->SizeOfAudioQueue();
1452 return 0;
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));
1463 Unused << 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
1482 // given CDM proxy.
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});
1488 #endif
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(); }
1502 NS_IMETHODIMP
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."),
1521 data);
1523 nsCOMPtr<nsIMemoryReporterManager> imgr =
1524 do_GetService("@mozilla.org/memory-reporter-manager;1");
1526 if (imgr) {
1527 imgr->EndReport();
1530 [](size_t) { /* unused reject function */ });
1532 int64_t video = 0;
1533 int64_t audio = 0;
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.");
1548 return NS_OK;
1551 MediaDecoderOwner* MediaDecoder::GetOwner() const {
1552 MOZ_ASSERT(NS_IsMainThread());
1553 // mOwner is valid until shutdown.
1554 return mOwner;
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
1665 #undef DUMP
1666 #undef LOG
1667 #undef NS_DispatchToMainThread