Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "ExternalEngineStateMachine.h"
#include "PerformanceRecorder.h"
#ifdef MOZ_WMF_MEDIA_ENGINE
# include "MFMediaEngineDecoderModule.h"
# include "mozilla/MFMediaEngineChild.h"
# include "mozilla/StaticPrefs_media.h"
#endif
#include "mozilla/AppShutdown.h"
#include "mozilla/Atomics.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/glean/GleanMetrics.h"
#include "nsThreadUtils.h"
#include "VideoUtils.h"
namespace mozilla {
extern LazyLogModule gMediaDecoderLog;
#define FMT(x, ...) \
"Decoder=%p, State=%s, " x, mDecoderID, GetStateStr(), ##__VA_ARGS__
#define LOG(x, ...) \
DDMOZ_LOG(gMediaDecoderLog, LogLevel::Debug, "Decoder=%p, State=%s, " x, \
mDecoderID, GetStateStr(), ##__VA_ARGS__)
#define LOGV(x, ...) \
DDMOZ_LOG(gMediaDecoderLog, LogLevel::Verbose, "Decoder=%p, State=%s, " x, \
mDecoderID, GetStateStr(), ##__VA_ARGS__)
#define LOGW(x, ...) NS_WARNING(nsPrintfCString(FMT(x, ##__VA_ARGS__)).get())
#define LOGE(x, ...) \
NS_DebugBreak(NS_DEBUG_WARNING, \
nsPrintfCString(FMT(x, ##__VA_ARGS__)).get(), nullptr, \
__FILE__, __LINE__)
const char* ExternalEngineEventToStr(ExternalEngineEvent aEvent) {
#define EVENT_TO_STR(event) \
case ExternalEngineEvent::event: \
return #event
switch (aEvent) {
EVENT_TO_STR(LoadedMetaData);
EVENT_TO_STR(LoadedFirstFrame);
EVENT_TO_STR(LoadedData);
EVENT_TO_STR(Waiting);
EVENT_TO_STR(Playing);
EVENT_TO_STR(Seeked);
EVENT_TO_STR(BufferingStarted);
EVENT_TO_STR(BufferingEnded);
EVENT_TO_STR(Timeupdate);
EVENT_TO_STR(Ended);
EVENT_TO_STR(RequestForAudio);
EVENT_TO_STR(RequestForVideo);
EVENT_TO_STR(AudioEnough);
EVENT_TO_STR(VideoEnough);
default:
MOZ_ASSERT_UNREACHABLE("Undefined event!");
return "Undefined";
}
#undef EVENT_TO_STR
}
/**
* This class monitors the amount of crash happened for a remote engine
* process. It the amount of crash of the remote process exceeds the defined
* threshold, then `ShouldRecoverProcess()` will return false to indicate that
* we should not keep spawning that remote process because it's too easy to
* crash.
*
* In addition, we also have another mechanism in the media format reader
* (MFR) to detect crash amount of remote processes, but that would only
* happen during the decoding process. The main reason to choose using this
* simple monitor, instead of the mechanism in the MFR is because that
* mechanism can't detect every crash happening in the remote process, such as
* crash happening during initializing the remote engine, or setting the CDM
* pipepline, which can happen prior to decoding.
*/
class ProcessCrashMonitor final {
public:
static void NotifyCrash() {
StaticMutexAutoLock lock(sMutex);
auto* monitor = ProcessCrashMonitor::EnsureInstance();
if (!monitor) {
return;
}
monitor->mCrashNums++;
}
static bool ShouldRecoverProcess() {
StaticMutexAutoLock lock(sMutex);
auto* monitor = ProcessCrashMonitor::EnsureInstance();
if (!monitor) {
return false;
}
return monitor->mCrashNums <= monitor->mMaxCrashes;
}
private:
ProcessCrashMonitor() : mCrashNums(0) {
#ifdef MOZ_WMF_MEDIA_ENGINE
mMaxCrashes = StaticPrefs::media_wmf_media_engine_max_crashes();
#else
mMaxCrashes = 0;
#endif
};
ProcessCrashMonitor(const ProcessCrashMonitor&) = delete;
ProcessCrashMonitor& operator=(const ProcessCrashMonitor&) = delete;
static ProcessCrashMonitor* EnsureInstance() {
if (sIsShutdown) {
return nullptr;
}
if (!sCrashMonitor) {
sCrashMonitor.reset(new ProcessCrashMonitor());
GetMainThreadSerialEventTarget()->Dispatch(
NS_NewRunnableFunction("ProcessCrashMonitor::EnsureInstance", [&] {
RunOnShutdown(
[&] {
StaticMutexAutoLock lock(sMutex);
sCrashMonitor.reset();
sIsShutdown = true;
},
ShutdownPhase::XPCOMShutdown);
}));
}
return sCrashMonitor.get();
}
static inline StaticMutex sMutex;
static inline UniquePtr<ProcessCrashMonitor> sCrashMonitor;
static inline Atomic<bool> sIsShutdown{false};
uint32_t mCrashNums;
uint32_t mMaxCrashes;
};
/* static */
const char* ExternalEngineStateMachine::StateToStr(State aNextState) {
#define STATE_TO_STR(state) \
case State::state: \
return #state
switch (aNextState) {
STATE_TO_STR(InitEngine);
STATE_TO_STR(ReadingMetadata);
STATE_TO_STR(RunningEngine);
STATE_TO_STR(SeekingData);
STATE_TO_STR(ShutdownEngine);
STATE_TO_STR(RecoverEngine);
default:
MOZ_ASSERT_UNREACHABLE("Undefined state!");
return "Undefined";
}
#undef STATE_TO_STR
}
const char* ExternalEngineStateMachine::GetStateStr() const {
return StateToStr(mState.mName);
}
void ExternalEngineStateMachine::ChangeStateTo(State aNextState) {
LOG("Change state : '%s' -> '%s' (play-state=%d)", StateToStr(mState.mName),
StateToStr(aNextState), mPlayState.Ref());
// Assert the possible state transitions.
MOZ_ASSERT_IF(
mState.IsReadingMetadata(),
aNextState == State::InitEngine || aNextState == State::ShutdownEngine);
MOZ_ASSERT_IF(mState.IsInitEngine(), aNextState == State::RunningEngine ||
aNextState == State::ShutdownEngine);
MOZ_ASSERT_IF(mState.IsRunningEngine(),
aNextState == State::SeekingData ||
aNextState == State::ShutdownEngine ||
aNextState == State::RecoverEngine);
MOZ_ASSERT_IF(mState.IsSeekingData(),
aNextState == State::RunningEngine ||
aNextState == State::ShutdownEngine ||
aNextState == State::RecoverEngine);
MOZ_ASSERT_IF(mState.IsShutdownEngine(), aNextState == State::ShutdownEngine);
MOZ_ASSERT_IF(
mState.IsRecoverEngine(),
aNextState == State::SeekingData || aNextState == State::ShutdownEngine);
if (aNextState == State::SeekingData) {
mState = StateObject({StateObject::SeekingData()});
} else if (aNextState == State::InitEngine) {
mState = StateObject({StateObject::InitEngine()});
} else if (aNextState == State::RunningEngine) {
mState = StateObject({StateObject::RunningEngine()});
} else if (aNextState == State::ShutdownEngine) {
mState = StateObject({StateObject::ShutdownEngine()});
} else if (aNextState == State::RecoverEngine) {
mState = StateObject({StateObject::RecoverEngine()});
} else {
MOZ_ASSERT_UNREACHABLE("Wrong state!");
}
NotifyAudibleStateChangeIfNeeded();
}
ExternalEngineStateMachine::ExternalEngineStateMachine(
MediaDecoder* aDecoder, MediaFormatReader* aReader)
: MediaDecoderStateMachineBase(aDecoder, aReader) {
LOG("Created ExternalEngineStateMachine");
MOZ_ASSERT(mState.IsReadingMetadata());
ReadMetadata();
}
ExternalEngineStateMachine::~ExternalEngineStateMachine() {
LOG("ExternalEngineStateMachine is destroyed");
}
void ExternalEngineStateMachine::InitEngine() {
MOZ_ASSERT(mState.IsInitEngine() || mState.IsRecoverEngine());
#ifdef MOZ_WMF_MEDIA_ENGINE
mEngine.reset(new MFMediaEngineWrapper(this, mFrameStats));
#endif
if (mEngine) {
MOZ_ASSERT(mInfo);
auto* state = mState.AsInitEngine();
ExternalPlaybackEngine::InitFlagSet flags;
if (mMinimizePreroll) {
flags += ExternalPlaybackEngine::InitFlag::ShouldPreload;
}
if (mReader->IsEncryptedCustomIdent()) {
flags += ExternalPlaybackEngine::InitFlag::EncryptedCustomIdent;
}
state->mInitPromise = mEngine->Init(*mInfo, flags);
state->mInitPromise
->Then(OwnerThread(), __func__, this,
&ExternalEngineStateMachine::OnEngineInitSuccess,
&ExternalEngineStateMachine::OnEngineInitFailure)
->Track(state->mEngineInitRequest);
}
}
void ExternalEngineStateMachine::OnEngineInitSuccess() {
AssertOnTaskQueue();
AUTO_PROFILER_LABEL("ExternalEngineStateMachine::OnEngineInitSuccess",
MEDIA_PLAYBACK);
MOZ_ASSERT(mState.IsInitEngine() || mState.IsRecoverEngine());
LOG("Initialized the external playback engine %" PRIu64, mEngine->Id());
auto* state = mState.AsInitEngine();
state->mEngineInitRequest.Complete();
mReader->UpdateMediaEngineId(mEngine->Id());
state->mInitPromise = nullptr;
if (mState.IsInitEngine()) {
StartRunningEngine();
return;
}
// We just recovered from CDM process crash, seek to previous position.
SeekTarget target(mCurrentPosition.Ref(), SeekTarget::Type::Accurate);
Seek(target);
}
void ExternalEngineStateMachine::OnEngineInitFailure() {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsInitEngine() || mState.IsRecoverEngine());
LOGE("Failed to initialize the external playback engine");
auto* state = mState.AsInitEngine();
state->mEngineInitRequest.Complete();
state->mInitPromise = nullptr;
// TODO : Should fallback to the normal playback with media engine.
ReportTelemetry(NS_ERROR_DOM_MEDIA_FATAL_ERR);
DecodeError(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
}
void ExternalEngineStateMachine::ReadMetadata() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mState.IsReadingMetadata());
Unused << OwnerThread()->Dispatch(NS_NewRunnableFunction(
"ExternalEngineStateMachine::ReadMetadata",
[self = RefPtr<ExternalEngineStateMachine>{this}, this] {
mReader->ReadMetadata()
->Then(OwnerThread(), __func__, this,
&ExternalEngineStateMachine::OnMetadataRead,
&ExternalEngineStateMachine::OnMetadataNotRead)
->Track(mState.AsReadingMetadata()->mMetadataRequest);
}));
}
void ExternalEngineStateMachine::OnMetadataRead(MetadataHolder&& aMetadata) {
AssertOnTaskQueue();
AUTO_PROFILER_LABEL("ExternalEngineStateMachine::OnMetadataRead",
MEDIA_PLAYBACK);
MOZ_ASSERT(mState.IsReadingMetadata());
LOG("OnMetadataRead");
mState.AsReadingMetadata()->mMetadataRequest.Complete();
mInfo.emplace(*aMetadata.mInfo);
mMediaSeekable = Info().mMediaSeekable;
mMediaSeekableOnlyInBufferedRanges =
Info().mMediaSeekableOnlyInBufferedRanges;
if (!IsFormatSupportedByExternalEngine(*mInfo)) {
// The external engine doesn't support the type, try to notify the decoder
// to use our own state machine again. Not a real "error", because it would
// fallback to another state machine.
DecodeError(
MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR));
return;
}
#ifdef MOZ_WMF_MEDIA_ENGINE
// Only support encrypted playback. Not a real "error", because it would
// fallback to another state machine.
if ((!mInfo->IsEncrypted() && !mReader->IsEncryptedCustomIdent()) &&
StaticPrefs::media_wmf_media_engine_enabled() == 2) {
LOG("External engine only supports encrypted playback by the pref");
DecodeError(
MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR));
return;
}
#endif
if (Info().mMetadataDuration.isSome()) {
mDuration = Info().mMetadataDuration;
} else if (Info().mUnadjustedMetadataEndTime.isSome()) {
const media::TimeUnit unadjusted = Info().mUnadjustedMetadataEndTime.ref();
const media::TimeUnit adjustment = Info().mStartTime;
mInfo->mMetadataDuration.emplace(unadjusted - adjustment);
mDuration = Info().mMetadataDuration;
}
// If we don't know the duration by this point, we assume infinity, per spec.
if (mDuration.Ref().isNothing()) {
mDuration = Some(media::TimeUnit::FromInfinity());
}
MOZ_ASSERT(mDuration.Ref().isSome());
if (mInfo->HasVideo()) {
mVideoDisplay = mInfo->mVideo.mDisplay;
}
LOG("Metadata loaded : a=%s, v=%s, size=[%dx%d], duration=%s",
mInfo->HasAudio() ? mInfo->mAudio.mMimeType.get() : "none",
mInfo->HasVideo() ? mInfo->mVideo.mMimeType.get() : "none",
mVideoDisplay.width, mVideoDisplay.height,
mDuration.Ref()->ToString().get());
mMetadataLoadedEvent.Notify(std::move(aMetadata.mInfo),
std::move(aMetadata.mTags),
MediaDecoderEventVisibility::Observable);
ChangeStateTo(State::InitEngine);
InitEngine();
}
void ExternalEngineStateMachine::OnMetadataNotRead(const MediaResult& aError) {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsReadingMetadata());
LOGE("Decode metadata failed, shutting down decoder");
mState.AsReadingMetadata()->mMetadataRequest.Complete();
ReportTelemetry(aError);
DecodeError(aError);
}
bool ExternalEngineStateMachine::IsFormatSupportedByExternalEngine(
const MediaInfo& aInfo) {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsReadingMetadata());
#ifdef MOZ_WMF_MEDIA_ENGINE
const bool audioSupported =
!aInfo.HasAudio() ||
MFMediaEngineDecoderModule::SupportsConfig(aInfo.mAudio);
const bool videoSupported =
!aInfo.HasVideo() ||
MFMediaEngineDecoderModule::SupportsConfig(aInfo.mVideo);
LOG("audio=%s (supported=%d), video=%s(supported=%d)",
aInfo.HasAudio() ? aInfo.mAudio.mMimeType.get() : "none", audioSupported,
aInfo.HasVideo() ? aInfo.mVideo.mMimeType.get() : "none", videoSupported);
return audioSupported && videoSupported;
#else
return false;
#endif
}
RefPtr<MediaDecoder::SeekPromise> ExternalEngineStateMachine::InvokeSeek(
const SeekTarget& aTarget) {
return InvokeAsync(
OwnerThread(), __func__,
[self = RefPtr<ExternalEngineStateMachine>(this), this,
target = aTarget]() -> RefPtr<MediaDecoder::SeekPromise> {
AssertOnTaskQueue();
if (!mEngine || !mEngine->IsInited()) {
LOG("Can't perform seek (%" PRId64 ") now, add a pending seek task",
target.GetTime().ToMicroseconds());
// We haven't added any pending seek before
if (mPendingSeek.mPromise.IsEmpty()) {
mPendingTasks.AppendElement(NS_NewRunnableFunction(
"ExternalEngineStateMachine::InvokeSeek",
[self = RefPtr{this}, this] {
if (!mPendingSeek.Exists()) {
return;
}
Seek(*mPendingSeek.mTarget)
->Then(OwnerThread(), __func__,
[self = RefPtr{this},
this](const MediaDecoder::SeekPromise::
ResolveOrRejectValue& aVal) {
mPendingSeekRequest.Complete();
if (aVal.IsResolve()) {
mPendingSeek.Resolve(__func__);
} else {
mPendingSeek.RejectIfExists(__func__);
}
mPendingSeek = SeekJob();
})
->Track(mPendingSeekRequest);
}));
} else {
// Reject previous pending promise, as we will create a new one
LOG("Replace previous pending seek with a new one");
mPendingSeek.RejectIfExists(__func__);
mPendingSeekRequest.DisconnectIfExists();
}
mPendingSeek.mTarget = Some(target);
return mPendingSeek.mPromise.Ensure(__func__);
}
if (mPendingSeek.Exists()) {
LOG("Discard pending seek because another new seek happens");
mPendingSeek.RejectIfExists(__func__);
mPendingSeek = SeekJob();
mPendingSeekRequest.DisconnectIfExists();
}
return self->Seek(target);
});
}
RefPtr<MediaDecoder::SeekPromise> ExternalEngineStateMachine::Seek(
const SeekTarget& aTarget) {
AssertOnTaskQueue();
if (!mState.IsRunningEngine() && !mState.IsSeekingData() &&
!mState.IsRecoverEngine()) {
MOZ_ASSERT(false, "Can't seek due to unsupported state.");
return MediaDecoder::SeekPromise::CreateAndReject(true, __func__);
}
// We don't support these type of seek, because they're depending on the
// implementation of the external engine, which might not be supported.
if (aTarget.IsNextFrame() || aTarget.IsVideoOnly()) {
return MediaDecoder::SeekPromise::CreateAndReject(true, __func__);
}
LOG("Start seeking to %" PRId64, aTarget.GetTime().ToMicroseconds());
auto* state = mState.AsSeekingData();
if (!state) {
// We're in other states, so change the state to seeking.
ChangeStateTo(State::SeekingData);
state = mState.AsSeekingData();
}
state->SetTarget(aTarget);
// Update related status.
mSentPlaybackEndedEvent = false;
mOnPlaybackEvent.Notify(MediaPlaybackEvent::SeekStarted);
mOnNextFrameStatus.Notify(MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING);
// Notify the external playback engine about seeking. After the engine changes
// its current time, it would send `seeked` event.
mEngine->Seek(aTarget.GetTime());
state->mWaitingEngineSeeked = true;
SeekReader();
return state->mSeekJob.mPromise.Ensure(__func__);
}
void ExternalEngineStateMachine::SeekReader() {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsSeekingData());
auto* state = mState.AsSeekingData();
// Reset the reader first and ask it to perform a demuxer seek.
ResetDecode();
state->mWaitingReaderSeeked = true;
LOG("Seek reader to %" PRId64, state->GetTargetTime().ToMicroseconds());
mReader->Seek(state->mSeekJob.mTarget.ref())
->Then(OwnerThread(), __func__, this,
&ExternalEngineStateMachine::OnSeekResolved,
&ExternalEngineStateMachine::OnSeekRejected)
->Track(state->mSeekRequest);
}
void ExternalEngineStateMachine::OnSeekResolved(const media::TimeUnit& aUnit) {
AUTO_PROFILER_LABEL("ExternalEngineStateMachine::OnSeekResolved",
MEDIA_PLAYBACK);
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsSeekingData());
auto* state = mState.AsSeekingData();
LOG("OnReaderSeekResolved");
state->mSeekRequest.Complete();
state->mWaitingReaderSeeked = false;
// Start sending new data to the external playback engine.
if (HasAudio()) {
mHasEnoughAudio = false;
OnRequestAudio();
}
if (HasVideo()) {
mHasEnoughVideo = false;
OnRequestVideo();
}
CheckIfSeekCompleted();
}
void ExternalEngineStateMachine::OnSeekRejected(
const SeekRejectValue& aReject) {
AUTO_PROFILER_LABEL("ExternalEngineStateMachine::OnSeekRejected",
MEDIA_PLAYBACK);
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsSeekingData());
auto* state = mState.AsSeekingData();
LOG("OnReaderSeekRejected");
state->mSeekRequest.Complete();
if (aReject.mError == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
LOG("OnSeekRejected reason=WAITING_FOR_DATA type=%s",
MediaData::EnumValueToString(aReject.mType));
MOZ_ASSERT_IF(aReject.mType == MediaData::Type::AUDIO_DATA,
!IsRequestingAudioData());
MOZ_ASSERT_IF(aReject.mType == MediaData::Type::VIDEO_DATA,
!IsRequestingVideoData());
MOZ_ASSERT_IF(aReject.mType == MediaData::Type::AUDIO_DATA,
!IsWaitingAudioData());
MOZ_ASSERT_IF(aReject.mType == MediaData::Type::VIDEO_DATA,
!IsWaitingVideoData());
// Fire 'waiting' to notify the player that we are waiting for data.
mOnNextFrameStatus.Notify(
MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_SEEKING);
WaitForData(aReject.mType);
return;
}
if (aReject.mError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
EndOfStream(aReject.mType);
return;
}
MOZ_ASSERT(NS_FAILED(aReject.mError),
"Cancels should also disconnect mSeekRequest");
state->RejectIfExists(__func__);
ReportTelemetry(aReject.mError);
DecodeError(aReject.mError);
}
bool ExternalEngineStateMachine::IsSeeking() {
AssertOnTaskQueue();
const auto* state = mState.AsSeekingData();
return state && state->IsSeeking();
}
void ExternalEngineStateMachine::CheckIfSeekCompleted() {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsSeekingData());
auto* state = mState.AsSeekingData();
if (state->mWaitingEngineSeeked || state->mWaitingReaderSeeked) {
LOG("Seek hasn't been completed yet, waitEngineSeeked=%d, "
"waitReaderSeeked=%d",
state->mWaitingEngineSeeked, state->mWaitingReaderSeeked);
return;
}
// As seeking should be accurate and we can't control the exact timing inside
// the external media engine. We always set the newCurrentTime = seekTime
// so that the updated HTMLMediaElement.currentTime will always be the seek
// target.
if (state->GetTargetTime() != mCurrentPosition) {
LOG("Force adjusting current time (%" PRId64
") to match to target (%" PRId64 ")",
mCurrentPosition.Ref().ToMicroseconds(),
state->GetTargetTime().ToMicroseconds());
mCurrentPosition = state->GetTargetTime();
}
LOG("Seek completed");
state->Resolve(__func__);
mOnPlaybackEvent.Notify(MediaPlaybackEvent::Invalidate);
mOnNextFrameStatus.Notify(MediaDecoderOwner::NEXT_FRAME_AVAILABLE);
StartRunningEngine();
}
void ExternalEngineStateMachine::ResetDecode() {
AssertOnTaskQueue();
if (!mInfo) {
return;
}
LOG("ResetDecode");
MediaFormatReader::TrackSet tracks;
if (HasVideo()) {
mVideoDataRequest.DisconnectIfExists();
mVideoWaitRequest.DisconnectIfExists();
tracks += TrackInfo::kVideoTrack;
}
if (HasAudio()) {
mAudioDataRequest.DisconnectIfExists();
mAudioWaitRequest.DisconnectIfExists();
tracks += TrackInfo::kAudioTrack;
}
mReader->ResetDecode(tracks);
}
RefPtr<GenericPromise> ExternalEngineStateMachine::InvokeSetSink(
const RefPtr<AudioDeviceInfo>& aSink) {
MOZ_ASSERT(NS_IsMainThread());
// TODO : can media engine support this?
return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
}
RefPtr<ShutdownPromise> ExternalEngineStateMachine::Shutdown() {
AssertOnTaskQueue();
if (mState.IsShutdownEngine()) {
LOG("Already shutdown");
return mState.AsShutdownEngine()->mShutdown;
}
LOG("Shutdown");
ChangeStateTo(State::ShutdownEngine);
ResetDecode();
mAudioDataRequest.DisconnectIfExists();
mVideoDataRequest.DisconnectIfExists();
mAudioWaitRequest.DisconnectIfExists();
mVideoWaitRequest.DisconnectIfExists();
mDuration.DisconnectAll();
mCurrentPosition.DisconnectAll();
mIsAudioDataAudible.DisconnectAll();
mMetadataManager.Disconnect();
mSetCDMProxyPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_ABORT_ERR, __func__);
mSetCDMProxyRequest.DisconnectIfExists();
mPendingSeek.RejectIfExists(__func__);
mPendingSeekRequest.DisconnectIfExists();
mPendingTasks.Clear();
if (mEngine) {
mEngine->Shutdown();
}
auto* state = mState.AsShutdownEngine();
state->mShutdown = mReader->Shutdown()->Then(
OwnerThread(), __func__, [self = RefPtr{this}, this]() {
LOG("Shutting down state machine task queue");
return OwnerThread()->BeginShutdown();
});
return state->mShutdown;
}
void ExternalEngineStateMachine::BufferedRangeUpdated() {
AssertOnTaskQueue();
AUTO_PROFILER_LABEL("ExternalEngineStateMachine::BufferedRangeUpdated",
MEDIA_PLAYBACK);
// While playing an unseekable stream of unknown duration, mDuration
// is updated as we play. But if data is being downloaded
// faster than played, mDuration won't reflect the end of playable data
// since we haven't played the frame at the end of buffered data. So update
// mDuration here as new data is downloaded to prevent such a lag.
if (mBuffered.Ref().IsInvalid()) {
return;
}
bool exists;
media::TimeUnit end{mBuffered.Ref().GetEnd(&exists)};
if (!exists) {
return;
}
// Use estimated duration from buffer ranges when mDuration is unknown or
// the estimated duration is larger.
if (mDuration.Ref().isNothing() || mDuration.Ref()->IsInfinite() ||
end > mDuration.Ref().ref()) {
mDuration = Some(end);
DDLOG(DDLogCategory::Property, "duration_us",
mDuration.Ref()->ToMicroseconds());
}
}
#define PERFORM_WHEN_ALLOW(Func) \
do { \
if (mState.IsShutdownEngine() || mHasFatalError || \
AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { \
return; \
} \
/* Initialzation is not done yet, postpone the operation */ \
if (!mEngine || !mEngine->IsInited()) { \
LOG("%s is called before init", __func__); \
mPendingTasks.AppendElement(NewRunnableMethod( \
__func__, this, &ExternalEngineStateMachine::Func)); \
return; \
} \
} while (false)
void ExternalEngineStateMachine::SetPlaybackRate(double aPlaybackRate) {
AssertOnTaskQueue();
// TODO : consider to make `mPlaybackRate` a mirror to fit other usages like
// `mVolume` and `mPreservesPitch`.
mPlaybackRate = aPlaybackRate;
PlaybackRateChanged();
}
void ExternalEngineStateMachine::PlaybackRateChanged() {
AssertOnTaskQueue();
PERFORM_WHEN_ALLOW(PlaybackRateChanged);
MOZ_ASSERT(mState.IsReadingMetadata() || mState.IsRunningEngine() ||
mState.IsSeekingData());
mEngine->SetPlaybackRate(mPlaybackRate);
}
void ExternalEngineStateMachine::VolumeChanged() {
AssertOnTaskQueue();
PERFORM_WHEN_ALLOW(VolumeChanged);
MOZ_ASSERT(mState.IsReadingMetadata() || mState.IsRunningEngine() ||
mState.IsSeekingData());
mEngine->SetVolume(mVolume);
}
void ExternalEngineStateMachine::PreservesPitchChanged() {
AssertOnTaskQueue();
PERFORM_WHEN_ALLOW(PreservesPitchChanged);
MOZ_ASSERT(mState.IsReadingMetadata() || mState.IsRunningEngine() ||
mState.IsSeekingData());
mEngine->SetPreservesPitch(mPreservesPitch);
}
void ExternalEngineStateMachine::PlayStateChanged() {
AssertOnTaskQueue();
PERFORM_WHEN_ALLOW(PlayStateChanged);
MOZ_ASSERT(mState.IsReadingMetadata() || mState.IsRunningEngine() ||
mState.IsSeekingData());
if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
mEngine->Play();
} else if (mPlayState == MediaDecoder::PLAY_STATE_PAUSED) {
mEngine->Pause();
}
NotifyAudibleStateChangeIfNeeded();
}
void ExternalEngineStateMachine::LoopingChanged() {
AssertOnTaskQueue();
PERFORM_WHEN_ALLOW(LoopingChanged);
MOZ_ASSERT(mState.IsReadingMetadata() || mState.IsRunningEngine() ||
mState.IsSeekingData());
mEngine->SetLooping(mLooping);
}
#undef PERFORM_WHEN_ALLOW
void ExternalEngineStateMachine::EndOfStream(MediaData::Type aType) {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsRunningEngine() || mState.IsSeekingData());
static auto DataTypeToTrackType = [](const MediaData::Type& aType) {
if (aType == MediaData::Type::VIDEO_DATA) {
return TrackInfo::TrackType::kVideoTrack;
}
if (aType == MediaData::Type::AUDIO_DATA) {
return TrackInfo::TrackType::kAudioTrack;
}
return TrackInfo::TrackType::kUndefinedTrack;
};
mEngine->NotifyEndOfStream(DataTypeToTrackType(aType));
}
void