gecko-dev/dom/media/ExternalEngineStateMachine.cpp

885 строки
30 KiB
C++

/* 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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "ExternalEngineStateMachine.h"
#include "PerformanceRecorder.h"
#ifdef MOZ_WMF
# include "mozilla/MFMediaEngineChild.h"
#endif
#include "mozilla/ProfilerLabels.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
}
/* 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);
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'", StateToStr(mState.mName),
StateToStr(aNextState));
// Assert the possible state transitions.
MOZ_ASSERT_IF(mState.IsInitEngine(), aNextState == State::ReadingMetadata ||
aNextState == State::ShutdownEngine);
MOZ_ASSERT_IF(mState.IsReadingMetadata(),
aNextState == State::RunningEngine ||
aNextState == State::ShutdownEngine);
MOZ_ASSERT_IF(
mState.IsRunningEngine(),
aNextState == State::SeekingData || aNextState == State::ShutdownEngine);
MOZ_ASSERT_IF(mState.IsSeekingData(),
aNextState == State::RunningEngine ||
aNextState == State::ShutdownEngine);
MOZ_ASSERT_IF(mState.IsShutdownEngine(), aNextState == State::ShutdownEngine);
if (aNextState == State::SeekingData) {
mState = StateObject({StateObject::SeekingData()});
} else if (aNextState == State::ReadingMetadata) {
mState = StateObject({StateObject::ReadingMetadata()});
} else if (aNextState == State::RunningEngine) {
mState = StateObject({StateObject::RunningEngine()});
} else if (aNextState == State::ShutdownEngine) {
mState = StateObject({StateObject::ShutdownEngine()});
} else {
MOZ_ASSERT_UNREACHABLE("Wrong state!");
}
}
ExternalEngineStateMachine::ExternalEngineStateMachine(
MediaDecoder* aDecoder, MediaFormatReader* aReader)
: MediaDecoderStateMachineBase(aDecoder, aReader) {
LOG("Created ExternalEngineStateMachine");
MOZ_ASSERT(mState.IsInitEngine());
#ifdef MOZ_WMF
mEngine.reset(new MFMediaEngineWrapper(this));
#endif
if (mEngine) {
auto* state = mState.AsInitEngine();
state->mInitPromise = mEngine->Init(!mMinimizePreroll);
state->mInitPromise
->Then(OwnerThread(), __func__, this,
&ExternalEngineStateMachine::OnEngineInitSuccess,
&ExternalEngineStateMachine::OnEngineInitFailure)
->Track(state->mEngineInitRequest);
} else {
ShutdownInternal();
}
}
void ExternalEngineStateMachine::OnEngineInitSuccess() {
AssertOnTaskQueue();
AUTO_PROFILER_LABEL("ExternalEngineStateMachine::OnEngineInitSuccess",
MEDIA_PLAYBACK);
MOZ_ASSERT(mState.IsInitEngine());
LOG("Initialized the external playback engine %" PRIu64
", start reading metadata",
mEngine->Id());
auto* state = mState.AsInitEngine();
state->mEngineInitRequest.Complete();
mReader->UpdateMediaEngineId(mEngine->Id());
state->mInitPromise = nullptr;
ChangeStateTo(State::ReadingMetadata);
ReadMetadata();
}
void ExternalEngineStateMachine::OnEngineInitFailure() {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsInitEngine());
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.
DecodeError(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
ShutdownInternal();
}
void ExternalEngineStateMachine::ReadMetadata() {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsReadingMetadata());
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;
mEngine->SetMediaInfo(*mInfo);
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());
mMetadataLoadedEvent.Notify(std::move(aMetadata.mInfo),
std::move(aMetadata.mTags),
MediaDecoderEventVisibility::Observable);
StartRunningEngine();
}
void ExternalEngineStateMachine::OnMetadataNotRead(const MediaResult& aError) {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsReadingMetadata());
LOGE("Decode metadata failed, shutting down decoder");
mState.AsReadingMetadata()->mMetadataRequest.Complete();
DecodeError(aError);
}
RefPtr<MediaDecoder::SeekPromise> ExternalEngineStateMachine::Seek(
const SeekTarget& aTarget) {
AssertOnTaskQueue();
if (!mState.IsRunningEngine() && !mState.IsSeekingData()) {
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 the running engine state, and 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->mSeekJob.mTarget->GetTime().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("OnSeekResolved");
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("OnSeekRejected");
state->mSeekRequest.Complete();
if (aReject.mError == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
LOG("OnSeekRejected reason=WAITING_FOR_DATA type=%s",
MediaData::TypeToStr(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__);
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;
}
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();
LOG("Shutdown");
return ShutdownInternal();
}
RefPtr<ShutdownPromise> ExternalEngineStateMachine::ShutdownInternal() {
AssertOnTaskQueue();
LOG("ShutdownInternal");
ChangeStateTo(State::ShutdownEngine);
ResetDecode();
mAudioDataRequest.DisconnectIfExists();
mVideoDataRequest.DisconnectIfExists();
mAudioWaitRequest.DisconnectIfExists();
mVideoWaitRequest.DisconnectIfExists();
mBuffered.DisconnectIfConnected();
mPlayState.DisconnectIfConnected();
mVolume.DisconnectIfConnected();
mPreservesPitch.DisconnectIfConnected();
mLooping.DisconnectIfConnected();
mDuration.DisconnectAll();
mCurrentPosition.DisconnectAll();
// TODO : implement audible check
mIsAudioDataAudible.DisconnectAll();
mMetadataManager.Disconnect();
mEngine->Shutdown();
return mReader->Shutdown()->Then(
OwnerThread(), __func__, [self = RefPtr{this}, this]() {
LOG("Shutting down state machine task queue");
return OwnerThread()->BeginShutdown();
});
}
void ExternalEngineStateMachine::SetPlaybackRate(double aPlaybackRate) {
AssertOnTaskQueue();
mEngine->SetVolume(aPlaybackRate);
}
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 { \
/* Initialzation is not done yet, posepone the operation */ \
if (mState.IsInitEngine() && mState.AsInitEngine()->mInitPromise) { \
LOG("%s is called before init", __func__); \
mState.AsInitEngine()->mInitPromise->Then( \
OwnerThread(), __func__, \
[self = RefPtr{this}]( \
const GenericNonExclusivePromise::ResolveOrRejectValue& aVal) { \
if (aVal.IsResolve()) { \
self->Func(); \
} \
}); \
return; \
} else if (mState.IsShutdownEngine()) { \
return; \
} \
} while (false)
void ExternalEngineStateMachine::VolumeChanged() {
AssertOnTaskQueue();
PERFORM_WHEN_ALLOW(VolumeChanged);
mEngine->SetVolume(mVolume);
}
void ExternalEngineStateMachine::PreservesPitchChanged() {
AssertOnTaskQueue();
PERFORM_WHEN_ALLOW(PreservesPitchChanged);
mEngine->SetPreservesPitch(mPreservesPitch);
}
void ExternalEngineStateMachine::PlayStateChanged() {
AssertOnTaskQueue();
PERFORM_WHEN_ALLOW(PlayStateChanged);
if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
mEngine->Play();
} else if (mPlayState == MediaDecoder::PLAY_STATE_PAUSED) {
mEngine->Pause();
}
}
void ExternalEngineStateMachine::LoopingChanged() {
AssertOnTaskQueue();
PERFORM_WHEN_ALLOW(LoopingChanged);
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 ExternalEngineStateMachine::WaitForData(MediaData::Type aType) {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsRunningEngine() || mState.IsSeekingData());
AUTO_PROFILER_LABEL("ExternalEngineStateMachine::WaitForData",
MEDIA_PLAYBACK);
MOZ_ASSERT(aType == MediaData::Type::AUDIO_DATA ||
aType == MediaData::Type::VIDEO_DATA);
LOG("WaitForData");
RefPtr<ExternalEngineStateMachine> self = this;
if (aType == MediaData::Type::AUDIO_DATA) {
MOZ_ASSERT(HasAudio());
mReader->WaitForData(MediaData::Type::AUDIO_DATA)
->Then(
OwnerThread(), __func__,
[self, this](MediaData::Type aType) {
AUTO_PROFILER_LABEL(
"ExternalEngineStateMachine::WaitForData:AudioResolved",
MEDIA_PLAYBACK);
MOZ_ASSERT(aType == MediaData::Type::AUDIO_DATA);
LOG("Done waiting for audio data");
mAudioWaitRequest.Complete();
MaybeFinishWaitForData();
},
[self, this](const WaitForDataRejectValue& aRejection) {
AUTO_PROFILER_LABEL(
"ExternalEngineStateMachine::WaitForData:AudioRejected",
MEDIA_PLAYBACK);
mAudioWaitRequest.Complete();
DecodeError(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA);
})
->Track(mAudioWaitRequest);
} else {
MOZ_ASSERT(HasVideo());
mReader->WaitForData(MediaData::Type::VIDEO_DATA)
->Then(
OwnerThread(), __func__,
[self, this](MediaData::Type aType) {
AUTO_PROFILER_LABEL(
"ExternalEngineStateMachine::WaitForData:VideoResolved",
MEDIA_PLAYBACK);
MOZ_ASSERT(aType == MediaData::Type::VIDEO_DATA);
LOG("Done waiting for video data");
mVideoWaitRequest.Complete();
MaybeFinishWaitForData();
},
[self, this](const WaitForDataRejectValue& aRejection) {
AUTO_PROFILER_LABEL(
"ExternalEngineStateMachine::WaitForData:VideoRejected",
MEDIA_PLAYBACK);
mVideoWaitRequest.Complete();
DecodeError(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA);
})
->Track(mVideoWaitRequest);
}
}
void ExternalEngineStateMachine::MaybeFinishWaitForData() {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsRunningEngine() || mState.IsSeekingData());
bool isWaitingForAudio = HasAudio() && mAudioWaitRequest.Exists();
bool isWaitingForVideo = HasVideo() && mVideoWaitRequest.Exists();
if (isWaitingForAudio || isWaitingForVideo) {
LOG("Still waiting for data (waitAudio=%d, waitVideo=%d)",
isWaitingForAudio, isWaitingForVideo);
return;
}
LOG("Finished waiting for data");
if (mState.IsSeekingData()) {
SeekReader();
return;
}
if (HasAudio()) {
RunningEngineUpdate(MediaData::Type::AUDIO_DATA);
}
if (HasVideo()) {
RunningEngineUpdate(MediaData::Type::VIDEO_DATA);
}
}
void ExternalEngineStateMachine::StartRunningEngine() {
ChangeStateTo(State::RunningEngine);
if (HasAudio()) {
RunningEngineUpdate(MediaData::Type::AUDIO_DATA);
}
if (HasVideo()) {
RunningEngineUpdate(MediaData::Type::VIDEO_DATA);
}
}
void ExternalEngineStateMachine::RunningEngineUpdate(MediaData::Type aType) {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsRunningEngine() || mState.IsSeekingData());
LOG("RunningEngineUpdate");
if (aType == MediaData::Type::AUDIO_DATA && !mHasEnoughAudio) {
OnRequestAudio();
}
if (aType == MediaData::Type::VIDEO_DATA && !mHasEnoughVideo) {
OnRequestVideo();
}
}
void ExternalEngineStateMachine::OnRequestAudio() {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsRunningEngine() || mState.IsSeekingData());
LOGV("OnRequestAudio");
if (!HasAudio()) {
return;
}
if (IsRequestingAudioData() || mAudioWaitRequest.Exists() || IsSeeking()) {
LOGV(
"No need to request audio, isRequesting=%d, waitingAudio=%d, "
"isSeeking=%d",
IsRequestingAudioData(), mAudioWaitRequest.Exists(), IsSeeking());
return;
}
LOGV("Start requesting audio");
PerformanceRecorder perfRecorder(PerformanceRecorder::Stage::RequestData);
perfRecorder.Start();
RefPtr<ExternalEngineStateMachine> self = this;
mReader->RequestAudioData()
->Then(
OwnerThread(), __func__,
[this, self, perfRecorder(std::move(perfRecorder))](
const RefPtr<AudioData>& aAudio) mutable {
perfRecorder.End();
mAudioDataRequest.Complete();
LOGV("Completed requesting audio");
AUTO_PROFILER_LABEL(
"ExternalEngineStateMachine::OnRequestAudio:Resolved",
MEDIA_PLAYBACK);
MOZ_ASSERT(aAudio);
RunningEngineUpdate(MediaData::Type::AUDIO_DATA);
},
[this, self](const MediaResult& aError) {
mAudioDataRequest.Complete();
AUTO_PROFILER_LABEL(
"ExternalEngineStateMachine::OnRequestAudio:Rejected",
MEDIA_PLAYBACK);
LOG("OnRequestAudio ErrorName=%s Message=%s",
aError.ErrorName().get(), aError.Message().get());
switch (aError.Code()) {
case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
WaitForData(MediaData::Type::AUDIO_DATA);
break;
case NS_ERROR_DOM_MEDIA_CANCELED:
OnRequestAudio();
break;
case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
LOG("Reach to the end, no more audio data");
EndOfStream(MediaData::Type::AUDIO_DATA);
break;
default:
DecodeError(aError);
}
})
->Track(mAudioDataRequest);
}
void ExternalEngineStateMachine::OnRequestVideo() {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsRunningEngine() || mState.IsSeekingData());
LOGV("OnRequestVideo");
if (!HasVideo()) {
return;
}
if (IsRequestingVideoData() || mVideoWaitRequest.Exists() || IsSeeking()) {
LOGV(
"No need to request video, isRequesting=%d, waitingVideo=%d, "
"isSeeking=%d",
IsRequestingVideoData(), mVideoWaitRequest.Exists(), IsSeeking());
return;
}
LOGV("Start requesting video");
PerformanceRecorder perfRecorder(PerformanceRecorder::Stage::RequestData,
Info().mVideo.mImage.height);
perfRecorder.Start();
RefPtr<ExternalEngineStateMachine> self = this;
mReader->RequestVideoData(mCurrentPosition.Ref(), false)
->Then(
OwnerThread(), __func__,
[this, self, perfRecorder(std::move(perfRecorder))](
const RefPtr<VideoData>& aVideo) mutable {
perfRecorder.End();
mVideoDataRequest.Complete();
LOGV("Completed requesting video");
AUTO_PROFILER_LABEL(
"ExternalEngineStateMachine::OnRequestVideo:Resolved",
MEDIA_PLAYBACK);
MOZ_ASSERT(aVideo);
RunningEngineUpdate(MediaData::Type::VIDEO_DATA);
// TODO : set video into video container? Will implement this when
// starting implementing the video output for the media engine.
},
[this, self](const MediaResult& aError) {
mVideoDataRequest.Complete();
AUTO_PROFILER_LABEL(
"ExternalEngineStateMachine::OnRequestVideo:Rejected",
MEDIA_PLAYBACK);
LOG("OnRequestVideo ErrorName=%s Message=%s",
aError.ErrorName().get(), aError.Message().get());
switch (aError.Code()) {
case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
WaitForData(MediaData::Type::VIDEO_DATA);
break;
case NS_ERROR_DOM_MEDIA_CANCELED:
OnRequestVideo();
break;
case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
LOG("Reach to the end, no more video data");
EndOfStream(MediaData::Type::VIDEO_DATA);
break;
default:
DecodeError(aError);
}
})
->Track(mVideoDataRequest);
}
void ExternalEngineStateMachine::OnLoadedFirstFrame() {
AssertOnTaskQueue();
MediaDecoderEventVisibility visibility =
mSentFirstFrameLoadedEvent ? MediaDecoderEventVisibility::Suppressed
: MediaDecoderEventVisibility::Observable;
mSentFirstFrameLoadedEvent = true;
mFirstFrameLoadedEvent.Notify(UniquePtr<MediaInfo>(new MediaInfo(Info())),
visibility);
}
void ExternalEngineStateMachine::OnLoadedData() {
AssertOnTaskQueue();
// In case the external engine doesn't send the first frame loaded event
// correctly.
if (!mSentFirstFrameLoadedEvent) {
OnLoadedFirstFrame();
}
mOnNextFrameStatus.Notify(MediaDecoderOwner::NEXT_FRAME_AVAILABLE);
}
void ExternalEngineStateMachine::OnWaiting() {
AssertOnTaskQueue();
mOnNextFrameStatus.Notify(
MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING);
}
void ExternalEngineStateMachine::OnPlaying() {
AssertOnTaskQueue();
mOnNextFrameStatus.Notify(MediaDecoderOwner::NEXT_FRAME_AVAILABLE);
}
void ExternalEngineStateMachine::OnSeeked() {
AssertOnTaskQueue();
MOZ_ASSERT(mState.IsSeekingData());
auto* state = mState.AsSeekingData();
// Engine's event could arrive before finishing reader's seeking.
state->mWaitingEngineSeeked = false;
CheckIfSeekCompleted();
}
void ExternalEngineStateMachine::OnBufferingStarted() {
AssertOnTaskQueue();
mOnNextFrameStatus.Notify(
MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING);
if (HasAudio()) {
WaitForData(MediaData::Type::AUDIO_DATA);
}
if (HasVideo()) {
WaitForData(MediaData::Type::VIDEO_DATA);
}
}
void ExternalEngineStateMachine::OnBufferingEnded() {
AssertOnTaskQueue();
mOnNextFrameStatus.Notify(MediaDecoderOwner::NEXT_FRAME_AVAILABLE);
}
void ExternalEngineStateMachine::OnEnded() {
AssertOnTaskQueue();
if (mSentPlaybackEndedEvent) {
return;
}
LOG("Playback is ended");
mOnNextFrameStatus.Notify(MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE);
mOnPlaybackEvent.Notify(MediaPlaybackEvent::PlaybackEnded);
mSentPlaybackEndedEvent = true;
}
void ExternalEngineStateMachine::OnTimeupdate() {
AssertOnTaskQueue();
if (IsSeeking()) {
return;
}
mCurrentPosition = mEngine->GetCurrentPosition();
if (mDuration.Ref().ref() < mCurrentPosition.Ref()) {
mDuration = Some(mCurrentPosition.Ref());
}
}
void ExternalEngineStateMachine::NotifyEventInternal(
ExternalEngineEvent aEvent) {
AssertOnTaskQueue();
AUTO_PROFILER_LABEL("ExternalEngineStateMachine::NotifyEventInternal",
MEDIA_PLAYBACK);
LOG("Receive event %s", ExternalEngineEventToStr(aEvent));
switch (aEvent) {
case ExternalEngineEvent::LoadedMetaData:
// We read metadata by ourselves, ignore this if there is any.
MOZ_ASSERT(mInfo);
break;
case ExternalEngineEvent::LoadedFirstFrame:
OnLoadedFirstFrame();
break;
case ExternalEngineEvent::LoadedData:
OnLoadedData();
break;
case ExternalEngineEvent::Waiting:
OnWaiting();
break;
case ExternalEngineEvent::Playing:
OnPlaying();
break;
case ExternalEngineEvent::Seeked:
OnSeeked();
break;
case ExternalEngineEvent::BufferingStarted:
OnBufferingStarted();
break;
case ExternalEngineEvent::BufferingEnded:
OnBufferingEnded();
break;
case ExternalEngineEvent::Timeupdate:
OnTimeupdate();
break;
case ExternalEngineEvent::Ended:
OnEnded();
break;
case ExternalEngineEvent::RequestForAudio:
mHasEnoughAudio = false;
RunningEngineUpdate(MediaData::Type::AUDIO_DATA);
break;
case ExternalEngineEvent::RequestForVideo:
mHasEnoughVideo = false;
RunningEngineUpdate(MediaData::Type::VIDEO_DATA);
break;
case ExternalEngineEvent::AudioEnough:
mHasEnoughAudio = true;
break;
case ExternalEngineEvent::VideoEnough:
mHasEnoughVideo = true;
break;
default:
MOZ_ASSERT_UNREACHABLE("Undefined event!");
break;
}
}
#undef FMT
#undef LOG
#undef LOGV
#undef LOGW
#undef LOGE
} // namespace mozilla