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
#ifndef DOM_MEDIA_EXTERNALENGINESTATEMACHINE_H_
#define DOM_MEDIA_EXTERNALENGINESTATEMACHINE_H_
#include "MediaDecoderStateMachineBase.h"
#include "SeekJob.h"
#include "mozilla/Variant.h"
namespace mozilla {
/**
* ExternalPlaybackEngine represents a media engine which is responsible for
* decoding and playback, which are not controlled by Gecko.
*/
class ExternalPlaybackEngine;
enum class ExternalEngineEvent {
LoadedMetaData,
LoadedFirstFrame,
LoadedData,
Waiting,
Playing,
Seeked,
BufferingStarted,
BufferingEnded,
Timeupdate,
Ended,
RequestForAudio,
RequestForVideo,
AudioEnough,
VideoEnough,
};
const char* ExternalEngineEventToStr(ExternalEngineEvent aEvent);
/**
* When using ExternalEngineStateMachine, that means we use an external engine
* to control decoding and playback (including A/V sync). Eg. Media Foundation
* Media Engine on Windows.
*
* The external engine does most of playback works, and uses ExternalEngineEvent
* to tell us its internal state. Therefore, this state machine is responsible
* to address those events from the engine and coordinate the format reader in
* order to provide data to the engine correctly.
*/
DDLoggedTypeDeclName(ExternalEngineStateMachine);
class ExternalEngineStateMachine final
: public MediaDecoderStateMachineBase,
public DecoderDoctorLifeLogger<ExternalEngineStateMachine> {
public:
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ExternalEngineStateMachine, override)
ExternalEngineStateMachine(MediaDecoder* aDecoder,
MediaFormatReader* aReader);
RefPtr<MediaDecoder::SeekPromise> InvokeSeek(
const SeekTarget& aTarget) override;
RefPtr<GenericPromise> InvokeSetSink(
const RefPtr<AudioDeviceInfo>& aSink) override;
// The media sample would be managed by the external engine so we won't store
// any samples in our side.
size_t SizeOfVideoQueue() const override { return 0; }
size_t SizeOfAudioQueue() const override { return 0; }
// Not supported.
void SetVideoDecodeMode(VideoDecodeMode aMode) override {}
void InvokeSuspendMediaSink() override {}
void InvokeResumeMediaSink() override {}
RefPtr<GenericPromise> RequestDebugInfo(
dom::MediaDecoderStateMachineDebugInfo& aInfo) override {
// This debug info doesn't fit in this scenario because most decoding
// details are only visible inside the external engine.
return GenericPromise::CreateAndResolve(true, __func__);
}
void NotifyEvent(ExternalEngineEvent aEvent) {
// On the engine manager thread.
Unused << OwnerThread()->Dispatch(NS_NewRunnableFunction(
"ExternalEngineStateMachine::NotifyEvent",
[self = RefPtr{this}, aEvent] { self->NotifyEventInternal(aEvent); }));
}
void NotifyError(const MediaResult& aError) {
// On the engine manager thread.
Unused << OwnerThread()->Dispatch(NS_NewRunnableFunction(
"ExternalEngineStateMachine::NotifyError",
[self = RefPtr{this}, aError] { self->NotifyErrorInternal(aError); }));
}
void NotifyResizing(uint32_t aWidth, uint32_t aHeight) {
// On the engine manager thread.
Unused << OwnerThread()->Dispatch(
NS_NewRunnableFunction("ExternalEngineStateMachine::NotifyResizing",
[self = RefPtr{this}, aWidth, aHeight] {
self->NotifyResizingInternal(aWidth, aHeight);
}));
}
const char* GetStateStr() const;
RefPtr<SetCDMPromise> SetCDMProxy(CDMProxy* aProxy) override;
bool IsCDMProxySupported(CDMProxy* aProxy) override;
bool IsExternalEngineStateMachine() const override { return true; }
private:
~ExternalEngineStateMachine();
void AssertOnTaskQueue() const { MOZ_ASSERT(OnTaskQueue()); }
// A light-weight state object that helps to store some variables which would
// only be used in a certain state. Also be able to do the cleaning for the
// state transition. Only modify on the task queue.
struct StateObject final {
enum class State {
InitEngine,
ReadingMetadata,
RunningEngine,
SeekingData,
ShutdownEngine,
RecoverEngine,
};
struct InitEngine {
InitEngine() = default;
~InitEngine() { mEngineInitRequest.DisconnectIfExists(); }
MozPromiseRequestHolder<GenericNonExclusivePromise> mEngineInitRequest;
RefPtr<GenericNonExclusivePromise> mInitPromise;
};
struct ReadingMetadata {
ReadingMetadata() = default;
~ReadingMetadata() { mMetadataRequest.DisconnectIfExists(); }
MozPromiseRequestHolder<MediaFormatReader::MetadataPromise>
mMetadataRequest;
};
struct RunningEngine {};
struct SeekingData {
SeekingData() = default;
SeekingData(SeekingData&&) = default;
SeekingData(const SeekingData&) = delete;
SeekingData& operator=(const SeekingData&) = delete;
~SeekingData() {
mSeekJob.RejectIfExists(__func__);
mSeekRequest.DisconnectIfExists();
}
void SetTarget(const SeekTarget& aTarget) {
// If there is any promise for previous seeking, reject it first.
mSeekJob.RejectIfExists(__func__);
mSeekRequest.DisconnectIfExists();
// Then create a new seek job.
mSeekJob = SeekJob();
mSeekJob.mTarget = Some(aTarget);
}
void Resolve(StaticString aCallSite) {
MOZ_ASSERT(mSeekJob.Exists());
mSeekJob.Resolve(aCallSite);
mSeekJob = SeekJob();
}
void RejectIfExists(StaticString aCallSite) {
mSeekJob.RejectIfExists(aCallSite);
}
bool IsSeeking() const { return mSeekRequest.Exists(); }
media::TimeUnit GetTargetTime() const {
return mSeekJob.mTarget ? mSeekJob.mTarget->GetTime()
: media::TimeUnit::Invalid();
}
// Set it to true when starting seeking, and would be set to false after
// receiving engine's `seeked` event. Used on thhe task queue only.
bool mWaitingEngineSeeked = false;
bool mWaitingReaderSeeked = false;
MozPromiseRequestHolder<MediaFormatReader::SeekPromise> mSeekRequest;
SeekJob mSeekJob;
};
struct ShutdownEngine {
RefPtr<ShutdownPromise> mShutdown;
};
// This state is used to recover the media engine after the MF CDM process
// crashes.
struct RecoverEngine : public InitEngine {};
StateObject() : mData(ReadingMetadata()), mName(State::ReadingMetadata) {};
explicit StateObject(InitEngine&& aArg)
: mData(std::move(aArg)), mName(State::InitEngine) {};
explicit StateObject(RunningEngine&& aArg)
: mData(std::move(aArg)), mName(State::RunningEngine) {};
explicit StateObject(SeekingData&& aArg)
: mData(std::move(aArg)), mName(State::SeekingData) {};
explicit StateObject(ShutdownEngine&& aArg)
: mData(std::move(aArg)), mName(State::ShutdownEngine) {};
explicit StateObject(RecoverEngine&& aArg)
: mData(std::move(aArg)), mName(State::RecoverEngine) {};
bool IsInitEngine() const { return mData.is<InitEngine>(); }
bool IsReadingMetadata() const { return mData.is<ReadingMetadata>(); }
bool IsRunningEngine() const { return mData.is<RunningEngine>(); }
bool IsSeekingData() const { return mData.is<SeekingData>(); }
bool IsShutdownEngine() const { return mData.is<ShutdownEngine>(); }
bool IsRecoverEngine() const { return mData.is<RecoverEngine>(); }
InitEngine* AsInitEngine() {
if (IsInitEngine()) {
return &mData.as<InitEngine>();
}
if (IsRecoverEngine()) {
return &mData.as<RecoverEngine>();
}
return nullptr;
}
ReadingMetadata* AsReadingMetadata() {
return IsReadingMetadata() ? &mData.as<ReadingMetadata>() : nullptr;
}
SeekingData* AsSeekingData() {
return IsSeekingData() ? &mData.as<SeekingData>() : nullptr;
}
ShutdownEngine* AsShutdownEngine() {
return IsShutdownEngine() ? &mData.as<ShutdownEngine>() : nullptr;
}
Variant<InitEngine, ReadingMetadata, RunningEngine, SeekingData,
ShutdownEngine, RecoverEngine>
mData;
State mName;
} mState;
using State = StateObject::State;
void NotifyEventInternal(ExternalEngineEvent aEvent);
void NotifyErrorInternal(const MediaResult& aError);
void NotifyResizingInternal(uint32_t aWidth, uint32_t aHeight);
RefPtr<ShutdownPromise> Shutdown() override;
void SetPlaybackRate(double aPlaybackRate) override;
void BufferedRangeUpdated() override;
void VolumeChanged() override;
void PreservesPitchChanged() override;
void PlayStateChanged() override;
void LoopingChanged() override;
void PlaybackRateChanged();
// Not supported.
void SetCanPlayThrough(bool aCanPlayThrough) override {}
void SetFragmentEndTime(const media::TimeUnit& aFragmentEndTime) override {}
void InitEngine();
void OnEngineInitSuccess();
void OnEngineInitFailure();
void ReadMetadata();
void OnMetadataRead(MetadataHolder&& aMetadata);
void OnMetadataNotRead(const MediaResult& aError);
bool IsFormatSupportedByExternalEngine(const MediaInfo& aInfo);
// Functions for handling external engine event.
void OnLoadedFirstFrame();
void OnLoadedData();
void OnWaiting();
void OnPlaying();
void OnSeeked();
void OnBufferingStarted();
void OnBufferingEnded();
void OnTimeupdate();
void OnEnded();
void OnRequestAudio();
void OnRequestVideo();
void ResetDecode();
void EndOfStream(MediaData::Type aType);
void WaitForData(MediaData::Type aType);
void StartRunningEngine();
void RunningEngineUpdate(MediaData::Type aType);
void ChangeStateTo(State aNextState);
static const char* StateToStr(State aState);
RefPtr<MediaDecoder::SeekPromise> Seek(const SeekTarget& aTarget) override;
void SeekReader();
void OnSeekResolved(const media::TimeUnit& aUnit);
void OnSeekRejected(const SeekRejectValue& aReject);
bool IsSeeking();
void CheckIfSeekCompleted();
void MaybeFinishWaitForData();
void SetBlankVideoToVideoContainer();
media::TimeUnit GetVideoThreshold();
bool ShouldRunEngineUpdateForRequest();
void UpdateSecondaryVideoContainer() override;
void RecoverFromCDMProcessCrashIfNeeded();
void ReportTelemetry(const MediaResult& aError);
void DecodeError(const MediaResult& aError) override;
void NotifyAudibleStateChangeIfNeeded();
UniquePtr<ExternalPlaybackEngine> mEngine;
bool mHasEnoughAudio = false;
bool mHasEnoughVideo = false;
bool mSentPlaybackEndedEvent = false;
bool mHasReceivedFirstDecodedVideoFrame = false;
// Only used if setting CDM happens before the engine finishes initialization.
MozPromiseHolder<SetCDMPromise> mSetCDMProxyPromise;
MozPromiseRequestHolder<SetCDMPromise> mSetCDMProxyRequest;
// If seek happens while the engine is still initializing, then we would
// postpone the seek until the engine is ready.
SeekJob mPendingSeek;
MozPromiseRequestHolder<MediaDecoder::SeekPromise> mPendingSeekRequest;
// It would be zero for audio-only playback.
gfx::IntSize mVideoDisplay;
// It would be set if playback is encrypted.
nsCString mKeySystem;
// This array stores the tasks which needs to be executed only after the
// engine is ready but is called before that. It will be executed when
// starting running the engine.
nsTArray<RefPtr<nsIRunnable>> mPendingTasks;
bool mHasFatalError = false;
};
class ExternalPlaybackEngine {
public:
explicit ExternalPlaybackEngine(ExternalEngineStateMachine* aOwner)
: mOwner(aOwner) {}
virtual ~ExternalPlaybackEngine() = default;
enum class InitFlag {
None,
ShouldPreload,
EncryptedCustomIdent,
};
using InitFlagSet = EnumSet<InitFlag, uint8_t>;
// Init the engine and specify the preload request.
virtual RefPtr<GenericNonExclusivePromise> Init(
const MediaInfo& aInfo, const InitFlagSet& aFlags) = 0;
virtual void Shutdown() = 0;
virtual uint64_t Id() const = 0;
virtual bool IsInited() const = 0;
// Following methods should only be called after successfully initialize the
// external engine.
virtual void Play() = 0;
virtual void Pause() = 0;
virtual void Seek(const media::TimeUnit& aTargetTime) = 0;
virtual void SetPlaybackRate(double aPlaybackRate) = 0;
virtual void SetVolume(double aVolume) = 0;
virtual void SetLooping(bool aLooping) = 0;
virtual void SetPreservesPitch(bool aPreservesPitch) = 0;
virtual media::TimeUnit GetCurrentPosition() = 0;
virtual void NotifyEndOfStream(TrackInfo::TrackType aType) = 0;
virtual bool SetCDMProxy(CDMProxy* aProxy) = 0;
virtual void NotifyResizing(uint32_t aWidth, uint32_t aHeight) = 0;
ExternalEngineStateMachine* const MOZ_NON_OWNING_REF mOwner;
};
} // namespace mozilla
#endif // DOM_MEDIA_EXTERNALENGINESTATEMACHINE_H_