1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 #include "ExternalEngineStateMachine.h"
7 #include "PerformanceRecorder.h"
8 #ifdef MOZ_WMF_MEDIA_ENGINE
9 # include "MFMediaEngineDecoderModule.h"
10 # include "mozilla/MFMediaEngineChild.h"
11 # include "mozilla/StaticPrefs_media.h"
13 #include "mozilla/AppShutdown.h"
14 #include "mozilla/Atomics.h"
15 #include "mozilla/ClearOnShutdown.h"
16 #include "mozilla/ProfilerLabels.h"
17 #include "mozilla/UniquePtr.h"
18 #include "mozilla/StaticMutex.h"
19 #include "mozilla/glean/GleanMetrics.h"
20 #include "nsThreadUtils.h"
21 #include "VideoUtils.h"
25 extern LazyLogModule gMediaDecoderLog
;
28 "Decoder=%p, State=%s, " x, mDecoderID, GetStateStr(), ##__VA_ARGS__
30 DDMOZ_LOG(gMediaDecoderLog, LogLevel::Debug, "Decoder=%p, State=%s, " x, \
31 mDecoderID, GetStateStr(), ##__VA_ARGS__)
32 #define LOGV(x, ...) \
33 DDMOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, "Decoder=%p, State=%s, " x, \
34 mDecoderID, GetStateStr(), ##__VA_ARGS__)
35 #define LOGW(x, ...) NS_WARNING(nsPrintfCString(FMT(x, ##__VA_ARGS__)).get())
36 #define LOGE(x, ...) \
37 NS_DebugBreak(NS_DEBUG_WARNING, \
38 nsPrintfCString(FMT(x, ##__VA_ARGS__)).get(), nullptr, \
41 const char* ExternalEngineEventToStr(ExternalEngineEvent aEvent
) {
42 #define EVENT_TO_STR(event) \
43 case ExternalEngineEvent::event: \
46 EVENT_TO_STR(LoadedMetaData
);
47 EVENT_TO_STR(LoadedFirstFrame
);
48 EVENT_TO_STR(LoadedData
);
49 EVENT_TO_STR(Waiting
);
50 EVENT_TO_STR(Playing
);
52 EVENT_TO_STR(BufferingStarted
);
53 EVENT_TO_STR(BufferingEnded
);
54 EVENT_TO_STR(Timeupdate
);
56 EVENT_TO_STR(RequestForAudio
);
57 EVENT_TO_STR(RequestForVideo
);
58 EVENT_TO_STR(AudioEnough
);
59 EVENT_TO_STR(VideoEnough
);
61 MOZ_ASSERT_UNREACHABLE("Undefined event!");
68 * This class monitors the amount of crash happened for a remote engine
69 * process. It the amount of crash of the remote process exceeds the defined
70 * threshold, then `ShouldRecoverProcess()` will return false to indicate that
71 * we should not keep spawning that remote process because it's too easy to
74 * In addition, we also have another mechanism in the media format reader
75 * (MFR) to detect crash amount of remote processes, but that would only
76 * happen during the decoding process. The main reason to choose using this
77 * simple monitor, instead of the mechanism in the MFR is because that
78 * mechanism can't detect every crash happening in the remote process, such as
79 * crash happening during initializing the remote engine, or setting the CDM
80 * pipepline, which can happen prior to decoding.
82 class ProcessCrashMonitor final
{
84 static void NotifyCrash() {
85 StaticMutexAutoLock
lock(sMutex
);
86 auto* monitor
= ProcessCrashMonitor::EnsureInstance();
90 monitor
->mCrashNums
++;
92 static bool ShouldRecoverProcess() {
93 StaticMutexAutoLock
lock(sMutex
);
94 auto* monitor
= ProcessCrashMonitor::EnsureInstance();
98 return monitor
->mCrashNums
<= monitor
->mMaxCrashes
;
102 ProcessCrashMonitor() : mCrashNums(0) {
103 #ifdef MOZ_WMF_MEDIA_ENGINE
104 mMaxCrashes
= StaticPrefs::media_wmf_media_engine_max_crashes();
109 ProcessCrashMonitor(const ProcessCrashMonitor
&) = delete;
110 ProcessCrashMonitor
& operator=(const ProcessCrashMonitor
&) = delete;
112 static ProcessCrashMonitor
* EnsureInstance() {
116 if (!sCrashMonitor
) {
117 sCrashMonitor
.reset(new ProcessCrashMonitor());
118 GetMainThreadSerialEventTarget()->Dispatch(
119 NS_NewRunnableFunction("ProcessCrashMonitor::EnsureInstance", [&] {
122 StaticMutexAutoLock
lock(sMutex
);
123 sCrashMonitor
.reset();
126 ShutdownPhase::XPCOMShutdown
);
129 return sCrashMonitor
.get();
132 static inline StaticMutex sMutex
;
133 static inline MOZ_RUNINIT UniquePtr
<ProcessCrashMonitor
> sCrashMonitor
;
134 static inline Atomic
<bool> sIsShutdown
{false};
137 uint32_t mMaxCrashes
;
141 const char* ExternalEngineStateMachine::StateToStr(State aNextState
) {
142 #define STATE_TO_STR(state) \
145 switch (aNextState
) {
146 STATE_TO_STR(InitEngine
);
147 STATE_TO_STR(ReadingMetadata
);
148 STATE_TO_STR(RunningEngine
);
149 STATE_TO_STR(SeekingData
);
150 STATE_TO_STR(ShutdownEngine
);
151 STATE_TO_STR(RecoverEngine
);
153 MOZ_ASSERT_UNREACHABLE("Undefined state!");
159 const char* ExternalEngineStateMachine::GetStateStr() const {
160 return StateToStr(mState
.mName
);
163 void ExternalEngineStateMachine::ChangeStateTo(State aNextState
) {
164 LOG("Change state : '%s' -> '%s' (play-state=%d)", StateToStr(mState
.mName
),
165 StateToStr(aNextState
), mPlayState
.Ref());
166 // Assert the possible state transitions.
168 mState
.IsReadingMetadata(),
169 aNextState
== State::InitEngine
|| aNextState
== State::ShutdownEngine
);
170 MOZ_ASSERT_IF(mState
.IsInitEngine(), aNextState
== State::RunningEngine
||
171 aNextState
== State::ShutdownEngine
);
172 MOZ_ASSERT_IF(mState
.IsRunningEngine(),
173 aNextState
== State::SeekingData
||
174 aNextState
== State::ShutdownEngine
||
175 aNextState
== State::RecoverEngine
);
176 MOZ_ASSERT_IF(mState
.IsSeekingData(),
177 aNextState
== State::RunningEngine
||
178 aNextState
== State::ShutdownEngine
||
179 aNextState
== State::RecoverEngine
);
180 MOZ_ASSERT_IF(mState
.IsShutdownEngine(), aNextState
== State::ShutdownEngine
);
182 mState
.IsRecoverEngine(),
183 aNextState
== State::SeekingData
|| aNextState
== State::ShutdownEngine
);
184 if (aNextState
== State::SeekingData
) {
185 mState
= StateObject({StateObject::SeekingData()});
186 } else if (aNextState
== State::InitEngine
) {
187 mState
= StateObject({StateObject::InitEngine()});
188 } else if (aNextState
== State::RunningEngine
) {
189 mState
= StateObject({StateObject::RunningEngine()});
190 } else if (aNextState
== State::ShutdownEngine
) {
191 mState
= StateObject({StateObject::ShutdownEngine()});
192 } else if (aNextState
== State::RecoverEngine
) {
193 mState
= StateObject({StateObject::RecoverEngine()});
195 MOZ_ASSERT_UNREACHABLE("Wrong state!");
197 NotifyAudibleStateChangeIfNeeded();
200 ExternalEngineStateMachine::ExternalEngineStateMachine(
201 MediaDecoder
* aDecoder
, MediaFormatReader
* aReader
)
202 : MediaDecoderStateMachineBase(aDecoder
, aReader
) {
203 LOG("Created ExternalEngineStateMachine");
204 MOZ_ASSERT(mState
.IsReadingMetadata());
208 ExternalEngineStateMachine::~ExternalEngineStateMachine() {
209 LOG("ExternalEngineStateMachine is destroyed");
212 void ExternalEngineStateMachine::InitEngine() {
213 MOZ_ASSERT(mState
.IsInitEngine() || mState
.IsRecoverEngine());
214 #ifdef MOZ_WMF_MEDIA_ENGINE
215 mEngine
.reset(new MFMediaEngineWrapper(this, mFrameStats
));
219 auto* state
= mState
.AsInitEngine();
220 ExternalPlaybackEngine::InitFlagSet flags
;
221 if (mMinimizePreroll
) {
222 flags
+= ExternalPlaybackEngine::InitFlag::ShouldPreload
;
224 if (mReader
->IsEncryptedCustomIdent()) {
225 flags
+= ExternalPlaybackEngine::InitFlag::EncryptedCustomIdent
;
227 state
->mInitPromise
= mEngine
->Init(*mInfo
, flags
);
229 ->Then(OwnerThread(), __func__
, this,
230 &ExternalEngineStateMachine::OnEngineInitSuccess
,
231 &ExternalEngineStateMachine::OnEngineInitFailure
)
232 ->Track(state
->mEngineInitRequest
);
236 void ExternalEngineStateMachine::OnEngineInitSuccess() {
238 AUTO_PROFILER_LABEL("ExternalEngineStateMachine::OnEngineInitSuccess",
240 MOZ_ASSERT(mState
.IsInitEngine() || mState
.IsRecoverEngine());
241 LOG("Initialized the external playback engine %" PRIu64
, mEngine
->Id());
242 auto* state
= mState
.AsInitEngine();
243 state
->mEngineInitRequest
.Complete();
244 mReader
->UpdateMediaEngineId(mEngine
->Id());
245 state
->mInitPromise
= nullptr;
246 if (mState
.IsInitEngine()) {
247 StartRunningEngine();
250 // We just recovered from CDM process crash, seek to previous position.
251 SeekTarget
target(mCurrentPosition
.Ref(), SeekTarget::Type::Accurate
);
255 void ExternalEngineStateMachine::OnEngineInitFailure() {
257 MOZ_ASSERT(mState
.IsInitEngine() || mState
.IsRecoverEngine());
258 LOGE("Failed to initialize the external playback engine");
259 auto* state
= mState
.AsInitEngine();
260 state
->mEngineInitRequest
.Complete();
261 state
->mInitPromise
= nullptr;
262 // Even if we failed to initialize the media engine, we still want to try
263 // again with the normal state machine, so don't return a fatal error, return
264 // NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR instead.
265 ReportTelemetry(NS_ERROR_DOM_MEDIA_MEDIA_ENGINE_INITIALIZATION_ERR
);
266 DecodeError(MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR
,
270 void ExternalEngineStateMachine::ReadMetadata() {
271 MOZ_ASSERT(NS_IsMainThread());
272 MOZ_ASSERT(mState
.IsReadingMetadata());
273 Unused
<< OwnerThread()->Dispatch(NS_NewRunnableFunction(
274 "ExternalEngineStateMachine::ReadMetadata",
275 [self
= RefPtr
<ExternalEngineStateMachine
>{this}, this] {
276 mReader
->ReadMetadata()
277 ->Then(OwnerThread(), __func__
, this,
278 &ExternalEngineStateMachine::OnMetadataRead
,
279 &ExternalEngineStateMachine::OnMetadataNotRead
)
280 ->Track(mState
.AsReadingMetadata()->mMetadataRequest
);
284 void ExternalEngineStateMachine::OnMetadataRead(MetadataHolder
&& aMetadata
) {
286 AUTO_PROFILER_LABEL("ExternalEngineStateMachine::OnMetadataRead",
288 MOZ_ASSERT(mState
.IsReadingMetadata());
289 LOG("OnMetadataRead");
291 mState
.AsReadingMetadata()->mMetadataRequest
.Complete();
292 mInfo
.emplace(*aMetadata
.mInfo
);
293 mMediaSeekable
= Info().mMediaSeekable
;
294 mMediaSeekableOnlyInBufferedRanges
=
295 Info().mMediaSeekableOnlyInBufferedRanges
;
297 if (!IsFormatSupportedByExternalEngine(*mInfo
)) {
298 // The external engine doesn't support the type, try to notify the decoder
299 // to use our own state machine again. Not a real "error", because it would
300 // fallback to another state machine.
302 MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR
));
306 #ifdef MOZ_WMF_MEDIA_ENGINE
307 // Only support encrypted playback. Not a real "error", because it would
308 // fallback to another state machine.
309 if ((!mInfo
->IsEncrypted() && !mReader
->IsEncryptedCustomIdent()) &&
310 StaticPrefs::media_wmf_media_engine_enabled() == 2) {
311 LOG("External engine only supports encrypted playback by the pref");
313 MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR
));
318 if (Info().mMetadataDuration
.isSome()) {
319 mDuration
= Info().mMetadataDuration
;
320 } else if (Info().mUnadjustedMetadataEndTime
.isSome()) {
321 const media::TimeUnit unadjusted
= Info().mUnadjustedMetadataEndTime
.ref();
322 const media::TimeUnit adjustment
= Info().mStartTime
;
323 mInfo
->mMetadataDuration
.emplace(unadjusted
- adjustment
);
324 mDuration
= Info().mMetadataDuration
;
327 // If we don't know the duration by this point, we assume infinity, per spec.
328 if (mDuration
.Ref().isNothing()) {
329 mDuration
= Some(media::TimeUnit::FromInfinity());
331 MOZ_ASSERT(mDuration
.Ref().isSome());
333 if (mInfo
->HasVideo()) {
334 mVideoDisplay
= mInfo
->mVideo
.mDisplay
;
337 LOG("Metadata loaded : a=%s, v=%s, size=[%dx%d], duration=%s",
338 mInfo
->HasAudio() ? mInfo
->mAudio
.mMimeType
.get() : "none",
339 mInfo
->HasVideo() ? mInfo
->mVideo
.mMimeType
.get() : "none",
340 mVideoDisplay
.width
, mVideoDisplay
.height
,
341 mDuration
.Ref()->ToString().get());
343 mMetadataLoadedEvent
.Notify(std::move(aMetadata
.mInfo
),
344 std::move(aMetadata
.mTags
),
345 MediaDecoderEventVisibility::Observable
);
346 ChangeStateTo(State::InitEngine
);
350 void ExternalEngineStateMachine::OnMetadataNotRead(const MediaResult
& aError
) {
352 MOZ_ASSERT(mState
.IsReadingMetadata());
353 LOGE("Decode metadata failed, shutting down decoder");
354 mState
.AsReadingMetadata()->mMetadataRequest
.Complete();
355 ReportTelemetry(aError
);
359 bool ExternalEngineStateMachine::IsFormatSupportedByExternalEngine(
360 const MediaInfo
& aInfo
) {
362 MOZ_ASSERT(mState
.IsReadingMetadata());
363 #ifdef MOZ_WMF_MEDIA_ENGINE
364 const bool audioSupported
=
366 MFMediaEngineDecoderModule::SupportsConfig(aInfo
.mAudio
);
367 const bool videoSupported
=
369 MFMediaEngineDecoderModule::SupportsConfig(aInfo
.mVideo
);
370 LOG("audio=%s (supported=%d), video=%s(supported=%d)",
371 aInfo
.HasAudio() ? aInfo
.mAudio
.mMimeType
.get() : "none", audioSupported
,
372 aInfo
.HasVideo() ? aInfo
.mVideo
.mMimeType
.get() : "none", videoSupported
);
373 return audioSupported
&& videoSupported
;
379 RefPtr
<MediaDecoder::SeekPromise
> ExternalEngineStateMachine::InvokeSeek(
380 const SeekTarget
& aTarget
) {
382 OwnerThread(), __func__
,
383 [self
= RefPtr
<ExternalEngineStateMachine
>(this), this,
384 target
= aTarget
]() -> RefPtr
<MediaDecoder::SeekPromise
> {
386 if (!mEngine
|| !mEngine
->IsInited()) {
387 LOG("Can't perform seek (%" PRId64
") now, add a pending seek task",
388 target
.GetTime().ToMicroseconds());
389 // We haven't added any pending seek before
390 if (mPendingSeek
.mPromise
.IsEmpty()) {
391 mPendingTasks
.AppendElement(NS_NewRunnableFunction(
392 "ExternalEngineStateMachine::InvokeSeek",
393 [self
= RefPtr
{this}, this] {
394 if (!mPendingSeek
.Exists()) {
397 Seek(*mPendingSeek
.mTarget
)
398 ->Then(OwnerThread(), __func__
,
399 [self
= RefPtr
{this},
400 this](const MediaDecoder::SeekPromise::
401 ResolveOrRejectValue
& aVal
) {
402 mPendingSeekRequest
.Complete();
403 if (aVal
.IsResolve()) {
404 mPendingSeek
.Resolve(__func__
);
406 mPendingSeek
.RejectIfExists(__func__
);
408 mPendingSeek
= SeekJob();
410 ->Track(mPendingSeekRequest
);
413 // Reject previous pending promise, as we will create a new one
414 LOG("Replace previous pending seek with a new one");
415 mPendingSeek
.RejectIfExists(__func__
);
416 mPendingSeekRequest
.DisconnectIfExists();
418 mPendingSeek
.mTarget
= Some(target
);
419 return mPendingSeek
.mPromise
.Ensure(__func__
);
421 if (mPendingSeek
.Exists()) {
422 LOG("Discard pending seek because another new seek happens");
423 mPendingSeek
.RejectIfExists(__func__
);
424 mPendingSeek
= SeekJob();
425 mPendingSeekRequest
.DisconnectIfExists();
427 return self
->Seek(target
);
431 RefPtr
<MediaDecoder::SeekPromise
> ExternalEngineStateMachine::Seek(
432 const SeekTarget
& aTarget
) {
434 if (!mState
.IsRunningEngine() && !mState
.IsSeekingData() &&
435 !mState
.IsRecoverEngine()) {
436 MOZ_ASSERT(false, "Can't seek due to unsupported state.");
437 return MediaDecoder::SeekPromise::CreateAndReject(true, __func__
);
439 // We don't support these type of seek, because they're depending on the
440 // implementation of the external engine, which might not be supported.
441 if (aTarget
.IsNextFrame() || aTarget
.IsVideoOnly()) {
442 return MediaDecoder::SeekPromise::CreateAndReject(true, __func__
);
445 LOG("Start seeking to %" PRId64
, aTarget
.GetTime().ToMicroseconds());
446 auto* state
= mState
.AsSeekingData();
448 // We're in other states, so change the state to seeking.
449 ChangeStateTo(State::SeekingData
);
450 state
= mState
.AsSeekingData();
452 state
->SetTarget(aTarget
);
454 // Update related status.
455 mSentPlaybackEndedEvent
= false;
456 mOnPlaybackEvent
.Notify(MediaPlaybackEvent::SeekStarted
);
457 mOnNextFrameStatus
.Notify(MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING
);
459 // Notify the external playback engine about seeking. After the engine changes
460 // its current time, it would send `seeked` event.
461 mEngine
->Seek(aTarget
.GetTime());
462 state
->mWaitingEngineSeeked
= true;
464 return state
->mSeekJob
.mPromise
.Ensure(__func__
);
467 void ExternalEngineStateMachine::SeekReader() {
469 MOZ_ASSERT(mState
.IsSeekingData());
470 auto* state
= mState
.AsSeekingData();
472 // Reset the reader first and ask it to perform a demuxer seek.
474 state
->mWaitingReaderSeeked
= true;
475 LOG("Seek reader to %" PRId64
, state
->GetTargetTime().ToMicroseconds());
476 mReader
->Seek(state
->mSeekJob
.mTarget
.ref())
477 ->Then(OwnerThread(), __func__
, this,
478 &ExternalEngineStateMachine::OnSeekResolved
,
479 &ExternalEngineStateMachine::OnSeekRejected
)
480 ->Track(state
->mSeekRequest
);
483 void ExternalEngineStateMachine::OnSeekResolved(const media::TimeUnit
& aUnit
) {
484 AUTO_PROFILER_LABEL("ExternalEngineStateMachine::OnSeekResolved",
487 MOZ_ASSERT(mState
.IsSeekingData());
488 auto* state
= mState
.AsSeekingData();
490 LOG("OnReaderSeekResolved");
491 state
->mSeekRequest
.Complete();
492 state
->mWaitingReaderSeeked
= false;
494 // Start sending new data to the external playback engine.
496 mHasEnoughAudio
= false;
500 mHasEnoughVideo
= false;
503 CheckIfSeekCompleted();
506 void ExternalEngineStateMachine::OnSeekRejected(
507 const SeekRejectValue
& aReject
) {
508 AUTO_PROFILER_LABEL("ExternalEngineStateMachine::OnSeekRejected",
511 MOZ_ASSERT(mState
.IsSeekingData());
512 auto* state
= mState
.AsSeekingData();
514 LOG("OnReaderSeekRejected");
515 state
->mSeekRequest
.Complete();
516 if (aReject
.mError
== NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA
) {
517 LOG("OnSeekRejected reason=WAITING_FOR_DATA type=%s",
518 MediaData::EnumValueToString(aReject
.mType
));
519 MOZ_ASSERT_IF(aReject
.mType
== MediaData::Type::AUDIO_DATA
,
520 !IsRequestingAudioData());
521 MOZ_ASSERT_IF(aReject
.mType
== MediaData::Type::VIDEO_DATA
,
522 !IsRequestingVideoData());
523 MOZ_ASSERT_IF(aReject
.mType
== MediaData::Type::AUDIO_DATA
,
524 !IsWaitingAudioData());
525 MOZ_ASSERT_IF(aReject
.mType
== MediaData::Type::VIDEO_DATA
,
526 !IsWaitingVideoData());
528 // Fire 'waiting' to notify the player that we are waiting for data.
529 mOnNextFrameStatus
.Notify(
530 MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING
);
531 WaitForData(aReject
.mType
);
535 if (aReject
.mError
== NS_ERROR_DOM_MEDIA_END_OF_STREAM
) {
536 EndOfStream(aReject
.mType
);
540 MOZ_ASSERT(NS_FAILED(aReject
.mError
),
541 "Cancels should also disconnect mSeekRequest");
542 state
->RejectIfExists(__func__
);
543 ReportTelemetry(aReject
.mError
);
544 DecodeError(aReject
.mError
);
547 bool ExternalEngineStateMachine::IsSeeking() {
549 const auto* state
= mState
.AsSeekingData();
550 return state
&& state
->IsSeeking();
553 void ExternalEngineStateMachine::CheckIfSeekCompleted() {
555 MOZ_ASSERT(mState
.IsSeekingData());
556 auto* state
= mState
.AsSeekingData();
557 if (state
->mWaitingEngineSeeked
|| state
->mWaitingReaderSeeked
) {
558 LOG("Seek hasn't been completed yet, waitEngineSeeked=%d, "
559 "waitReaderSeeked=%d",
560 state
->mWaitingEngineSeeked
, state
->mWaitingReaderSeeked
);
564 // As seeking should be accurate and we can't control the exact timing inside
565 // the external media engine. We always set the newCurrentTime = seekTime
566 // so that the updated HTMLMediaElement.currentTime will always be the seek
568 if (state
->GetTargetTime() != mCurrentPosition
) {
569 LOG("Force adjusting current time (%" PRId64
570 ") to match to target (%" PRId64
")",
571 mCurrentPosition
.Ref().ToMicroseconds(),
572 state
->GetTargetTime().ToMicroseconds());
573 mCurrentPosition
= state
->GetTargetTime();
576 LOG("Seek completed");
577 state
->Resolve(__func__
);
578 mOnPlaybackEvent
.Notify(MediaPlaybackEvent::Invalidate
);
579 mOnNextFrameStatus
.Notify(MediaDecoderOwner::NEXT_FRAME_AVAILABLE
);
580 StartRunningEngine();
583 void ExternalEngineStateMachine::ResetDecode() {
590 MediaFormatReader::TrackSet tracks
;
592 mVideoDataRequest
.DisconnectIfExists();
593 mVideoWaitRequest
.DisconnectIfExists();
594 tracks
+= TrackInfo::kVideoTrack
;
597 mAudioDataRequest
.DisconnectIfExists();
598 mAudioWaitRequest
.DisconnectIfExists();
599 tracks
+= TrackInfo::kAudioTrack
;
601 mReader
->ResetDecode(tracks
);
604 RefPtr
<GenericPromise
> ExternalEngineStateMachine::InvokeSetSink(
605 const RefPtr
<AudioDeviceInfo
>& aSink
) {
606 MOZ_ASSERT(NS_IsMainThread());
607 // TODO : can media engine support this?
608 return GenericPromise::CreateAndReject(NS_ERROR_FAILURE
, __func__
);
611 RefPtr
<ShutdownPromise
> ExternalEngineStateMachine::Shutdown() {
613 if (mState
.IsShutdownEngine()) {
614 LOG("Already shutdown");
615 return mState
.AsShutdownEngine()->mShutdown
;
619 ChangeStateTo(State::ShutdownEngine
);
622 mAudioDataRequest
.DisconnectIfExists();
623 mVideoDataRequest
.DisconnectIfExists();
624 mAudioWaitRequest
.DisconnectIfExists();
625 mVideoWaitRequest
.DisconnectIfExists();
627 mDuration
.DisconnectAll();
628 mCurrentPosition
.DisconnectAll();
629 mIsAudioDataAudible
.DisconnectAll();
631 mMetadataManager
.Disconnect();
633 mSetCDMProxyPromise
.RejectIfExists(NS_ERROR_DOM_MEDIA_ABORT_ERR
, __func__
);
634 mSetCDMProxyRequest
.DisconnectIfExists();
636 mPendingSeek
.RejectIfExists(__func__
);
637 mPendingSeekRequest
.DisconnectIfExists();
639 mPendingTasks
.Clear();
645 auto* state
= mState
.AsShutdownEngine();
646 state
->mShutdown
= mReader
->Shutdown()->Then(
647 OwnerThread(), __func__
, [self
= RefPtr
{this}, this]() {
648 LOG("Shutting down state machine task queue");
649 return OwnerThread()->BeginShutdown();
651 return state
->mShutdown
;
654 void ExternalEngineStateMachine::BufferedRangeUpdated() {
656 AUTO_PROFILER_LABEL("ExternalEngineStateMachine::BufferedRangeUpdated",
659 // While playing an unseekable stream of unknown duration, mDuration
660 // is updated as we play. But if data is being downloaded
661 // faster than played, mDuration won't reflect the end of playable data
662 // since we haven't played the frame at the end of buffered data. So update
663 // mDuration here as new data is downloaded to prevent such a lag.
664 if (mBuffered
.Ref().IsInvalid()) {
669 media::TimeUnit end
{mBuffered
.Ref().GetEnd(&exists
)};
674 // Use estimated duration from buffer ranges when mDuration is unknown or
675 // the estimated duration is larger.
676 if (mDuration
.Ref().isNothing() || mDuration
.Ref()->IsInfinite() ||
677 end
> mDuration
.Ref().ref()) {
678 mDuration
= Some(end
);
679 DDLOG(DDLogCategory::Property
, "duration_us",
680 mDuration
.Ref()->ToMicroseconds());
684 #define PERFORM_WHEN_ALLOW(Func) \
686 if (mState.IsShutdownEngine() || mHasFatalError || \
687 AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { \
690 /* Initialzation is not done yet, postpone the operation */ \
691 if (!mEngine || !mEngine->IsInited()) { \
692 LOG("%s is called before init", __func__); \
693 mPendingTasks.AppendElement(NewRunnableMethod( \
694 __func__, this, &ExternalEngineStateMachine::Func)); \
699 void ExternalEngineStateMachine::SetPlaybackRate(double aPlaybackRate
) {
701 // TODO : consider to make `mPlaybackRate` a mirror to fit other usages like
702 // `mVolume` and `mPreservesPitch`.
703 mPlaybackRate
= aPlaybackRate
;
704 PlaybackRateChanged();
707 void ExternalEngineStateMachine::PlaybackRateChanged() {
709 PERFORM_WHEN_ALLOW(PlaybackRateChanged
);
710 MOZ_ASSERT(mState
.IsReadingMetadata() || mState
.IsRunningEngine() ||
711 mState
.IsSeekingData());
712 mEngine
->SetPlaybackRate(mPlaybackRate
);
715 void ExternalEngineStateMachine::VolumeChanged() {
717 PERFORM_WHEN_ALLOW(VolumeChanged
);
718 MOZ_ASSERT(mState
.IsReadingMetadata() || mState
.IsRunningEngine() ||
719 mState
.IsSeekingData());
720 mEngine
->SetVolume(mVolume
);
723 void ExternalEngineStateMachine::PreservesPitchChanged() {
725 PERFORM_WHEN_ALLOW(PreservesPitchChanged
);
726 MOZ_ASSERT(mState
.IsReadingMetadata() || mState
.IsRunningEngine() ||
727 mState
.IsSeekingData());
728 mEngine
->SetPreservesPitch(mPreservesPitch
);
731 void ExternalEngineStateMachine::PlayStateChanged() {
733 PERFORM_WHEN_ALLOW(PlayStateChanged
);
734 MOZ_ASSERT(mState
.IsReadingMetadata() || mState
.IsRunningEngine() ||
735 mState
.IsSeekingData());
736 if (mPlayState
== MediaDecoder::PLAY_STATE_PLAYING
) {
738 } else if (mPlayState
== MediaDecoder::PLAY_STATE_PAUSED
) {
741 NotifyAudibleStateChangeIfNeeded();
744 void ExternalEngineStateMachine::LoopingChanged() {
746 PERFORM_WHEN_ALLOW(LoopingChanged
);
747 MOZ_ASSERT(mState
.IsReadingMetadata() || mState
.IsRunningEngine() ||
748 mState
.IsSeekingData());
749 mEngine
->SetLooping(mLooping
);
752 #undef PERFORM_WHEN_ALLOW
754 void ExternalEngineStateMachine::EndOfStream(MediaData::Type aType
) {
756 MOZ_ASSERT(mState
.IsRunningEngine() || mState
.IsSeekingData());
757 static auto DataTypeToTrackType
= [](const MediaData::Type
& aType
) {
758 if (aType
== MediaData::Type::VIDEO_DATA
) {
759 return TrackInfo::TrackType::kVideoTrack
;
761 if (aType
== MediaData::Type::AUDIO_DATA
) {
762 return TrackInfo::TrackType::kAudioTrack
;
764 return TrackInfo::TrackType::kUndefinedTrack
;
766 mEngine
->NotifyEndOfStream(DataTypeToTrackType(aType
));
769 void ExternalEngineStateMachine::WaitForData(MediaData::Type aType
) {
771 MOZ_ASSERT(mState
.IsRunningEngine() || mState
.IsSeekingData());
772 AUTO_PROFILER_LABEL("ExternalEngineStateMachine::WaitForData",
774 MOZ_ASSERT(aType
== MediaData::Type::AUDIO_DATA
||
775 aType
== MediaData::Type::VIDEO_DATA
);
778 RefPtr
<ExternalEngineStateMachine
> self
= this;
779 if (aType
== MediaData::Type::AUDIO_DATA
) {
780 MOZ_ASSERT(HasAudio());
781 mReader
->WaitForData(MediaData::Type::AUDIO_DATA
)
783 OwnerThread(), __func__
,
784 [self
, this](MediaData::Type aType
) {
786 "ExternalEngineStateMachine::WaitForData:AudioResolved",
788 MOZ_ASSERT(aType
== MediaData::Type::AUDIO_DATA
);
789 LOG("Done waiting for audio data");
790 mAudioWaitRequest
.Complete();
791 MaybeFinishWaitForData();
793 [self
, this](const WaitForDataRejectValue
& aRejection
) {
795 "ExternalEngineStateMachine::WaitForData:AudioRejected",
797 mAudioWaitRequest
.Complete();
798 DecodeError(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA
);
800 ->Track(mAudioWaitRequest
);
802 MOZ_ASSERT(HasVideo());
803 mReader
->WaitForData(MediaData::Type::VIDEO_DATA
)
805 OwnerThread(), __func__
,
806 [self
, this](MediaData::Type aType
) {
808 "ExternalEngineStateMachine::WaitForData:VideoResolved",
810 MOZ_ASSERT(aType
== MediaData::Type::VIDEO_DATA
);
811 LOG("Done waiting for video data");
812 mVideoWaitRequest
.Complete();
813 MaybeFinishWaitForData();
815 [self
, this](const WaitForDataRejectValue
& aRejection
) {
817 "ExternalEngineStateMachine::WaitForData:VideoRejected",
819 mVideoWaitRequest
.Complete();
820 DecodeError(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA
);
822 ->Track(mVideoWaitRequest
);
826 void ExternalEngineStateMachine::MaybeFinishWaitForData() {
828 MOZ_ASSERT(mState
.IsRunningEngine() || mState
.IsSeekingData());
830 bool isWaitingForAudio
= HasAudio() && mAudioWaitRequest
.Exists();
831 bool isWaitingForVideo
= HasVideo() && mVideoWaitRequest
.Exists();
832 if (isWaitingForAudio
|| isWaitingForVideo
) {
833 LOG("Still waiting for data (waitAudio=%d, waitVideo=%d)",
834 isWaitingForAudio
, isWaitingForVideo
);
838 LOG("Finished waiting for data");
839 if (mState
.IsSeekingData()) {
844 RunningEngineUpdate(MediaData::Type::AUDIO_DATA
);
847 RunningEngineUpdate(MediaData::Type::VIDEO_DATA
);
851 void ExternalEngineStateMachine::StartRunningEngine() {
852 ChangeStateTo(State::RunningEngine
);
853 // Manually check the play state because the engine might be recovered from
854 // crash or just get recreated, so PlayStateChanged() won't be triggered.
855 if (mPlayState
== MediaDecoder::PLAY_STATE_PLAYING
) {
859 RunningEngineUpdate(MediaData::Type::AUDIO_DATA
);
862 RunningEngineUpdate(MediaData::Type::VIDEO_DATA
);
864 // Run tasks which was called before the engine is ready.
865 if (!mPendingTasks
.IsEmpty()) {
866 for (auto& task
: mPendingTasks
) {
867 Unused
<< OwnerThread()->Dispatch(task
.forget());
869 mPendingTasks
.Clear();
873 void ExternalEngineStateMachine::RunningEngineUpdate(MediaData::Type aType
) {
875 MOZ_ASSERT(mState
.IsRunningEngine() || mState
.IsSeekingData());
876 if (aType
== MediaData::Type::AUDIO_DATA
&& !mHasEnoughAudio
) {
879 if (aType
== MediaData::Type::VIDEO_DATA
&& !mHasEnoughVideo
) {
884 void ExternalEngineStateMachine::OnRequestAudio() {
886 MOZ_ASSERT(mState
.IsRunningEngine() || mState
.IsSeekingData());
892 if (IsRequestingAudioData() || mAudioWaitRequest
.Exists() || IsSeeking()) {
894 "No need to request audio, isRequesting=%d, waitingAudio=%d, "
896 IsRequestingAudioData(), mAudioWaitRequest
.Exists(), IsSeeking());
900 PerformanceRecorder
<PlaybackStage
> perfRecorder(MediaStage::RequestData
);
901 RefPtr
<ExternalEngineStateMachine
> self
= this;
902 mReader
->RequestAudioData()
904 OwnerThread(), __func__
,
905 [this, self
, perfRecorder(std::move(perfRecorder
))](
906 const RefPtr
<AudioData
>& aAudio
) mutable {
907 perfRecorder
.Record();
908 mAudioDataRequest
.Complete();
910 "ExternalEngineStateMachine::OnRequestAudio:Resolved",
913 RunningEngineUpdate(MediaData::Type::AUDIO_DATA
);
915 [this, self
](const MediaResult
& aError
) {
916 mAudioDataRequest
.Complete();
918 "ExternalEngineStateMachine::OnRequestAudio:Rejected",
920 LOG("OnRequestAudio ErrorName=%s Message=%s",
921 aError
.ErrorName().get(), aError
.Message().get());
922 switch (aError
.Code()) {
923 case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA
:
924 WaitForData(MediaData::Type::AUDIO_DATA
);
926 case NS_ERROR_DOM_MEDIA_CANCELED
:
929 case NS_ERROR_DOM_MEDIA_END_OF_STREAM
:
930 LOG("Reach to the end, no more audio data");
931 EndOfStream(MediaData::Type::AUDIO_DATA
);
933 case NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR
:
934 // We will handle the process crash in `NotifyErrorInternal()`
935 // so here just silently ignore this.
938 ReportTelemetry(aError
);
942 ->Track(mAudioDataRequest
);
945 void ExternalEngineStateMachine::OnRequestVideo() {
947 MOZ_ASSERT(mState
.IsRunningEngine() || mState
.IsSeekingData());
953 if (IsTrackingVideoData() || IsSeeking()) {
955 "No need to request video, isRequesting=%d, waitingVideo=%d, "
957 IsRequestingVideoData(), mVideoWaitRequest
.Exists(), IsSeeking());
961 PerformanceRecorder
<PlaybackStage
> perfRecorder(MediaStage::RequestData
,
962 Info().mVideo
.mImage
.height
);
963 RefPtr
<ExternalEngineStateMachine
> self
= this;
964 mReader
->RequestVideoData(GetVideoThreshold(), false)
966 OwnerThread(), __func__
,
967 [this, self
, perfRecorder(std::move(perfRecorder
))](
968 const RefPtr
<VideoData
>& aVideo
) mutable {
969 perfRecorder
.Record();
970 mVideoDataRequest
.Complete();
972 "ExternalEngineStateMachine::OnRequestVideo:Resolved",
975 if (!mHasReceivedFirstDecodedVideoFrame
) {
976 mHasReceivedFirstDecodedVideoFrame
= true;
977 OnLoadedFirstFrame();
979 RunningEngineUpdate(MediaData::Type::VIDEO_DATA
);
980 // Send image to PIP window.
981 if (mSecondaryVideoContainer
.Ref()) {
982 mSecondaryVideoContainer
.Ref()->SetCurrentFrame(
983 mVideoDisplay
, aVideo
->mImage
, TimeStamp::Now(),
984 media::TimeUnit::Invalid(), aVideo
->mTime
);
986 mVideoFrameContainer
->SetCurrentFrame(
987 mVideoDisplay
, aVideo
->mImage
, TimeStamp::Now(),
988 media::TimeUnit::Invalid(), aVideo
->mTime
);
991 [this, self
](const MediaResult
& aError
) {
992 mVideoDataRequest
.Complete();
994 "ExternalEngineStateMachine::OnRequestVideo:Rejected",
996 LOG("OnRequestVideo ErrorName=%s Message=%s",
997 aError
.ErrorName().get(), aError
.Message().get());
998 switch (aError
.Code()) {
999 case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA
:
1000 WaitForData(MediaData::Type::VIDEO_DATA
);
1002 case NS_ERROR_DOM_MEDIA_CANCELED
:
1005 case NS_ERROR_DOM_MEDIA_END_OF_STREAM
:
1006 LOG("Reach to the end, no more video data");
1007 EndOfStream(MediaData::Type::VIDEO_DATA
);
1009 case NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR
:
1010 // We will handle the process crash in `NotifyErrorInternal()`
1011 // so here just silently ignore this.
1014 ReportTelemetry(aError
);
1015 DecodeError(aError
);
1018 ->Track(mVideoDataRequest
);
1021 void ExternalEngineStateMachine::OnLoadedFirstFrame() {
1022 AssertOnTaskQueue();
1023 // We will wait until receive the first video frame.
1024 if (mInfo
->HasVideo() && !mHasReceivedFirstDecodedVideoFrame
) {
1025 LOGV("Hasn't received first decoded video frame");
1028 LOGV("OnLoadedFirstFrame");
1029 MediaDecoderEventVisibility visibility
=
1030 mSentFirstFrameLoadedEvent
? MediaDecoderEventVisibility::Suppressed
1031 : MediaDecoderEventVisibility::Observable
;
1032 mSentFirstFrameLoadedEvent
= true;
1033 mFirstFrameLoadedEvent
.Notify(UniquePtr
<MediaInfo
>(new MediaInfo(Info())),
1035 mOnNextFrameStatus
.Notify(MediaDecoderOwner::NEXT_FRAME_AVAILABLE
);
1038 void ExternalEngineStateMachine::OnLoadedData() {
1039 AssertOnTaskQueue();
1040 // In case the external engine doesn't send the first frame loaded event
1042 if (!mSentFirstFrameLoadedEvent
) {
1043 OnLoadedFirstFrame();
1045 mOnNextFrameStatus
.Notify(MediaDecoderOwner::NEXT_FRAME_AVAILABLE
);
1048 void ExternalEngineStateMachine::OnWaiting() {
1049 AssertOnTaskQueue();
1050 mOnNextFrameStatus
.Notify(
1051 MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING
);
1054 void ExternalEngineStateMachine::OnPlaying() {
1055 AssertOnTaskQueue();
1056 mOnNextFrameStatus
.Notify(MediaDecoderOwner::NEXT_FRAME_AVAILABLE
);
1059 void ExternalEngineStateMachine::OnSeeked() {
1060 AssertOnTaskQueue();
1061 if (!mState
.IsSeekingData()) {
1062 LOG("Engine Seeking has been completed, ignore the event");
1065 MOZ_ASSERT(mState
.IsSeekingData());
1067 const auto currentTime
= mEngine
->GetCurrentPosition();
1068 auto* state
= mState
.AsSeekingData();
1069 LOG("OnEngineSeeked, target=%" PRId64
", currentTime=%" PRId64
,
1070 state
->GetTargetTime().ToMicroseconds(), currentTime
.ToMicroseconds());
1071 // It's possible to receive multiple seeked event if we seek the engine
1072 // before the previous seeking finishes, so we would wait until the last
1073 // seeking is finished.
1074 if (currentTime
>= state
->GetTargetTime()) {
1075 state
->mWaitingEngineSeeked
= false;
1076 CheckIfSeekCompleted();
1080 void ExternalEngineStateMachine::OnBufferingStarted() {
1081 AssertOnTaskQueue();
1082 mOnNextFrameStatus
.Notify(
1083 MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING
);
1085 WaitForData(MediaData::Type::AUDIO_DATA
);
1088 WaitForData(MediaData::Type::VIDEO_DATA
);
1092 void ExternalEngineStateMachine::OnBufferingEnded() {
1093 AssertOnTaskQueue();
1094 mOnNextFrameStatus
.Notify(MediaDecoderOwner::NEXT_FRAME_AVAILABLE
);
1097 void ExternalEngineStateMachine::OnEnded() {
1098 AssertOnTaskQueue();
1099 if (mSentPlaybackEndedEvent
) {
1102 LOG("Playback is ended");
1103 mOnNextFrameStatus
.Notify(MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE
);
1104 mOnPlaybackEvent
.Notify(MediaPlaybackEvent::PlaybackEnded
);
1105 mSentPlaybackEndedEvent
= true;
1108 void ExternalEngineStateMachine::OnTimeupdate() {
1109 AssertOnTaskQueue();
1113 mCurrentPosition
= mEngine
->GetCurrentPosition();
1114 if (mDuration
.Ref().ref() < mCurrentPosition
.Ref()) {
1115 mDuration
= Some(mCurrentPosition
.Ref());
1119 void ExternalEngineStateMachine::NotifyEventInternal(
1120 ExternalEngineEvent aEvent
) {
1121 AssertOnTaskQueue();
1122 AUTO_PROFILER_LABEL("ExternalEngineStateMachine::NotifyEventInternal",
1124 LOG("Receive event %s", ExternalEngineEventToStr(aEvent
));
1125 if (mState
.IsShutdownEngine()) {
1129 case ExternalEngineEvent::LoadedMetaData
:
1130 // We read metadata by ourselves, ignore this if there is any.
1133 case ExternalEngineEvent::LoadedFirstFrame
:
1134 OnLoadedFirstFrame();
1136 case ExternalEngineEvent::LoadedData
:
1139 case ExternalEngineEvent::Waiting
:
1142 case ExternalEngineEvent::Playing
:
1145 case ExternalEngineEvent::Seeked
:
1148 case ExternalEngineEvent::BufferingStarted
:
1149 OnBufferingStarted();
1151 case ExternalEngineEvent::BufferingEnded
:
1154 case ExternalEngineEvent::Timeupdate
:
1157 case ExternalEngineEvent::Ended
:
1160 case ExternalEngineEvent::RequestForAudio
:
1161 mHasEnoughAudio
= false;
1162 if (ShouldRunEngineUpdateForRequest()) {
1163 RunningEngineUpdate(MediaData::Type::AUDIO_DATA
);
1166 case ExternalEngineEvent::RequestForVideo
:
1167 mHasEnoughVideo
= false;
1168 if (ShouldRunEngineUpdateForRequest()) {
1169 RunningEngineUpdate(MediaData::Type::VIDEO_DATA
);
1172 case ExternalEngineEvent::AudioEnough
:
1173 mHasEnoughAudio
= true;
1175 case ExternalEngineEvent::VideoEnough
:
1176 mHasEnoughVideo
= true;
1179 MOZ_ASSERT_UNREACHABLE("Undefined event!");
1184 bool ExternalEngineStateMachine::ShouldRunEngineUpdateForRequest() {
1185 // Running engine update will request new data, which could be run on
1186 // `RunningEngine` or `SeekingData` state. However, in `SeekingData` we should
1187 // only request new data after finishing reader seek, otherwise the reader
1188 // would start requesting data from a wrong position.
1189 return mState
.IsRunningEngine() ||
1190 (mState
.AsSeekingData() &&
1191 !mState
.AsSeekingData()->mWaitingReaderSeeked
);
1194 void ExternalEngineStateMachine::NotifyErrorInternal(
1195 const MediaResult
& aError
) {
1196 AssertOnTaskQueue();
1197 LOG("Engine error: %s", aError
.Description().get());
1198 if (aError
== NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR
) {
1199 // The external engine doesn't support the type, try to notify the decoder
1200 // to use our own state machine again.
1201 ReportTelemetry(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR
);
1203 MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR
));
1204 } else if (aError
== NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR
) {
1205 ReportTelemetry(NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR
);
1206 RecoverFromCDMProcessCrashIfNeeded();
1207 } else if (mState
.IsInitEngine() && mKeySystem
.IsEmpty()) {
1208 // If any error occurs during media engine initialization, we should attempt
1209 // to use another state machine for playback. Unless the key system is
1210 // already set, it indicates that playback can only be initiated via the
1211 // media engine. In this case, we will propagate the error and refrain
1212 // from trying another state machine.
1213 LOG("Error happened on the engine initialization, the media engine "
1214 "playback might not be supported");
1215 ReportTelemetry(NS_ERROR_DOM_MEDIA_MEDIA_ENGINE_INITIALIZATION_ERR
);
1217 MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR
));
1219 ReportTelemetry(aError
);
1220 DecodeError(aError
);
1224 void ExternalEngineStateMachine::NotifyResizingInternal(uint32_t aWidth
,
1226 LOG("video resize from [%d,%d] to [%d,%d]", mVideoDisplay
.width
,
1227 mVideoDisplay
.height
, aWidth
, aHeight
);
1228 mVideoDisplay
= gfx::IntSize
{aWidth
, aHeight
};
1231 void ExternalEngineStateMachine::RecoverFromCDMProcessCrashIfNeeded() {
1232 AssertOnTaskQueue();
1233 if (mState
.IsRecoverEngine()) {
1236 ProcessCrashMonitor::NotifyCrash();
1237 if (!ProcessCrashMonitor::ShouldRecoverProcess()) {
1238 LOG("CDM process has crashed too many times, abort recovery");
1240 MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR
));
1244 if (mState
.IsInitEngine()) {
1245 LOG("Failed on the engine initialization, the media engine playback might "
1246 "not be supported");
1248 MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR
));
1252 LOG("CDM process crashed, recover the engine again (last time=%" PRId64
")",
1253 mCurrentPosition
.Ref().ToMicroseconds());
1254 ChangeStateTo(State::RecoverEngine
);
1256 mVideoDataRequest
.DisconnectIfExists();
1257 mVideoWaitRequest
.DisconnectIfExists();
1260 mAudioDataRequest
.DisconnectIfExists();
1261 mAudioWaitRequest
.DisconnectIfExists();
1263 // Ask the reader to shutdown current decoders which are no longer available
1264 // due to the remote process crash.
1265 mReader
->ReleaseResources();
1269 media::TimeUnit
ExternalEngineStateMachine::GetVideoThreshold() {
1270 AssertOnTaskQueue();
1271 if (auto* state
= mState
.AsSeekingData()) {
1272 return state
->GetTargetTime();
1274 return mCurrentPosition
.Ref();
1277 void ExternalEngineStateMachine::UpdateSecondaryVideoContainer() {
1278 AssertOnTaskQueue();
1279 LOG("UpdateSecondaryVideoContainer=%p", mSecondaryVideoContainer
.Ref().get());
1280 mOnSecondaryVideoContainerInstalled
.Notify(mSecondaryVideoContainer
.Ref());
1283 RefPtr
<SetCDMPromise
> ExternalEngineStateMachine::SetCDMProxy(
1285 if (mState
.IsShutdownEngine()) {
1286 return SetCDMPromise::CreateAndReject(NS_ERROR_FAILURE
, __func__
);
1289 if (!mEngine
|| !mEngine
->IsInited()) {
1290 LOG("SetCDMProxy is called before init");
1291 mReader
->SetEncryptedCustomIdent();
1292 mPendingTasks
.AppendElement(NS_NewRunnableFunction(
1293 "ExternalEngineStateMachine::SetCDMProxy",
1294 [self
= RefPtr
{this}, proxy
= RefPtr
{aProxy
}, this] {
1296 ->Then(OwnerThread(), __func__
,
1297 [self
= RefPtr
{this},
1298 this](const SetCDMPromise::ResolveOrRejectValue
& aVal
) {
1299 mSetCDMProxyRequest
.Complete();
1300 if (aVal
.IsResolve()) {
1301 mSetCDMProxyPromise
.Resolve(true, __func__
);
1303 mSetCDMProxyPromise
.Reject(NS_ERROR_DOM_MEDIA_CDM_ERR
,
1307 ->Track(mSetCDMProxyRequest
);
1309 return mSetCDMProxyPromise
.Ensure(__func__
);
1312 // TODO : set CDM proxy again if we recreate the media engine after crash.
1313 mKeySystem
= NS_ConvertUTF16toUTF8(aProxy
->KeySystem());
1314 LOG("SetCDMProxy=%p (key-system=%s)", aProxy
, mKeySystem
.get());
1315 MOZ_DIAGNOSTIC_ASSERT(mEngine
);
1316 // TODO : we should check the result of setting CDM proxy in the MFCDM process
1317 if (!mEngine
->SetCDMProxy(aProxy
)) {
1318 LOG("Failed to set CDM proxy on the engine");
1319 return SetCDMPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CDM_ERR
, __func__
);
1321 return MediaDecoderStateMachineBase::SetCDMProxy(aProxy
);
1324 bool ExternalEngineStateMachine::IsCDMProxySupported(CDMProxy
* aProxy
) {
1327 // 1=enabled encrypted and clear, 2=enabled encrytped
1328 if (StaticPrefs::media_wmf_media_engine_enabled() != 1 &&
1329 StaticPrefs::media_wmf_media_engine_enabled() != 2) {
1333 // The CDM needs to be hosted in the same process of the external engine, and
1334 // only WMFCDM meets this requirement.
1335 return aProxy
->AsWMFCDMProxy();
1341 void ExternalEngineStateMachine::ReportTelemetry(const MediaResult
& aError
) {
1342 glean::mfcdm::ErrorExtra extraData
;
1343 extraData
.errorName
= Some(aError
.ErrorName());
1344 extraData
.currentState
= Some(nsAutoCString
{StateToStr(mState
.mName
)});
1345 nsAutoCString resolution
;
1347 if (mInfo
->HasAudio()) {
1348 extraData
.audioCodec
= Some(mInfo
->mAudio
.mMimeType
);
1350 if (mInfo
->HasVideo()) {
1351 extraData
.videoCodec
= Some(mInfo
->mVideo
.mMimeType
);
1352 DetermineResolutionForTelemetry(*mInfo
, resolution
);
1353 extraData
.resolution
= Some(resolution
);
1356 if (!mKeySystem
.IsEmpty()) {
1357 extraData
.keySystem
= Some(mKeySystem
);
1359 glean::mfcdm::error
.Record(Some(extraData
));
1360 if (MOZ_LOG_TEST(gMediaDecoderLog
, LogLevel::Debug
)) {
1361 nsPrintfCString logMessage
{"MFCDM Error event, error=%s",
1362 aError
.ErrorName().get()};
1364 if (mInfo
->HasAudio()) {
1366 nsPrintfCString
{", audio=%s", mInfo
->mAudio
.mMimeType
.get()});
1368 if (mInfo
->HasVideo()) {
1369 logMessage
.Append(nsPrintfCString
{", video=%s, resolution=%s",
1370 mInfo
->mVideo
.mMimeType
.get(),
1374 if (!mKeySystem
.IsEmpty()) {
1375 logMessage
.Append(nsPrintfCString
{", keySystem=%s", mKeySystem
.get()});
1377 LOG("%s", logMessage
.get());
1381 void ExternalEngineStateMachine::DecodeError(const MediaResult
& aError
) {
1382 if (aError
!= NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA
||
1383 aError
!= NS_ERROR_DOM_MEDIA_CANCELED
) {
1384 mHasFatalError
= true;
1386 MediaDecoderStateMachineBase ::DecodeError(aError
);
1389 void ExternalEngineStateMachine::NotifyAudibleStateChangeIfNeeded() {
1390 // Only perform a simple check because we can't access audio data from the
1392 mIsAudioDataAudible
= mInfo
&& HasAudio() &&
1393 mPlayState
== MediaDecoder::PLAY_STATE_PLAYING
&&
1394 mState
.IsRunningEngine();
1403 } // namespace mozilla