Commit 71bcbbf6 authored by alwu's avatar alwu
Browse files

Bug 1756260 - part2 : implement a new state machine for the external engine. r=jolin

This patch introduces a new type of state machine that is used when the playback is controlled by an external playback engine which would provide high level operations but hide most low level tasks from its client. Eg. Media Foundation Media Engine.

The extenral engine should use events to communicate this state machine, and state machine should address those events in order to fullfill the need of the external engine.

Differential Revision: https://phabricator.services.mozilla.com/D140014
parent 2b0a067f
Loading
Loading
Loading
Loading
+835 −0

File added.

Preview size limit exceeded, changes collapsed.

+201 −0
Original line number Diff line number Diff line
/* 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/. */

#ifndef DOM_MEDIA_EXTERNALENGINESTATEMACHINE_H_
#define DOM_MEDIA_EXTERNALENGINESTATEMACHINE_H_

#include "MediaDecoderStateMachineBase.h"
#include "SeekJob.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<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::CreateAndReject(NS_ERROR_FAILURE, __func__);
  }

  void NotifyEvent(ExternalEngineEvent aEvent) {
    // On the engine manager thread.
    Unused << OwnerThread()->Dispatch(NS_NewRunnableFunction(
        "ExternalEngineStateMachine::NotifyEvent",
        [self = RefPtr{this}, aEvent] { self->NotifyEventInternal(aEvent); }));
  }

  const char* GetStateStr() const;

 private:
  ~ExternalEngineStateMachine() = default;

  void AssertOnTaskQueue() const { MOZ_ASSERT(OnTaskQueue()); }

  // Only modify on the task queue.
  enum class State {
    InitEngine,
    WaitingMetadata,
    RunningEngine,
    SeekingData,
    ShutdownEngine,
  } mState;

  void NotifyEventInternal(ExternalEngineEvent aEvent);

  RefPtr<ShutdownPromise> Shutdown() override;
  RefPtr<ShutdownPromise> ShutdownInternal();

  void SetPlaybackRate(double aPlaybackRate) override;
  void BufferedRangeUpdated() override;
  void VolumeChanged() override;
  void PreservesPitchChanged() override;
  void PlayStateChanged() override;
  void LoopingChanged() override;

  // Not supported.
  void SetIsLiveStream(bool aIsLiveStream) override {}
  void SetCanPlayThrough(bool aCanPlayThrough) override {}
  void SetFragmentEndTime(const media::TimeUnit& aFragmentEndTime) override {}

  void OnEngineInitSuccess();
  void OnEngineInitFailure();

  void OnMetadataRead(MetadataHolder&& aMetadata);
  void OnMetadataNotRead(const MediaResult& aError);

  // Functions for handling external engine event.
  void OnLoadedFirstFrame();
  void OnLoadedData();
  void OnWaiting();
  void OnPlaying();
  void OnSeeked();
  void CheckIfSeekCompleted();
  void OnBufferingStarted();
  void OnBufferingEnded();
  void OnTimeupdate();
  void OnEnded();
  void OnRequestAudio();
  void OnRequestVideo();

  void ChangeStateTo(State aState);

  void ResetDecode();

  void EndOfStream(MediaData::Type aType);
  void WaitForData(MediaData::Type aType);

  void StartRunningEngine();
  void RunningEngineUpdate(MediaData::Type aType);

  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);

  void MaybeFinishWaitForData();

  UniquePtr<ExternalPlaybackEngine> mEngine;

  MozPromiseRequestHolder<GenericNonExclusivePromise> mEngineInitRequest;
  MozPromiseRequestHolder<MediaFormatReader::MetadataPromise> mMetadataRequest;
  MozPromiseRequestHolder<MediaFormatReader::SeekPromise> mSeekRequest;

  Maybe<SeekJob> mSeekJob;

  // 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 mWaitingEngineSeekedEvent = false;
  bool mIsReaderSeekingCompleted = false;

  bool mHasEnoughAudio = false;
  bool mHasEnoughVideo = false;
  bool mSentPlaybackEndedEvent = false;
};

class ExternalPlaybackEngine {
 public:
  explicit ExternalPlaybackEngine(ExternalEngineStateMachine* aOwner)
      : mOwner(aOwner) {}

  virtual ~ExternalPlaybackEngine() = default;

  // Init the engine and specify the preload request.
  virtual RefPtr<GenericNonExclusivePromise> Init(bool aShouldPreload);
  virtual void Play() = 0;
  virtual void Pause() = 0;
  virtual void Seek(const media::TimeUnit& aTargetTime) = 0;
  virtual void Shutdown() = 0;
  virtual void SetPlaybackRate(double aPlaybackRate) = 0;
  virtual void SetVolume(double aVolume) = 0;
  virtual void SetLooping(bool aLooping);
  virtual void SetPreservesPitch(bool aPreservesPitch) = 0;
  virtual media::TimeUnit GetCurrentPosition() = 0;
  virtual void NotifyEndOfStream(TrackInfo::TrackType aType) = 0;
  virtual uint64_t Id() const = 0;
  virtual void SetMediaInfo(const MediaInfo& aInfo) = 0;

  ExternalEngineStateMachine* const MOZ_NON_OWNING_REF mOwner;
};

}  // namespace mozilla

#endif  // DOM_MEDIA_EXTERNALENGINESTATEMACHINE_H_
+0 −1
Original line number Diff line number Diff line
@@ -5,7 +5,6 @@
#include "MediaDecoderStateMachineBase.h"

#include "MediaDecoder.h"
#include "ReaderProxy.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/TaskQueue.h"
#include "nsThreadUtils.h"
+4 −3
Original line number Diff line number Diff line
@@ -6,9 +6,13 @@
#define DOM_MEDIA_MEDIADECODERSTATEMACHINEBASE_H_

#include "DecoderDoctorDiagnostics.h"
#include "MediaDecoder.h"
#include "MediaDecoderOwner.h"
#include "MediaEventSource.h"
#include "MediaInfo.h"
#include "MediaMetadataManager.h"
#include "ReaderProxy.h"
#include "VideoFrameContainer.h"
#include "mozilla/dom/MediaDebugInfoBinding.h"
#include "mozilla/Variant.h"
#include "nsISupportsImpl.h"
@@ -19,11 +23,8 @@ namespace mozilla {

class AbstractThread;
class FrameStatistics;
class MediaDecoder;
class MediaFormatReader;
class ReaderProxy;
class TaskQueue;
class VideoFrameContainer;

struct MediaPlaybackEvent {
  enum EventType {
+3 −0
Original line number Diff line number Diff line
@@ -148,6 +148,7 @@ EXPORTS += [
    "DOMMediaStream.h",
    "DriftCompensation.h",
    "DynamicResampler.h",
    "ExternalEngineStateMachine.h",
    "FileBlockCache.h",
    "ForwardedInputTrack.h",
    "FrameStatistics.h",
@@ -189,6 +190,7 @@ EXPORTS += [
    "PrincipalChangeObserver.h",
    "PrincipalHandle.h",
    "QueueObject.h",
    "ReaderProxy.h",
    "SeekJob.h",
    "SeekTarget.h",
    "SelfRef.h",
@@ -268,6 +270,7 @@ UNIFIED_SOURCES += [
    "DeviceInputTrack.cpp",
    "DOMMediaStream.cpp",
    "DynamicResampler.cpp",
    "ExternalEngineStateMachine.cpp",
    "FileBlockCache.cpp",
    "FileMediaResource.cpp",
    "ForwardedInputTrack.cpp",