mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 10:18:41 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			1376 lines
		
	
	
	
		
			48 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1376 lines
		
	
	
	
		
			48 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_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!");
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
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();
 | 
						|
    state->mInitPromise = mEngine->Init(*mInfo, !mMinimizePreroll);
 | 
						|
    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() &&
 | 
						|
      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::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__);
 | 
						|
  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();
 | 
						|
  // TODO : implement audible check
 | 
						|
  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();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
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 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);
 | 
						|
  // Manually check the play state because the engine might be recovered from
 | 
						|
  // crash or just get recreated, so PlayStateChanged() won't be triggered.
 | 
						|
  if (mPlayState == MediaDecoder::PLAY_STATE_PLAYING) {
 | 
						|
    mEngine->Play();
 | 
						|
  }
 | 
						|
  if (HasAudio()) {
 | 
						|
    RunningEngineUpdate(MediaData::Type::AUDIO_DATA);
 | 
						|
  }
 | 
						|
  if (HasVideo()) {
 | 
						|
    RunningEngineUpdate(MediaData::Type::VIDEO_DATA);
 | 
						|
  }
 | 
						|
  // Run tasks which was called before the engine is ready.
 | 
						|
  if (!mPendingTasks.IsEmpty()) {
 | 
						|
    for (auto& task : mPendingTasks) {
 | 
						|
      Unused << OwnerThread()->Dispatch(task.forget());
 | 
						|
    }
 | 
						|
    mPendingTasks.Clear();
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ExternalEngineStateMachine::RunningEngineUpdate(MediaData::Type aType) {
 | 
						|
  AssertOnTaskQueue();
 | 
						|
  MOZ_ASSERT(mState.IsRunningEngine() || mState.IsSeekingData());
 | 
						|
  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<PlaybackStage> perfRecorder(MediaStage::RequestData);
 | 
						|
  RefPtr<ExternalEngineStateMachine> self = this;
 | 
						|
  mReader->RequestAudioData()
 | 
						|
      ->Then(
 | 
						|
          OwnerThread(), __func__,
 | 
						|
          [this, self, perfRecorder(std::move(perfRecorder))](
 | 
						|
              const RefPtr<AudioData>& aAudio) mutable {
 | 
						|
            perfRecorder.Record();
 | 
						|
            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;
 | 
						|
              case NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR:
 | 
						|
                // We will handle the process crash in `NotifyErrorInternal()`
 | 
						|
                // so here just silently ignore this.
 | 
						|
                break;
 | 
						|
              default:
 | 
						|
                ReportTelemetry(aError);
 | 
						|
                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<PlaybackStage> perfRecorder(MediaStage::RequestData,
 | 
						|
                                                  Info().mVideo.mImage.height);
 | 
						|
  RefPtr<ExternalEngineStateMachine> self = this;
 | 
						|
  mReader->RequestVideoData(GetVideoThreshold(), false)
 | 
						|
      ->Then(
 | 
						|
          OwnerThread(), __func__,
 | 
						|
          [this, self, perfRecorder(std::move(perfRecorder))](
 | 
						|
              const RefPtr<VideoData>& aVideo) mutable {
 | 
						|
            perfRecorder.Record();
 | 
						|
            mVideoDataRequest.Complete();
 | 
						|
            LOGV("Completed requesting video");
 | 
						|
            AUTO_PROFILER_LABEL(
 | 
						|
                "ExternalEngineStateMachine::OnRequestVideo:Resolved",
 | 
						|
                MEDIA_PLAYBACK);
 | 
						|
            MOZ_ASSERT(aVideo);
 | 
						|
            if (!mHasReceivedFirstDecodedVideoFrame) {
 | 
						|
              mHasReceivedFirstDecodedVideoFrame = true;
 | 
						|
              OnLoadedFirstFrame();
 | 
						|
            }
 | 
						|
            RunningEngineUpdate(MediaData::Type::VIDEO_DATA);
 | 
						|
            // Send image to PIP window.
 | 
						|
            if (mSecondaryVideoContainer.Ref()) {
 | 
						|
              mSecondaryVideoContainer.Ref()->SetCurrentFrame(
 | 
						|
                  mVideoDisplay, aVideo->mImage, TimeStamp::Now());
 | 
						|
            } else {
 | 
						|
              mVideoFrameContainer->SetCurrentFrame(
 | 
						|
                  mVideoDisplay, aVideo->mImage, TimeStamp::Now());
 | 
						|
            }
 | 
						|
          },
 | 
						|
          [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;
 | 
						|
              case NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR:
 | 
						|
                // We will handle the process crash in `NotifyErrorInternal()`
 | 
						|
                // so here just silently ignore this.
 | 
						|
                break;
 | 
						|
              default:
 | 
						|
                ReportTelemetry(aError);
 | 
						|
                DecodeError(aError);
 | 
						|
            }
 | 
						|
          })
 | 
						|
      ->Track(mVideoDataRequest);
 | 
						|
}
 | 
						|
 | 
						|
void ExternalEngineStateMachine::OnLoadedFirstFrame() {
 | 
						|
  AssertOnTaskQueue();
 | 
						|
  // We will wait until receive the first video frame.
 | 
						|
  if (mInfo->HasVideo() && !mHasReceivedFirstDecodedVideoFrame) {
 | 
						|
    LOGV("Hasn't received first decoded video frame");
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  LOGV("OnLoadedFirstFrame");
 | 
						|
  MediaDecoderEventVisibility visibility =
 | 
						|
      mSentFirstFrameLoadedEvent ? MediaDecoderEventVisibility::Suppressed
 | 
						|
                                 : MediaDecoderEventVisibility::Observable;
 | 
						|
  mSentFirstFrameLoadedEvent = true;
 | 
						|
  mFirstFrameLoadedEvent.Notify(UniquePtr<MediaInfo>(new MediaInfo(Info())),
 | 
						|
                                visibility);
 | 
						|
  mOnNextFrameStatus.Notify(MediaDecoderOwner::NEXT_FRAME_AVAILABLE);
 | 
						|
}
 | 
						|
 | 
						|
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();
 | 
						|
  if (!mState.IsSeekingData()) {
 | 
						|
    LOG("Engine Seeking has been completed, ignore the event");
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  MOZ_ASSERT(mState.IsSeekingData());
 | 
						|
 | 
						|
  const auto currentTime = mEngine->GetCurrentPosition();
 | 
						|
  auto* state = mState.AsSeekingData();
 | 
						|
  LOG("OnEngineSeeked, target=%" PRId64 ", currentTime=%" PRId64,
 | 
						|
      state->GetTargetTime().ToMicroseconds(), currentTime.ToMicroseconds());
 | 
						|
  // It's possible to receive multiple seeked event if we seek the engine
 | 
						|
  // before the previous seeking finishes, so we would wait until the last
 | 
						|
  // seeking is finished.
 | 
						|
  if (currentTime >= state->GetTargetTime()) {
 | 
						|
    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));
 | 
						|
  if (mState.IsShutdownEngine()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  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;
 | 
						|
      if (ShouldRunEngineUpdateForRequest()) {
 | 
						|
        RunningEngineUpdate(MediaData::Type::AUDIO_DATA);
 | 
						|
      }
 | 
						|
      break;
 | 
						|
    case ExternalEngineEvent::RequestForVideo:
 | 
						|
      mHasEnoughVideo = false;
 | 
						|
      if (ShouldRunEngineUpdateForRequest()) {
 | 
						|
        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;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
bool ExternalEngineStateMachine::ShouldRunEngineUpdateForRequest() {
 | 
						|
  // Running engine update will request new data, which could be run on
 | 
						|
  // `RunningEngine` or `SeekingData` state. However, in `SeekingData` we should
 | 
						|
  // only request new data after finishing reader seek, otherwise the reader
 | 
						|
  // would start requesting data from a wrong position.
 | 
						|
  return mState.IsRunningEngine() ||
 | 
						|
         (mState.AsSeekingData() &&
 | 
						|
          !mState.AsSeekingData()->mWaitingReaderSeeked);
 | 
						|
}
 | 
						|
 | 
						|
void ExternalEngineStateMachine::NotifyErrorInternal(
 | 
						|
    const MediaResult& aError) {
 | 
						|
  AssertOnTaskQueue();
 | 
						|
  LOG("Engine error: %s", aError.Description().get());
 | 
						|
  if (aError == NS_ERROR_DOM_MEDIA_NOT_SUPPORTED_ERR) {
 | 
						|
    // The external engine doesn't support the type, try to notify the decoder
 | 
						|
    // to use our own state machine again.
 | 
						|
    ReportTelemetry(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR);
 | 
						|
    DecodeError(
 | 
						|
        MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR));
 | 
						|
  } else if (aError == NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR) {
 | 
						|
    ReportTelemetry(NS_ERROR_DOM_MEDIA_REMOTE_DECODER_CRASHED_MF_CDM_ERR);
 | 
						|
    RecoverFromCDMProcessCrashIfNeeded();
 | 
						|
  } else {
 | 
						|
    ReportTelemetry(aError);
 | 
						|
    DecodeError(aError);
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ExternalEngineStateMachine::NotifyResizingInternal(uint32_t aWidth,
 | 
						|
                                                        uint32_t aHeight) {
 | 
						|
  LOG("video resize from [%d,%d] to [%d,%d]", mVideoDisplay.width,
 | 
						|
      mVideoDisplay.height, aWidth, aHeight);
 | 
						|
  mVideoDisplay = gfx::IntSize{aWidth, aHeight};
 | 
						|
}
 | 
						|
 | 
						|
void ExternalEngineStateMachine::RecoverFromCDMProcessCrashIfNeeded() {
 | 
						|
  AssertOnTaskQueue();
 | 
						|
  if (mState.IsRecoverEngine()) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  ProcessCrashMonitor::NotifyCrash();
 | 
						|
  if (!ProcessCrashMonitor::ShouldRecoverProcess()) {
 | 
						|
    LOG("CDM process has crashed too many times, abort recovery");
 | 
						|
    DecodeError(
 | 
						|
        MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR));
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  if (mState.IsInitEngine()) {
 | 
						|
    LOG("Failed on the engine initialization, the media engine playback might "
 | 
						|
        "not be supported");
 | 
						|
    DecodeError(
 | 
						|
        MediaResult(NS_ERROR_DOM_MEDIA_EXTERNAL_ENGINE_NOT_SUPPORTED_ERR));
 | 
						|
    return;
 | 
						|
  }
 | 
						|
 | 
						|
  LOG("CDM process crashed, recover the engine again (last time=%" PRId64 ")",
 | 
						|
      mCurrentPosition.Ref().ToMicroseconds());
 | 
						|
  ChangeStateTo(State::RecoverEngine);
 | 
						|
  if (HasVideo()) {
 | 
						|
    mVideoDataRequest.DisconnectIfExists();
 | 
						|
    mVideoWaitRequest.DisconnectIfExists();
 | 
						|
  }
 | 
						|
  if (HasAudio()) {
 | 
						|
    mAudioDataRequest.DisconnectIfExists();
 | 
						|
    mAudioWaitRequest.DisconnectIfExists();
 | 
						|
  }
 | 
						|
  // Ask the reader to shutdown current decoders which are no longer available
 | 
						|
  // due to the remote process crash.
 | 
						|
  mReader->ReleaseResources();
 | 
						|
  InitEngine();
 | 
						|
}
 | 
						|
 | 
						|
media::TimeUnit ExternalEngineStateMachine::GetVideoThreshold() {
 | 
						|
  AssertOnTaskQueue();
 | 
						|
  if (auto* state = mState.AsSeekingData()) {
 | 
						|
    return state->GetTargetTime();
 | 
						|
  }
 | 
						|
  return mCurrentPosition.Ref();
 | 
						|
}
 | 
						|
 | 
						|
void ExternalEngineStateMachine::UpdateSecondaryVideoContainer() {
 | 
						|
  AssertOnTaskQueue();
 | 
						|
  LOG("UpdateSecondaryVideoContainer=%p", mSecondaryVideoContainer.Ref().get());
 | 
						|
  mOnSecondaryVideoContainerInstalled.Notify(mSecondaryVideoContainer.Ref());
 | 
						|
}
 | 
						|
 | 
						|
RefPtr<SetCDMPromise> ExternalEngineStateMachine::SetCDMProxy(
 | 
						|
    CDMProxy* aProxy) {
 | 
						|
  if (mState.IsShutdownEngine()) {
 | 
						|
    return SetCDMPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
 | 
						|
  }
 | 
						|
 | 
						|
  if (!mEngine || !mEngine->IsInited()) {
 | 
						|
    LOG("SetCDMProxy is called before init");
 | 
						|
    mPendingTasks.AppendElement(NS_NewRunnableFunction(
 | 
						|
        "ExternalEngineStateMachine::SetCDMProxy",
 | 
						|
        [self = RefPtr{this}, proxy = RefPtr{aProxy}, this] {
 | 
						|
          SetCDMProxy(proxy)
 | 
						|
              ->Then(OwnerThread(), __func__,
 | 
						|
                     [self = RefPtr{this},
 | 
						|
                      this](const SetCDMPromise::ResolveOrRejectValue& aVal) {
 | 
						|
                       mSetCDMProxyRequest.Complete();
 | 
						|
                       if (aVal.IsResolve()) {
 | 
						|
                         mSetCDMProxyPromise.Resolve(true, __func__);
 | 
						|
                       } else {
 | 
						|
                         mSetCDMProxyPromise.Reject(NS_ERROR_DOM_MEDIA_CDM_ERR,
 | 
						|
                                                    __func__);
 | 
						|
                       }
 | 
						|
                     })
 | 
						|
              ->Track(mSetCDMProxyRequest);
 | 
						|
        }));
 | 
						|
    return mSetCDMProxyPromise.Ensure(__func__);
 | 
						|
  }
 | 
						|
 | 
						|
  // TODO : set CDM proxy again if we recreate the media engine after crash.
 | 
						|
  mKeySystem = NS_ConvertUTF16toUTF8(aProxy->KeySystem());
 | 
						|
  LOG("SetCDMProxy=%p (key-system=%s)", aProxy, mKeySystem.get());
 | 
						|
  MOZ_DIAGNOSTIC_ASSERT(mEngine);
 | 
						|
  // TODO : we should check the result of setting CDM proxy in the MFCDM process
 | 
						|
  if (!mEngine->SetCDMProxy(aProxy)) {
 | 
						|
    LOG("Failed to set CDM proxy on the engine");
 | 
						|
    return SetCDMPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CDM_ERR, __func__);
 | 
						|
  }
 | 
						|
  return MediaDecoderStateMachineBase::SetCDMProxy(aProxy);
 | 
						|
}
 | 
						|
 | 
						|
bool ExternalEngineStateMachine::IsCDMProxySupported(CDMProxy* aProxy) {
 | 
						|
#ifdef MOZ_WMF_CDM
 | 
						|
  MOZ_ASSERT(aProxy);
 | 
						|
  // 1=enabled encrypted and clear, 2=enabled encrytped
 | 
						|
  if (StaticPrefs::media_wmf_media_engine_enabled() != 1 &&
 | 
						|
      StaticPrefs::media_wmf_media_engine_enabled() != 2) {
 | 
						|
    return false;
 | 
						|
  }
 | 
						|
 | 
						|
  // The CDM needs to be hosted in the same process of the external engine, and
 | 
						|
  // only WMFCDM meets this requirement.
 | 
						|
  return aProxy->AsWMFCDMProxy();
 | 
						|
#else
 | 
						|
  return false;
 | 
						|
#endif
 | 
						|
}
 | 
						|
 | 
						|
void ExternalEngineStateMachine::ReportTelemetry(const MediaResult& aError) {
 | 
						|
  glean::mfcdm::ErrorExtra extraData;
 | 
						|
  extraData.errorName = Some(aError.ErrorName());
 | 
						|
  extraData.currentState = Some(nsAutoCString{StateToStr(mState.mName)});
 | 
						|
  nsAutoCString resolution;
 | 
						|
  if (mInfo) {
 | 
						|
    if (mInfo->HasAudio()) {
 | 
						|
      extraData.audioCodec = Some(mInfo->mAudio.mMimeType);
 | 
						|
    }
 | 
						|
    if (mInfo->HasVideo()) {
 | 
						|
      extraData.videoCodec = Some(mInfo->mVideo.mMimeType);
 | 
						|
      DetermineResolutionForTelemetry(*mInfo, resolution);
 | 
						|
      extraData.resolution = Some(resolution);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if (!mKeySystem.IsEmpty()) {
 | 
						|
    extraData.keySystem = Some(mKeySystem);
 | 
						|
  }
 | 
						|
  glean::mfcdm::error.Record(Some(extraData));
 | 
						|
  if (MOZ_LOG_TEST(gMediaDecoderLog, LogLevel::Debug)) {
 | 
						|
    nsPrintfCString logMessage{"MFCDM Error event, error=%s",
 | 
						|
                               aError.ErrorName().get()};
 | 
						|
    if (mInfo) {
 | 
						|
      if (mInfo->HasAudio()) {
 | 
						|
        logMessage.Append(
 | 
						|
            nsPrintfCString{", audio=%s", mInfo->mAudio.mMimeType.get()});
 | 
						|
      }
 | 
						|
      if (mInfo->HasVideo()) {
 | 
						|
        logMessage.Append(nsPrintfCString{", video=%s, resolution=%s",
 | 
						|
                                          mInfo->mVideo.mMimeType.get(),
 | 
						|
                                          resolution.get()});
 | 
						|
      }
 | 
						|
    }
 | 
						|
    if (!mKeySystem.IsEmpty()) {
 | 
						|
      logMessage.Append(nsPrintfCString{", keySystem=%s", mKeySystem.get()});
 | 
						|
    }
 | 
						|
    LOG("%s", logMessage.get());
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
void ExternalEngineStateMachine::DecodeError(const MediaResult& aError) {
 | 
						|
  if (aError != NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA ||
 | 
						|
      aError != NS_ERROR_DOM_MEDIA_CANCELED) {
 | 
						|
    mHasFatalError = true;
 | 
						|
  }
 | 
						|
  MediaDecoderStateMachineBase ::DecodeError(aError);
 | 
						|
}
 | 
						|
 | 
						|
#undef FMT
 | 
						|
#undef LOG
 | 
						|
#undef LOGV
 | 
						|
#undef LOGW
 | 
						|
#undef LOGE
 | 
						|
 | 
						|
}  // namespace mozilla
 |