ExternalEngineStateMachine.h (13070B)
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/. */ 4 5 #ifndef DOM_MEDIA_EXTERNALENGINESTATEMACHINE_H_ 6 #define DOM_MEDIA_EXTERNALENGINESTATEMACHINE_H_ 7 8 #include "MediaDecoderStateMachineBase.h" 9 #include "SeekJob.h" 10 #include "mozilla/Variant.h" 11 12 namespace mozilla { 13 14 /** 15 * ExternalPlaybackEngine represents a media engine which is responsible for 16 * decoding and playback, which are not controlled by Gecko. 17 */ 18 class ExternalPlaybackEngine; 19 20 enum class ExternalEngineEvent { 21 LoadedMetaData, 22 LoadedFirstFrame, 23 LoadedData, 24 Waiting, 25 Playing, 26 Seeked, 27 BufferingStarted, 28 BufferingEnded, 29 Timeupdate, 30 Ended, 31 RequestForAudio, 32 RequestForVideo, 33 AudioEnough, 34 VideoEnough, 35 }; 36 const char* ExternalEngineEventToStr(ExternalEngineEvent aEvent); 37 38 /** 39 * When using ExternalEngineStateMachine, that means we use an external engine 40 * to control decoding and playback (including A/V sync). Eg. Media Foundation 41 * Media Engine on Windows. 42 * 43 * The external engine does most of playback works, and uses ExternalEngineEvent 44 * to tell us its internal state. Therefore, this state machine is responsible 45 * to address those events from the engine and coordinate the format reader in 46 * order to provide data to the engine correctly. 47 */ 48 DDLoggedTypeDeclName(ExternalEngineStateMachine); 49 50 class ExternalEngineStateMachine final 51 : public MediaDecoderStateMachineBase, 52 public DecoderDoctorLifeLogger<ExternalEngineStateMachine> { 53 public: 54 NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ExternalEngineStateMachine, override) 55 56 ExternalEngineStateMachine(MediaDecoder* aDecoder, 57 MediaFormatReader* aReader); 58 59 RefPtr<MediaDecoder::SeekPromise> InvokeSeek( 60 const SeekTarget& aTarget) override; 61 62 RefPtr<GenericPromise> InvokeSetSink( 63 const RefPtr<AudioDeviceInfo>& aSink) override; 64 65 // The media sample would be managed by the external engine so we won't store 66 // any samples in our side. 67 size_t SizeOfVideoQueue() const override { return 0; } 68 size_t SizeOfAudioQueue() const override { return 0; } 69 70 // Not supported. 71 void SetVideoDecodeMode(VideoDecodeMode aMode) override {} 72 void InvokeSuspendMediaSink() override {} 73 void InvokeResumeMediaSink() override {} 74 RefPtr<GenericPromise> RequestDebugInfo( 75 dom::MediaDecoderStateMachineDebugInfo& aInfo) override { 76 // This debug info doesn't fit in this scenario because most decoding 77 // details are only visible inside the external engine. 78 return GenericPromise::CreateAndResolve(true, __func__); 79 } 80 81 void NotifyEvent(ExternalEngineEvent aEvent) { 82 // On the engine manager thread. 83 (void)OwnerThread()->Dispatch(NS_NewRunnableFunction( 84 "ExternalEngineStateMachine::NotifyEvent", 85 [self = RefPtr{this}, aEvent] { self->NotifyEventInternal(aEvent); })); 86 } 87 void NotifyError(const MediaResult& aError) { 88 // On the engine manager thread. 89 (void)OwnerThread()->Dispatch(NS_NewRunnableFunction( 90 "ExternalEngineStateMachine::NotifyError", 91 [self = RefPtr{this}, aError] { self->NotifyErrorInternal(aError); })); 92 } 93 void NotifyResizing(uint32_t aWidth, uint32_t aHeight) { 94 // On the engine manager thread. 95 (void)OwnerThread()->Dispatch( 96 NS_NewRunnableFunction("ExternalEngineStateMachine::NotifyResizing", 97 [self = RefPtr{this}, aWidth, aHeight] { 98 self->NotifyResizingInternal(aWidth, aHeight); 99 })); 100 } 101 102 const char* GetStateStr() const; 103 104 RefPtr<SetCDMPromise> SetCDMProxy(CDMProxy* aProxy) override; 105 106 nsresult IsCDMProxySupported(CDMProxy* aProxy) override; 107 108 bool IsExternalEngineStateMachine() const override { return true; } 109 110 private: 111 ~ExternalEngineStateMachine(); 112 113 void AssertOnTaskQueue() const { MOZ_ASSERT(OnTaskQueue()); } 114 115 // A light-weight state object that helps to store some variables which would 116 // only be used in a certain state. Also be able to do the cleaning for the 117 // state transition. Only modify on the task queue. 118 struct StateObject final { 119 enum class State { 120 InitEngine, 121 ReadingMetadata, 122 RunningEngine, 123 SeekingData, 124 ShutdownEngine, 125 RecoverEngine, 126 }; 127 struct InitEngine { 128 InitEngine() = default; 129 ~InitEngine() { mEngineInitRequest.DisconnectIfExists(); } 130 MozPromiseRequestHolder<GenericNonExclusivePromise> mEngineInitRequest; 131 RefPtr<GenericNonExclusivePromise> mInitPromise; 132 }; 133 struct ReadingMetadata { 134 ReadingMetadata() = default; 135 ~ReadingMetadata() { mMetadataRequest.DisconnectIfExists(); } 136 MozPromiseRequestHolder<MediaFormatReader::MetadataPromise> 137 mMetadataRequest; 138 }; 139 struct RunningEngine {}; 140 struct SeekingData { 141 SeekingData() = default; 142 SeekingData(SeekingData&&) = default; 143 SeekingData(const SeekingData&) = delete; 144 SeekingData& operator=(const SeekingData&) = delete; 145 ~SeekingData() { 146 mSeekJob.RejectIfExists(__func__); 147 mSeekRequest.DisconnectIfExists(); 148 } 149 void SetTarget(const SeekTarget& aTarget) { 150 // If there is any promise for previous seeking, reject it first. 151 mSeekJob.RejectIfExists(__func__); 152 mSeekRequest.DisconnectIfExists(); 153 // Then create a new seek job. 154 mSeekJob = SeekJob(); 155 mSeekJob.mTarget = Some(aTarget); 156 } 157 void Resolve(StaticString aCallSite) { 158 MOZ_ASSERT(mSeekJob.Exists()); 159 mSeekJob.Resolve(aCallSite); 160 mSeekJob = SeekJob(); 161 } 162 void RejectIfExists(StaticString aCallSite) { 163 mSeekJob.RejectIfExists(aCallSite); 164 } 165 bool IsSeeking() const { return mSeekRequest.Exists(); } 166 media::TimeUnit GetTargetTime() const { 167 return mSeekJob.mTarget ? mSeekJob.mTarget->GetTime() 168 : media::TimeUnit::Invalid(); 169 } 170 // Set it to true when starting seeking, and would be set to false after 171 // receiving engine's `seeked` event. Used on thhe task queue only. 172 bool mWaitingEngineSeeked = false; 173 bool mWaitingReaderSeeked = false; 174 MozPromiseRequestHolder<MediaFormatReader::SeekPromise> mSeekRequest; 175 SeekJob mSeekJob; 176 }; 177 struct ShutdownEngine { 178 RefPtr<ShutdownPromise> mShutdown; 179 }; 180 // This state is used to recover the media engine after the MF CDM process 181 // crashes. 182 struct RecoverEngine : public InitEngine {}; 183 184 StateObject() : mData(ReadingMetadata()), mName(State::ReadingMetadata) {}; 185 explicit StateObject(InitEngine&& aArg) 186 : mData(std::move(aArg)), mName(State::InitEngine) {}; 187 explicit StateObject(RunningEngine&& aArg) 188 : mData(std::move(aArg)), mName(State::RunningEngine) {}; 189 explicit StateObject(SeekingData&& aArg) 190 : mData(std::move(aArg)), mName(State::SeekingData) {}; 191 explicit StateObject(ShutdownEngine&& aArg) 192 : mData(std::move(aArg)), mName(State::ShutdownEngine) {}; 193 explicit StateObject(RecoverEngine&& aArg) 194 : mData(std::move(aArg)), mName(State::RecoverEngine) {}; 195 196 bool IsInitEngine() const { return mData.is<InitEngine>(); } 197 bool IsReadingMetadata() const { return mData.is<ReadingMetadata>(); } 198 bool IsRunningEngine() const { return mData.is<RunningEngine>(); } 199 bool IsSeekingData() const { return mData.is<SeekingData>(); } 200 bool IsShutdownEngine() const { return mData.is<ShutdownEngine>(); } 201 bool IsRecoverEngine() const { return mData.is<RecoverEngine>(); } 202 203 InitEngine* AsInitEngine() { 204 if (IsInitEngine()) { 205 return &mData.as<InitEngine>(); 206 } 207 if (IsRecoverEngine()) { 208 return &mData.as<RecoverEngine>(); 209 } 210 return nullptr; 211 } 212 ReadingMetadata* AsReadingMetadata() { 213 return IsReadingMetadata() ? &mData.as<ReadingMetadata>() : nullptr; 214 } 215 SeekingData* AsSeekingData() { 216 return IsSeekingData() ? &mData.as<SeekingData>() : nullptr; 217 } 218 ShutdownEngine* AsShutdownEngine() { 219 return IsShutdownEngine() ? &mData.as<ShutdownEngine>() : nullptr; 220 } 221 222 Variant<InitEngine, ReadingMetadata, RunningEngine, SeekingData, 223 ShutdownEngine, RecoverEngine> 224 mData; 225 State mName; 226 } mState; 227 using State = StateObject::State; 228 229 void NotifyEventInternal(ExternalEngineEvent aEvent); 230 void NotifyErrorInternal(const MediaResult& aError); 231 void NotifyResizingInternal(uint32_t aWidth, uint32_t aHeight); 232 233 RefPtr<ShutdownPromise> Shutdown() override; 234 235 void SetPlaybackRate(double aPlaybackRate) override; 236 void BufferedRangeUpdated() override; 237 void VolumeChanged() override; 238 void PreservesPitchChanged() override; 239 void PlayStateChanged() override; 240 void LoopingChanged() override; 241 void PlaybackRateChanged(); 242 243 // Not supported. 244 void SetCanPlayThrough(bool aCanPlayThrough) override {} 245 void SetFragmentEndTime(const media::TimeUnit& aFragmentEndTime) override {} 246 247 void InitEngine(); 248 void OnEngineInitSuccess(); 249 void OnEngineInitFailure(); 250 251 void ReadMetadata(); 252 void OnMetadataRead(MetadataHolder&& aMetadata); 253 void OnMetadataNotRead(const MediaResult& aError); 254 bool IsFormatSupportedByExternalEngine(const MediaInfo& aInfo); 255 256 // Functions for handling external engine event. 257 void OnLoadedFirstFrame(); 258 void OnLoadedData(); 259 void OnWaiting(); 260 void OnPlaying(); 261 void OnSeeked(); 262 void OnBufferingStarted(); 263 void OnBufferingEnded(); 264 void OnTimeupdate(); 265 void OnEnded(); 266 void OnRequestAudio(); 267 void OnRequestVideo(); 268 269 void ResetDecode(); 270 271 void EndOfStream(MediaData::Type aType); 272 void WaitForData(MediaData::Type aType); 273 274 void StartRunningEngine(); 275 void RunningEngineUpdate(MediaData::Type aType); 276 277 void ChangeStateTo(State aNextState); 278 static const char* StateToStr(State aState); 279 280 RefPtr<MediaDecoder::SeekPromise> Seek(const SeekTarget& aTarget) override; 281 void SeekReader(); 282 void OnSeekResolved(const media::TimeUnit& aUnit); 283 void OnSeekRejected(const SeekRejectValue& aReject); 284 bool IsSeeking(); 285 void CheckIfSeekCompleted(); 286 287 void MaybeFinishWaitForData(); 288 289 void SetBlankVideoToVideoContainer(); 290 291 media::TimeUnit GetVideoThreshold(); 292 293 bool ShouldRunEngineUpdateForRequest(); 294 295 void UpdateSecondaryVideoContainer() override; 296 297 void RecoverFromCDMProcessCrashIfNeeded(); 298 299 void ReportTelemetry(const MediaResult& aError); 300 301 void DecodeError(const MediaResult& aError) override; 302 303 void NotifyAudibleStateChangeIfNeeded(); 304 305 UniquePtr<ExternalPlaybackEngine> mEngine; 306 307 bool mHasEnoughAudio = false; 308 bool mHasEnoughVideo = false; 309 bool mSentPlaybackEndedEvent = false; 310 bool mHasReceivedFirstDecodedVideoFrame = false; 311 312 // Only used if setting CDM happens before the engine finishes initialization. 313 MozPromiseHolder<SetCDMPromise> mSetCDMProxyPromise; 314 MozPromiseRequestHolder<SetCDMPromise> mSetCDMProxyRequest; 315 316 // If seek happens while the engine is still initializing, then we would 317 // postpone the seek until the engine is ready. 318 SeekJob mPendingSeek; 319 MozPromiseRequestHolder<MediaDecoder::SeekPromise> mPendingSeekRequest; 320 321 // It would be zero for audio-only playback. 322 gfx::IntSize mVideoDisplay; 323 324 // It would be set if playback is encrypted. 325 nsCString mKeySystem; 326 327 // This array stores the tasks which needs to be executed only after the 328 // engine is ready but is called before that. It will be executed when 329 // starting running the engine. 330 nsTArray<RefPtr<nsIRunnable>> mPendingTasks; 331 332 bool mHasFatalError = false; 333 }; 334 335 class ExternalPlaybackEngine { 336 public: 337 explicit ExternalPlaybackEngine(ExternalEngineStateMachine* aOwner) 338 : mOwner(aOwner) {} 339 340 virtual ~ExternalPlaybackEngine() = default; 341 342 enum class InitFlag { 343 None, 344 ShouldPreload, 345 EncryptedCustomIdent, 346 }; 347 using InitFlagSet = EnumSet<InitFlag, uint8_t>; 348 349 // Init the engine and specify the preload request. 350 virtual RefPtr<GenericNonExclusivePromise> Init( 351 const MediaInfo& aInfo, const InitFlagSet& aFlags) = 0; 352 virtual void Shutdown() = 0; 353 virtual uint64_t Id() const = 0; 354 virtual bool IsInited() const = 0; 355 356 // Following methods should only be called after successfully initialize the 357 // external engine. 358 virtual void Play() = 0; 359 virtual void Pause() = 0; 360 virtual void Seek(const media::TimeUnit& aTargetTime) = 0; 361 virtual void SetPlaybackRate(double aPlaybackRate) = 0; 362 virtual void SetVolume(double aVolume) = 0; 363 virtual void SetLooping(bool aLooping) = 0; 364 virtual void SetPreservesPitch(bool aPreservesPitch) = 0; 365 virtual media::TimeUnit GetCurrentPosition() = 0; 366 virtual void NotifyEndOfStream(TrackInfo::TrackType aType) = 0; 367 virtual bool SetCDMProxy(CDMProxy* aProxy) = 0; 368 virtual void NotifyResizing(uint32_t aWidth, uint32_t aHeight) = 0; 369 370 ExternalEngineStateMachine* const MOZ_NON_OWNING_REF mOwner; 371 }; 372 373 } // namespace mozilla 374 375 #endif // DOM_MEDIA_EXTERNALENGINESTATEMACHINE_H_