Backed out changeset f594e6f00208 (bug 1940883) for causing crashes in bug 1941164.
[gecko.git] / dom / media / ExternalEngineStateMachine.cpp
blob2fdd3325192a74f8cf3bd1058ab8034c8ad93556
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"
12 #endif
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"
23 namespace mozilla {
25 extern LazyLogModule gMediaDecoderLog;
27 #define FMT(x, ...) \
28 "Decoder=%p, State=%s, " x, mDecoderID, GetStateStr(), ##__VA_ARGS__
29 #define LOG(x, ...) \
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, \
39 __FILE__, __LINE__)
41 const char* ExternalEngineEventToStr(ExternalEngineEvent aEvent) {
42 #define EVENT_TO_STR(event) \
43 case ExternalEngineEvent::event: \
44 return #event
45 switch (aEvent) {
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);
51 EVENT_TO_STR(Seeked);
52 EVENT_TO_STR(BufferingStarted);
53 EVENT_TO_STR(BufferingEnded);
54 EVENT_TO_STR(Timeupdate);
55 EVENT_TO_STR(Ended);
56 EVENT_TO_STR(RequestForAudio);
57 EVENT_TO_STR(RequestForVideo);
58 EVENT_TO_STR(AudioEnough);
59 EVENT_TO_STR(VideoEnough);
60 default:
61 MOZ_ASSERT_UNREACHABLE("Undefined event!");
62 return "Undefined";
64 #undef EVENT_TO_STR
67 /**
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
72 * crash.
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 {
83 public:
84 static void NotifyCrash() {
85 StaticMutexAutoLock lock(sMutex);
86 auto* monitor = ProcessCrashMonitor::EnsureInstance();
87 if (!monitor) {
88 return;
90 monitor->mCrashNums++;
92 static bool ShouldRecoverProcess() {
93 StaticMutexAutoLock lock(sMutex);
94 auto* monitor = ProcessCrashMonitor::EnsureInstance();
95 if (!monitor) {
96 return false;
98 return monitor->mCrashNums <= monitor->mMaxCrashes;
101 private:
102 ProcessCrashMonitor() : mCrashNums(0) {
103 #ifdef MOZ_WMF_MEDIA_ENGINE
104 mMaxCrashes = StaticPrefs::media_wmf_media_engine_max_crashes();
105 #else
106 mMaxCrashes = 0;
107 #endif
109 ProcessCrashMonitor(const ProcessCrashMonitor&) = delete;
110 ProcessCrashMonitor& operator=(const ProcessCrashMonitor&) = delete;
112 static ProcessCrashMonitor* EnsureInstance() {
113 if (sIsShutdown) {
114 return nullptr;
116 if (!sCrashMonitor) {
117 sCrashMonitor.reset(new ProcessCrashMonitor());
118 GetMainThreadSerialEventTarget()->Dispatch(
119 NS_NewRunnableFunction("ProcessCrashMonitor::EnsureInstance", [&] {
120 RunOnShutdown(
121 [&] {
122 StaticMutexAutoLock lock(sMutex);
123 sCrashMonitor.reset();
124 sIsShutdown = true;
126 ShutdownPhase::XPCOMShutdown);
127 }));
129 return sCrashMonitor.get();
132 static inline StaticMutex sMutex;
133 static inline MOZ_RUNINIT UniquePtr<ProcessCrashMonitor> sCrashMonitor;
134 static inline Atomic<bool> sIsShutdown{false};
136 uint32_t mCrashNums;
137 uint32_t mMaxCrashes;
140 /* static */
141 const char* ExternalEngineStateMachine::StateToStr(State aNextState) {
142 #define STATE_TO_STR(state) \
143 case State::state: \
144 return #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);
152 default:
153 MOZ_ASSERT_UNREACHABLE("Undefined state!");
154 return "Undefined";
156 #undef STATE_TO_STR
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.
167 MOZ_ASSERT_IF(
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);
181 MOZ_ASSERT_IF(
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()});
194 } else {
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());
205 ReadMetadata();
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));
216 #endif
217 if (mEngine) {
218 MOZ_ASSERT(mInfo);
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);
228 state->mInitPromise
229 ->Then(OwnerThread(), __func__, this,
230 &ExternalEngineStateMachine::OnEngineInitSuccess,
231 &ExternalEngineStateMachine::OnEngineInitFailure)
232 ->Track(state->mEngineInitRequest);
236 void ExternalEngineStateMachine::OnEngineInitSuccess() {
237 AssertOnTaskQueue();
238 AUTO_PROFILER_LABEL("ExternalEngineStateMachine::OnEngineInitSuccess",
239 MEDIA_PLAYBACK);
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();
248 return;
250 // We just recovered from CDM process crash, seek to previous position.
251 SeekTarget target(mCurrentPosition.Ref(), SeekTarget::Type::Accurate);
252 Seek(target);
255 void ExternalEngineStateMachine::OnEngineInitFailure() {
256 AssertOnTaskQueue();
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,
267 __func__));
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);
281 }));
284 void ExternalEngineStateMachine::OnMetadataRead(MetadataHolder&& aMetadata) {
285 AssertOnTaskQueue();
286 AUTO_PROFILER_LABEL("ExternalEngineStateMachine::OnMetadataRead",
287 MEDIA_PLAYBACK);
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.
301 DecodeError(
302 MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR));
303 return;
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");
312 DecodeError(
313 MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR));
314 return;
316 #endif
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);
347 InitEngine();
350 void ExternalEngineStateMachine::OnMetadataNotRead(const MediaResult& aError) {
351 AssertOnTaskQueue();
352 MOZ_ASSERT(mState.IsReadingMetadata());
353 LOGE("Decode metadata failed, shutting down decoder");
354 mState.AsReadingMetadata()->mMetadataRequest.Complete();
355 ReportTelemetry(aError);
356 DecodeError(aError);
359 bool ExternalEngineStateMachine::IsFormatSupportedByExternalEngine(
360 const MediaInfo& aInfo) {
361 AssertOnTaskQueue();
362 MOZ_ASSERT(mState.IsReadingMetadata());
363 #ifdef MOZ_WMF_MEDIA_ENGINE
364 const bool audioSupported =
365 !aInfo.HasAudio() ||
366 MFMediaEngineDecoderModule::SupportsConfig(aInfo.mAudio);
367 const bool videoSupported =
368 !aInfo.HasVideo() ||
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;
374 #else
375 return false;
376 #endif
379 RefPtr<MediaDecoder::SeekPromise> ExternalEngineStateMachine::InvokeSeek(
380 const SeekTarget& aTarget) {
381 return InvokeAsync(
382 OwnerThread(), __func__,
383 [self = RefPtr<ExternalEngineStateMachine>(this), this,
384 target = aTarget]() -> RefPtr<MediaDecoder::SeekPromise> {
385 AssertOnTaskQueue();
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()) {
395 return;
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__);
405 } else {
406 mPendingSeek.RejectIfExists(__func__);
408 mPendingSeek = SeekJob();
410 ->Track(mPendingSeekRequest);
411 }));
412 } else {
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) {
433 AssertOnTaskQueue();
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();
447 if (!state) {
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;
463 SeekReader();
464 return state->mSeekJob.mPromise.Ensure(__func__);
467 void ExternalEngineStateMachine::SeekReader() {
468 AssertOnTaskQueue();
469 MOZ_ASSERT(mState.IsSeekingData());
470 auto* state = mState.AsSeekingData();
472 // Reset the reader first and ask it to perform a demuxer seek.
473 ResetDecode();
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",
485 MEDIA_PLAYBACK);
486 AssertOnTaskQueue();
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.
495 if (HasAudio()) {
496 mHasEnoughAudio = false;
497 OnRequestAudio();
499 if (HasVideo()) {
500 mHasEnoughVideo = false;
501 OnRequestVideo();
503 CheckIfSeekCompleted();
506 void ExternalEngineStateMachine::OnSeekRejected(
507 const SeekRejectValue& aReject) {
508 AUTO_PROFILER_LABEL("ExternalEngineStateMachine::OnSeekRejected",
509 MEDIA_PLAYBACK);
510 AssertOnTaskQueue();
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);
532 return;
535 if (aReject.mError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
536 EndOfStream(aReject.mType);
537 return;
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() {
548 AssertOnTaskQueue();
549 const auto* state = mState.AsSeekingData();
550 return state && state->IsSeeking();
553 void ExternalEngineStateMachine::CheckIfSeekCompleted() {
554 AssertOnTaskQueue();
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);
561 return;
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
567 // target.
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() {
584 AssertOnTaskQueue();
585 if (!mInfo) {
586 return;
589 LOG("ResetDecode");
590 MediaFormatReader::TrackSet tracks;
591 if (HasVideo()) {
592 mVideoDataRequest.DisconnectIfExists();
593 mVideoWaitRequest.DisconnectIfExists();
594 tracks += TrackInfo::kVideoTrack;
596 if (HasAudio()) {
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() {
612 AssertOnTaskQueue();
613 if (mState.IsShutdownEngine()) {
614 LOG("Already shutdown");
615 return mState.AsShutdownEngine()->mShutdown;
618 LOG("Shutdown");
619 ChangeStateTo(State::ShutdownEngine);
620 ResetDecode();
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();
641 if (mEngine) {
642 mEngine->Shutdown();
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() {
655 AssertOnTaskQueue();
656 AUTO_PROFILER_LABEL("ExternalEngineStateMachine::BufferedRangeUpdated",
657 MEDIA_PLAYBACK);
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()) {
665 return;
668 bool exists;
669 media::TimeUnit end{mBuffered.Ref().GetEnd(&exists)};
670 if (!exists) {
671 return;
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) \
685 do { \
686 if (mState.IsShutdownEngine() || mHasFatalError || \
687 AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { \
688 return; \
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)); \
695 return; \
697 } while (false)
699 void ExternalEngineStateMachine::SetPlaybackRate(double aPlaybackRate) {
700 AssertOnTaskQueue();
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() {
708 AssertOnTaskQueue();
709 PERFORM_WHEN_ALLOW(PlaybackRateChanged);
710 MOZ_ASSERT(mState.IsReadingMetadata() || mState.IsRunningEngine() ||
711 mState.IsSeekingData());
712 mEngine->SetPlaybackRate(mPlaybackRate);
715 void ExternalEngineStateMachine::VolumeChanged() {
716 AssertOnTaskQueue();
717 PERFORM_WHEN_ALLOW(VolumeChanged);
718 MOZ_ASSERT(mState.IsReadingMetadata() || mState.IsRunningEngine() ||
719 mState.IsSeekingData());
720 mEngine->SetVolume(mVolume);
723 void ExternalEngineStateMachine::PreservesPitchChanged() {
724 AssertOnTaskQueue();
725 PERFORM_WHEN_ALLOW(PreservesPitchChanged);
726 MOZ_ASSERT(mState.IsReadingMetadata() || mState.IsRunningEngine() ||
727 mState.IsSeekingData());
728 mEngine->SetPreservesPitch(mPreservesPitch);
731 void ExternalEngineStateMachine::PlayStateChanged() {
732 AssertOnTaskQueue();
733 PERFORM_WHEN_ALLOW(PlayStateChanged);
734 MOZ_ASSERT(mState.IsReadingMetadata() || mState.IsRunningEngine() ||
735 mState.IsSeekingData());
736 if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
737 mEngine->Play();
738 } else if (mPlayState == MediaDecoder::PLAY_STATE_PAUSED) {
739 mEngine->Pause();
741 NotifyAudibleStateChangeIfNeeded();
744 void ExternalEngineStateMachine::LoopingChanged() {
745 AssertOnTaskQueue();
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) {
755 AssertOnTaskQueue();
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) {
770 AssertOnTaskQueue();
771 MOZ_ASSERT(mState.IsRunningEngine() || mState.IsSeekingData());
772 AUTO_PROFILER_LABEL("ExternalEngineStateMachine::WaitForData",
773 MEDIA_PLAYBACK);
774 MOZ_ASSERT(aType == MediaData::Type::AUDIO_DATA ||
775 aType == MediaData::Type::VIDEO_DATA);
777 LOG("WaitForData");
778 RefPtr<ExternalEngineStateMachine> self = this;
779 if (aType == MediaData::Type::AUDIO_DATA) {
780 MOZ_ASSERT(HasAudio());
781 mReader->WaitForData(MediaData::Type::AUDIO_DATA)
782 ->Then(
783 OwnerThread(), __func__,
784 [self, this](MediaData::Type aType) {
785 AUTO_PROFILER_LABEL(
786 "ExternalEngineStateMachine::WaitForData:AudioResolved",
787 MEDIA_PLAYBACK);
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) {
794 AUTO_PROFILER_LABEL(
795 "ExternalEngineStateMachine::WaitForData:AudioRejected",
796 MEDIA_PLAYBACK);
797 mAudioWaitRequest.Complete();
798 DecodeError(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA);
800 ->Track(mAudioWaitRequest);
801 } else {
802 MOZ_ASSERT(HasVideo());
803 mReader->WaitForData(MediaData::Type::VIDEO_DATA)
804 ->Then(
805 OwnerThread(), __func__,
806 [self, this](MediaData::Type aType) {
807 AUTO_PROFILER_LABEL(
808 "ExternalEngineStateMachine::WaitForData:VideoResolved",
809 MEDIA_PLAYBACK);
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) {
816 AUTO_PROFILER_LABEL(
817 "ExternalEngineStateMachine::WaitForData:VideoRejected",
818 MEDIA_PLAYBACK);
819 mVideoWaitRequest.Complete();
820 DecodeError(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA);
822 ->Track(mVideoWaitRequest);
826 void ExternalEngineStateMachine::MaybeFinishWaitForData() {
827 AssertOnTaskQueue();
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);
835 return;
838 LOG("Finished waiting for data");
839 if (mState.IsSeekingData()) {
840 SeekReader();
841 return;
843 if (HasAudio()) {
844 RunningEngineUpdate(MediaData::Type::AUDIO_DATA);
846 if (HasVideo()) {
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) {
856 mEngine->Play();
858 if (HasAudio()) {
859 RunningEngineUpdate(MediaData::Type::AUDIO_DATA);
861 if (HasVideo()) {
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) {
874 AssertOnTaskQueue();
875 MOZ_ASSERT(mState.IsRunningEngine() || mState.IsSeekingData());
876 if (aType == MediaData::Type::AUDIO_DATA && !mHasEnoughAudio) {
877 OnRequestAudio();
879 if (aType == MediaData::Type::VIDEO_DATA && !mHasEnoughVideo) {
880 OnRequestVideo();
884 void ExternalEngineStateMachine::OnRequestAudio() {
885 AssertOnTaskQueue();
886 MOZ_ASSERT(mState.IsRunningEngine() || mState.IsSeekingData());
888 if (!HasAudio()) {
889 return;
892 if (IsRequestingAudioData() || mAudioWaitRequest.Exists() || IsSeeking()) {
893 LOGV(
894 "No need to request audio, isRequesting=%d, waitingAudio=%d, "
895 "isSeeking=%d",
896 IsRequestingAudioData(), mAudioWaitRequest.Exists(), IsSeeking());
897 return;
900 PerformanceRecorder<PlaybackStage> perfRecorder(MediaStage::RequestData);
901 RefPtr<ExternalEngineStateMachine> self = this;
902 mReader->RequestAudioData()
903 ->Then(
904 OwnerThread(), __func__,
905 [this, self, perfRecorder(std::move(perfRecorder))](
906 const RefPtr<AudioData>& aAudio) mutable {
907 perfRecorder.Record();
908 mAudioDataRequest.Complete();
909 AUTO_PROFILER_LABEL(
910 "ExternalEngineStateMachine::OnRequestAudio:Resolved",
911 MEDIA_PLAYBACK);
912 MOZ_ASSERT(aAudio);
913 RunningEngineUpdate(MediaData::Type::AUDIO_DATA);
915 [this, self](const MediaResult& aError) {
916 mAudioDataRequest.Complete();
917 AUTO_PROFILER_LABEL(
918 "ExternalEngineStateMachine::OnRequestAudio:Rejected",
919 MEDIA_PLAYBACK);
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);
925 break;
926 case NS_ERROR_DOM_MEDIA_CANCELED:
927 OnRequestAudio();
928 break;
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);
932 break;
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.
936 break;
937 default:
938 ReportTelemetry(aError);
939 DecodeError(aError);
942 ->Track(mAudioDataRequest);
945 void ExternalEngineStateMachine::OnRequestVideo() {
946 AssertOnTaskQueue();
947 MOZ_ASSERT(mState.IsRunningEngine() || mState.IsSeekingData());
949 if (!HasVideo()) {
950 return;
953 if (IsTrackingVideoData() || IsSeeking()) {
954 LOGV(
955 "No need to request video, isRequesting=%d, waitingVideo=%d, "
956 "isSeeking=%d",
957 IsRequestingVideoData(), mVideoWaitRequest.Exists(), IsSeeking());
958 return;
961 PerformanceRecorder<PlaybackStage> perfRecorder(MediaStage::RequestData,
962 Info().mVideo.mImage.height);
963 RefPtr<ExternalEngineStateMachine> self = this;
964 mReader->RequestVideoData(GetVideoThreshold(), false)
965 ->Then(
966 OwnerThread(), __func__,
967 [this, self, perfRecorder(std::move(perfRecorder))](
968 const RefPtr<VideoData>& aVideo) mutable {
969 perfRecorder.Record();
970 mVideoDataRequest.Complete();
971 AUTO_PROFILER_LABEL(
972 "ExternalEngineStateMachine::OnRequestVideo:Resolved",
973 MEDIA_PLAYBACK);
974 MOZ_ASSERT(aVideo);
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);
985 } else {
986 mVideoFrameContainer->SetCurrentFrame(
987 mVideoDisplay, aVideo->mImage, TimeStamp::Now(),
988 media::TimeUnit::Invalid(), aVideo->mTime);
991 [this, self](const MediaResult& aError) {
992 mVideoDataRequest.Complete();
993 AUTO_PROFILER_LABEL(
994 "ExternalEngineStateMachine::OnRequestVideo:Rejected",
995 MEDIA_PLAYBACK);
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);
1001 break;
1002 case NS_ERROR_DOM_MEDIA_CANCELED:
1003 OnRequestVideo();
1004 break;
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);
1008 break;
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.
1012 break;
1013 default:
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");
1026 return;
1028 LOGV("OnLoadedFirstFrame");
1029 MediaDecoderEventVisibility visibility =
1030 mSentFirstFrameLoadedEvent ? MediaDecoderEventVisibility::Suppressed
1031 : MediaDecoderEventVisibility::Observable;
1032 mSentFirstFrameLoadedEvent = true;
1033 mFirstFrameLoadedEvent.Notify(UniquePtr<MediaInfo>(new MediaInfo(Info())),
1034 visibility);
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
1041 // correctly.
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");
1063 return;
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);
1084 if (HasAudio()) {
1085 WaitForData(MediaData::Type::AUDIO_DATA);
1087 if (HasVideo()) {
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) {
1100 return;
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();
1110 if (IsSeeking()) {
1111 return;
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",
1123 MEDIA_PLAYBACK);
1124 LOG("Receive event %s", ExternalEngineEventToStr(aEvent));
1125 if (mState.IsShutdownEngine()) {
1126 return;
1128 switch (aEvent) {
1129 case ExternalEngineEvent::LoadedMetaData:
1130 // We read metadata by ourselves, ignore this if there is any.
1131 MOZ_ASSERT(mInfo);
1132 break;
1133 case ExternalEngineEvent::LoadedFirstFrame:
1134 OnLoadedFirstFrame();
1135 break;
1136 case ExternalEngineEvent::LoadedData:
1137 OnLoadedData();
1138 break;
1139 case ExternalEngineEvent::Waiting:
1140 OnWaiting();
1141 break;
1142 case ExternalEngineEvent::Playing:
1143 OnPlaying();
1144 break;
1145 case ExternalEngineEvent::Seeked:
1146 OnSeeked();
1147 break;
1148 case ExternalEngineEvent::BufferingStarted:
1149 OnBufferingStarted();
1150 break;
1151 case ExternalEngineEvent::BufferingEnded:
1152 OnBufferingEnded();
1153 break;
1154 case ExternalEngineEvent::Timeupdate:
1155 OnTimeupdate();
1156 break;
1157 case ExternalEngineEvent::Ended:
1158 OnEnded();
1159 break;
1160 case ExternalEngineEvent::RequestForAudio:
1161 mHasEnoughAudio = false;
1162 if (ShouldRunEngineUpdateForRequest()) {
1163 RunningEngineUpdate(MediaData::Type::AUDIO_DATA);
1165 break;
1166 case ExternalEngineEvent::RequestForVideo:
1167 mHasEnoughVideo = false;
1168 if (ShouldRunEngineUpdateForRequest()) {
1169 RunningEngineUpdate(MediaData::Type::VIDEO_DATA);
1171 break;
1172 case ExternalEngineEvent::AudioEnough:
1173 mHasEnoughAudio = true;
1174 break;
1175 case ExternalEngineEvent::VideoEnough:
1176 mHasEnoughVideo = true;
1177 break;
1178 default:
1179 MOZ_ASSERT_UNREACHABLE("Undefined event!");
1180 break;
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);
1202 DecodeError(
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);
1216 DecodeError(
1217 MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR));
1218 } else {
1219 ReportTelemetry(aError);
1220 DecodeError(aError);
1224 void ExternalEngineStateMachine::NotifyResizingInternal(uint32_t aWidth,
1225 uint32_t aHeight) {
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()) {
1234 return;
1236 ProcessCrashMonitor::NotifyCrash();
1237 if (!ProcessCrashMonitor::ShouldRecoverProcess()) {
1238 LOG("CDM process has crashed too many times, abort recovery");
1239 DecodeError(
1240 MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR));
1241 return;
1244 if (mState.IsInitEngine()) {
1245 LOG("Failed on the engine initialization, the media engine playback might "
1246 "not be supported");
1247 DecodeError(
1248 MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR));
1249 return;
1252 LOG("CDM process crashed, recover the engine again (last time=%" PRId64 ")",
1253 mCurrentPosition.Ref().ToMicroseconds());
1254 ChangeStateTo(State::RecoverEngine);
1255 if (HasVideo()) {
1256 mVideoDataRequest.DisconnectIfExists();
1257 mVideoWaitRequest.DisconnectIfExists();
1259 if (HasAudio()) {
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();
1266 InitEngine();
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(
1284 CDMProxy* aProxy) {
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] {
1295 SetCDMProxy(proxy)
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__);
1302 } else {
1303 mSetCDMProxyPromise.Reject(NS_ERROR_DOM_MEDIA_CDM_ERR,
1304 __func__);
1307 ->Track(mSetCDMProxyRequest);
1308 }));
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) {
1325 #ifdef MOZ_WMF_CDM
1326 MOZ_ASSERT(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) {
1330 return false;
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();
1336 #else
1337 return false;
1338 #endif
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;
1346 if (mInfo) {
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()};
1363 if (mInfo) {
1364 if (mInfo->HasAudio()) {
1365 logMessage.Append(
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(),
1371 resolution.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
1391 // external engine.
1392 mIsAudioDataAudible = mInfo && HasAudio() &&
1393 mPlayState == MediaDecoder::PLAY_STATE_PLAYING &&
1394 mState.IsRunningEngine();
1397 #undef FMT
1398 #undef LOG
1399 #undef LOGV
1400 #undef LOGW
1401 #undef LOGE
1403 } // namespace mozilla