Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/dom/media/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 129 kB image not shown  

Quelle  MediaFormatReader.cpp   Sprache: C

 
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* 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 "MediaFormatReader.h"

#include <algorithm>
#include <map>
#include <queue>

#include "AllocationPolicy.h"
#ifdef MOZ_AV1
#  include "AOMDecoder.h"
#endif
#include "DecoderBenchmark.h"
#include "MediaData.h"
#include "MediaDataDecoderProxy.h"
#include "MediaInfo.h"
#include "MP4Decoder.h"
#include "PDMFactory.h"
#include "PerformanceRecorder.h"
#include "VideoFrameContainer.h"
#include "VideoUtils.h"
#include "VPXDecoder.h"
#include "mozilla/AbstractThread.h"
#include "mozilla/CDMProxy.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/NotNull.h"
#include "mozilla/Preferences.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ProfilerMarkers.h"
#include "mozilla/SharedThreadPool.h"
#include "mozilla/StaticPrefs_media.h"
#include "mozilla/TaskQueue.h"
#include "mozilla/Unused.h"
#include "mozilla/glean/DomMediaMetrics.h"
#include "nsContentUtils.h"
#include "nsLiteralString.h"
#include "nsPrintfCString.h"
#include "nsTHashSet.h"

using namespace mozilla::media;

static mozilla::LazyLogModule sFormatDecoderLog("MediaFormatReader");
mozilla::LazyLogModule gMediaDemuxerLog("MediaDemuxer");

#define LOG(arg, ...)                                                  \
  DDMOZ_LOG(sFormatDecoderLog, mozilla::LogLevel::Debug, "::%s: " arg, \
            __func__, ##__VA_ARGS__)
#define LOGV(arg, ...)                                                   \
  DDMOZ_LOG(sFormatDecoderLog, mozilla::LogLevel::Verbose, "::%s: " arg, \
            __func__, ##__VA_ARGS__)

#define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead

namespace mozilla {

using MediaDataDecoderID = void*;

/**
 * This class tracks shutdown promises to ensure all decoders are shut down
 * completely before MFR continues the rest of the shutdown procedure.
 */

class MediaFormatReader::ShutdownPromisePool {
 public:
  ShutdownPromisePool()
      : mOnShutdownComplete(new ShutdownPromise::Private(__func__)) {}

  // Return a promise which will be resolved when all the tracking promises
  // are resolved. Note no more promises should be added for tracking once
  // this function is called.
  RefPtr<ShutdownPromise> Shutdown();

  // Track a shutdown promise.
  void Track(const RefPtr<ShutdownPromise>& aPromise);

  // Shut down a decoder and track its shutdown promise.
  void ShutdownDecoder(already_AddRefed<MediaDataDecoder> aDecoder) {
    Track(RefPtr<MediaDataDecoder>(aDecoder)->Shutdown());
  }

 private:
  bool mShutdown = false;
  const RefPtr<ShutdownPromise::Private> mOnShutdownComplete;
  nsTHashSet<RefPtr<ShutdownPromise>> mPromises;
};

RefPtr<ShutdownPromise> MediaFormatReader::ShutdownPromisePool::Shutdown() {
  MOZ_DIAGNOSTIC_ASSERT(!mShutdown);
  mShutdown = true;
  if (mPromises.Count() == 0) {
    mOnShutdownComplete->Resolve(true, __func__);
  }
  return mOnShutdownComplete;
}

void MediaFormatReader::ShutdownPromisePool::Track(
    const RefPtr<ShutdownPromise>& aPromise) {
  MOZ_DIAGNOSTIC_ASSERT(!mShutdown);
  MOZ_DIAGNOSTIC_ASSERT(!mPromises.Contains(aPromise));
  mPromises.Insert(aPromise);
  aPromise->Then(AbstractThread::GetCurrent(), __func__, [aPromise, this]() {
    MOZ_DIAGNOSTIC_ASSERT(mPromises.Contains(aPromise));
    mPromises.Remove(aPromise);
    if (mShutdown && mPromises.Count() == 0) {
      mOnShutdownComplete->Resolve(true, __func__);
    }
  });
}

void MediaFormatReader::DecoderData::ShutdownDecoder() {
  MOZ_ASSERT(mOwner->OnTaskQueue());

  MutexAutoLock lock(mMutex);

  if (!mDecoder) {
    // No decoder to shut down.
    return;
  }

  if (mFlushing) {
    // Flush is is in action. Shutdown will be initiated after flush completes.
    MOZ_DIAGNOSTIC_ASSERT(mShutdownPromise);
    mOwner->mShutdownPromisePool->Track(mShutdownPromise->Ensure(__func__));
    // The order of decoder creation and shutdown is handled by LocalAllocPolicy
    // and ShutdownPromisePool. MFR can now reset these members to a fresh state
    // and be ready to create new decoders again without explicitly waiting for
    // flush/shutdown to complete.
    mShutdownPromise = nullptr;
    mFlushing = false;
  } else {
    // No flush is in action. We can shut down the decoder now.
    mOwner->mShutdownPromisePool->Track(mDecoder->Shutdown());
  }

  // mShutdownPromisePool will handle the order of decoder shutdown so
  // we can forget mDecoder and be ready to create a new one.
  mDecoder = nullptr;
  mDescription = "shutdown"_ns;
  mHasReportedVideoHardwareSupportTelemtry = false;
  mOwner->ScheduleUpdate(mType == MediaData::Type::AUDIO_DATA
                             ? TrackType::kAudioTrack
                             : TrackType::kVideoTrack);
}

void MediaFormatReader::DecoderData::Flush() {
  AUTO_PROFILER_LABEL("MediaFormatReader::Flush", MEDIA_PLAYBACK);
  MOZ_ASSERT(mOwner->OnTaskQueue());

  if (mFlushing || mFlushed) {
    // Flush still pending or already flushed, nothing more to do.
    return;
  }
  mDecodeRequest.DisconnectIfExists();
  mDrainRequest.DisconnectIfExists();
  mDrainState = DrainState::None;
  CancelWaitingForKey();
  mOutput.Clear();
  mNumSamplesInput = 0;
  mNumSamplesOutput = 0;
  mSizeOfQueue = 0;
  if (mDecoder) {
    TrackType type = mType == MediaData::Type::AUDIO_DATA
                         ? TrackType::kAudioTrack
                         : TrackType::kVideoTrack;
    mFlushing = true;
    MOZ_DIAGNOSTIC_ASSERT(!mShutdownPromise);
    mShutdownPromise = new SharedShutdownPromiseHolder();
    RefPtr<SharedShutdownPromiseHolder> p = mShutdownPromise;
    RefPtr<MediaDataDecoder> d = mDecoder;
    DDLOGEX2("MediaFormatReader::DecoderData"this, DDLogCategory::Log,
             "flushing", DDNoValue{});
    mDecoder->Flush()->Then(
        mOwner->OwnerThread(), __func__,
        [type, this, p, d]() {
          AUTO_PROFILER_LABEL("MediaFormatReader::Flush:Resolved",
                              MEDIA_PLAYBACK);
          DDLOGEX2("MediaFormatReader::DecoderData"this, DDLogCategory::Log,
                   "flushed", DDNoValue{});
          if (!p->IsEmpty()) {
            // Shutdown happened before flush completes.
            // Let's continue to shut down the decoder. Note
            // we don't access |this| because this decoder
            // is no longer managed by MFR::DecoderData.
            d->Shutdown()->ChainTo(p->Steal(), __func__);
            return;
          }
          mFlushing = false;
          mShutdownPromise = nullptr;
          mOwner->ScheduleUpdate(type);
        },
        [type, this, p, d](const MediaResult& aError) {
          AUTO_PROFILER_LABEL("MediaFormatReader::Flush:Rejected",
                              MEDIA_PLAYBACK);
          DDLOGEX2("MediaFormatReader::DecoderData"this, DDLogCategory::Log,
                   "flush_error", aError);
          if (!p->IsEmpty()) {
            d->Shutdown()->ChainTo(p->Steal(), __func__);
            return;
          }
          mFlushing = false;
          mShutdownPromise = nullptr;
          mOwner->NotifyError(type, aError);
        });
  }
  mFlushed = true;
}

class MediaFormatReader::DecoderFactory {
  using InitPromise = MediaDataDecoder::InitPromise;
  using TokenPromise = AllocPolicy::Promise;
  using Token = AllocPolicy::Token;
  using CreateDecoderPromise = PlatformDecoderModule::CreateDecoderPromise;

 public:
  explicit DecoderFactory(MediaFormatReader* aOwner)
      : mAudio(aOwner->mAudio, TrackInfo::kAudioTrack, aOwner->OwnerThread()),
        mVideo(aOwner->mVideo, TrackInfo::kVideoTrack, aOwner->OwnerThread()),
        mOwner(WrapNotNull(aOwner)) {
    DecoderDoctorLogger::LogConstruction("MediaFormatReader::DecoderFactory",
                                         this);
    DecoderDoctorLogger::LinkParentAndChild(
        aOwner, "decoder factory""MediaFormatReader::DecoderFactory"this);
  }

  ~DecoderFactory() {
    DecoderDoctorLogger::LogDestruction("MediaFormatReader::DecoderFactory",
                                        this);
  }

  void CreateDecoder(TrackType aTrack);

  // Shutdown any decoder pending initialization and reset mAudio/mVideo to its
  // pristine state so CreateDecoder() is ready to be called again immediately.
  void ShutdownDecoder(TrackType aTrack) {
    MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack ||
               aTrack == TrackInfo::kVideoTrack);
    auto& data = aTrack == TrackInfo::kAudioTrack ? mAudio : mVideo;
    data.mPolicy->Cancel();
    data.mTokenRequest.DisconnectIfExists();
    if (data.mLiveToken) {
      // We haven't completed creation of the decoder, and it hasn't been
      // initialised yet.
      data.mLiveToken = nullptr;
      // The decoder will be shutdown as soon as it's available and tracked by
      // the ShutdownPromisePool.
      mOwner->mShutdownPromisePool->Track(data.mCreateDecoderPromise->Then(
          mOwner->mTaskQueue, __func__,
          [](CreateDecoderPromise::ResolveOrRejectValue&& aResult) {
            if (aResult.IsReject()) {
              return ShutdownPromise::CreateAndResolve(true, __func__);
            }
            return aResult.ResolveValue()->Shutdown();
          }));
      // Free the token to leave room for a new decoder.
      data.mToken = nullptr;
    }
    data.mInitRequest.DisconnectIfExists();
    if (data.mDecoder) {
      mOwner->mShutdownPromisePool->ShutdownDecoder(data.mDecoder.forget());
    }
    data.mStage = Stage::None;
    MOZ_ASSERT(!data.mToken);
  }

 private:
  enum class Stage : int8_t { None, WaitForToken, CreateDecoder, WaitForInit };

  struct Data {
    Data(DecoderData& aOwnerData, TrackType aTrack, TaskQueue* aThread)
        : mOwnerData(aOwnerData),
          mTrack(aTrack),
          mPolicy(new SingleAllocPolicy(aTrack, aThread)) {}
    DecoderData& mOwnerData;
    const TrackType mTrack;
    RefPtr<SingleAllocPolicy> mPolicy;
    Stage mStage = Stage::None;
    RefPtr<Token> mToken;
    RefPtr<MediaDataDecoder> mDecoder;
    MozPromiseRequestHolder<TokenPromise> mTokenRequest;
    struct DecoderCancelled : public SupportsWeakPtr {
      NS_INLINE_DECL_REFCOUNTING_ONEVENTTARGET(DecoderCancelled)
     private:
      ~DecoderCancelled() = default;
    };
    // Set when decoder is about to be created. If cleared before the decoder
    // creation promise is resolved; it indicates that Shutdown() was called and
    // further processing such as initialization should stop.
    RefPtr<DecoderCancelled> mLiveToken;
    RefPtr<CreateDecoderPromise> mCreateDecoderPromise;
    MozPromiseRequestHolder<InitPromise> mInitRequest;
  } mAudio, mVideo;

  void RunStage(Data& aData);
  void DoCreateDecoder(Data& aData);
  void DoInitDecoder(Data& aData);

  // guaranteed to be valid by the owner.
  const NotNull<MediaFormatReader*> mOwner;
};

void MediaFormatReader::DecoderFactory::CreateDecoder(TrackType aTrack) {
  MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack ||
             aTrack == TrackInfo::kVideoTrack);
  Data& data = aTrack == TrackInfo::kAudioTrack ? mAudio : mVideo;
  MOZ_DIAGNOSTIC_ASSERT_IF(mOwner->GetDecoderData(data.mTrack).IsEncrypted(),
                           mOwner->mCDMProxy);
  RunStage(data);
}

void MediaFormatReader::DecoderFactory::RunStage(Data& aData) {
  switch (aData.mStage) {
    case Stage::None: {
      MOZ_DIAGNOSTIC_ASSERT(!aData.mToken);
      aData.mPolicy->Alloc()
          ->Then(
              mOwner->OwnerThread(), __func__,
              [this, &aData](RefPtr<Token> aToken) {
                aData.mTokenRequest.Complete();
                aData.mToken = std::move(aToken);
                aData.mStage = Stage::CreateDecoder;
                RunStage(aData);
              },
              [&aData]() {
                aData.mTokenRequest.Complete();
                aData.mStage = Stage::None;
              })
          ->Track(aData.mTokenRequest);
      aData.mStage = Stage::WaitForToken;
      break;
    }

    case Stage::WaitForToken: {
      MOZ_DIAGNOSTIC_ASSERT(!aData.mToken);
      MOZ_DIAGNOSTIC_ASSERT(aData.mTokenRequest.Exists());
      break;
    }

    case Stage::CreateDecoder: {
      MOZ_DIAGNOSTIC_ASSERT(aData.mToken);
      MOZ_DIAGNOSTIC_ASSERT(!aData.mDecoder);
      MOZ_DIAGNOSTIC_ASSERT(!aData.mInitRequest.Exists());

      DoCreateDecoder(aData);
      aData.mStage = Stage::WaitForInit;
      break;
    }

    case Stage::WaitForInit: {
      MOZ_DIAGNOSTIC_ASSERT((aData.mDecoder && aData.mInitRequest.Exists()) ||
                            aData.mLiveToken);
      break;
    }
  }
}

void MediaFormatReader::DecoderFactory::DoCreateDecoder(Data& aData) {
  AUTO_PROFILER_LABEL("DecoderFactory::DoCreateDecoder", MEDIA_PLAYBACK);
  auto& ownerData = aData.mOwnerData;
  auto& decoder = mOwner->GetDecoderData(aData.mTrack);

  RefPtr<PDMFactory> platform = new PDMFactory();
  if (decoder.IsEncrypted()) {
    MOZ_DIAGNOSTIC_ASSERT(mOwner->mCDMProxy);
    platform->SetCDMProxy(mOwner->mCDMProxy);
  }

  RefPtr<PlatformDecoderModule::CreateDecoderPromise> p;
  MediaFormatReader* owner = mOwner;
  auto onWaitingForKeyEvent =
      [owner = ThreadSafeWeakPtr<MediaFormatReader>(owner)]() {
        RefPtr<MediaFormatReader> mfr(owner);
        MOZ_DIAGNOSTIC_ASSERT(mfr, "The MediaFormatReader didn't wait for us");
        return mfr ? &mfr->OnTrackWaitingForKeyProducer() : nullptr;
      };

  switch (aData.mTrack) {
    case TrackInfo::kAudioTrack: {
      p = platform->CreateDecoder(
          {*ownerData.GetCurrentInfo()->GetAsAudioInfo(), mOwner->mCrashHelper,
           CreateDecoderParams::UseNullDecoder(ownerData.mIsNullDecode),
           TrackInfo::kAudioTrack, std::move(onWaitingForKeyEvent),
           mOwner->mMediaEngineId, mOwner->mTrackingId,
           mOwner->mEncryptedCustomIdent
               ? CreateDecoderParams::EncryptedCustomIdent::True
               : CreateDecoderParams::EncryptedCustomIdent::False});
      break;
    }

    case TrackType::kVideoTrack: {
      // Decoders use the layers backend to decide if they can use hardware
      // decoding, so specify LAYERS_NONE if we want to forcibly disable it.
      using Option = CreateDecoderParams::Option;
      using OptionSet = CreateDecoderParams::OptionSet;

      p = platform->CreateDecoder(
          {*ownerData.GetCurrentInfo()->GetAsVideoInfo(),
           mOwner->mKnowsCompositor, mOwner->GetImageContainer(),
           mOwner->mCrashHelper,
           CreateDecoderParams::UseNullDecoder(ownerData.mIsNullDecode),
           TrackType::kVideoTrack, std::move(onWaitingForKeyEvent),
           CreateDecoderParams::VideoFrameRate(ownerData.mMeanRate.Mean()),
           OptionSet(ownerData.mHardwareDecodingDisabled
                         ? Option::HardwareDecoderNotAllowed
                         : Option::Default),
           mOwner->mMediaEngineId, mOwner->mTrackingId,
           mOwner->mEncryptedCustomIdent
               ? CreateDecoderParams::EncryptedCustomIdent::True
               : CreateDecoderParams::EncryptedCustomIdent::False});
      break;
    }

    default:
      p = PlatformDecoderModule::CreateDecoderPromise::CreateAndReject(
          NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
  }

  aData.mLiveToken = MakeRefPtr<Data::DecoderCancelled>();

  aData.mCreateDecoderPromise = p->Then(
      mOwner->OwnerThread(), __func__,
      [this, &aData, &ownerData, live = WeakPtr{aData.mLiveToken},
       owner = ThreadSafeWeakPtr<MediaFormatReader>(owner)](
          RefPtr<MediaDataDecoder>&& aDecoder) {
        if (!live) {
          return CreateDecoderPromise::CreateAndResolve(std::move(aDecoder),
                                                        __func__);
        }
        aData.mLiveToken = nullptr;
        aData.mDecoder = new MediaDataDecoderProxy(
            aDecoder.forget(), do_AddRef(ownerData.mTaskQueue.get()));
        aData.mDecoder = new AllocationWrapper(aData.mDecoder.forget(),
                                               aData.mToken.forget());
        DecoderDoctorLogger::LinkParentAndChild(
            aData.mDecoder.get(), "decoder",
            "MediaFormatReader::DecoderFactory"this);

        DoInitDecoder(aData);

        return CreateDecoderPromise::CreateAndResolve(aData.mDecoder, __func__);
      },
      [this, &aData,
       live = WeakPtr{aData.mLiveToken}](const MediaResult& aError) {
        NS_WARNING("Error constructing decoders");
        if (!live) {
          return CreateDecoderPromise::CreateAndReject(aError, __func__);
        }
        aData.mLiveToken = nullptr;
        aData.mToken = nullptr;
        aData.mStage = Stage::None;
        aData.mOwnerData.mDescription = aError.Description();
        DDLOGEX2("MediaFormatReader::DecoderFactory"this, DDLogCategory::Log,
                 "create_decoder_error", aError);
        mOwner->NotifyError(aData.mTrack, aError);

        return CreateDecoderPromise::CreateAndReject(aError, __func__);
      });
}

void MediaFormatReader::DecoderFactory::DoInitDecoder(Data& aData) {
  AUTO_PROFILER_LABEL("DecoderFactory::DoInitDecoder", MEDIA_PLAYBACK);
  auto& ownerData = aData.mOwnerData;

  DDLOGEX2("MediaFormatReader::DecoderFactory"this, DDLogCategory::Log,
           "initialize_decoder", DDNoValue{});
  aData.mDecoder->Init()
      ->Then(
          mOwner->OwnerThread(), __func__,
          [this, &aData, &ownerData](TrackType aTrack) {
            AUTO_PROFILER_LABEL("DecoderFactory::DoInitDecoder:Resolved",
                                MEDIA_PLAYBACK);
            aData.mInitRequest.Complete();
            aData.mStage = Stage::None;
            MutexAutoLock lock(ownerData.mMutex);
            ownerData.mDecoder = std::move(aData.mDecoder);
            ownerData.mDescription = ownerData.mDecoder->GetDescriptionName();
            DDLOGEX2("MediaFormatReader::DecoderFactory"this,
                     DDLogCategory::Log, "decoder_initialized", DDNoValue{});
            DecoderDoctorLogger::LinkParentAndChild(
                "MediaFormatReader::DecoderData", &ownerData, "decoder",
                ownerData.mDecoder.get());
            mOwner->SetVideoDecodeThreshold();
            mOwner->ScheduleUpdate(aTrack);
            if (aTrack == TrackInfo::kVideoTrack) {
              DecoderBenchmark::CheckVersion(
                  ownerData.GetCurrentInfo()->mMimeType);
            }
            if (aTrack == TrackInfo::kAudioTrack) {
              ownerData.mProcessName = ownerData.mDecoder->GetProcessName();
              ownerData.mCodecName = ownerData.mDecoder->GetCodecName();
            }
            nsCString needsConversion;
            switch (ownerData.mDecoder->NeedsConversion()) {
              case MediaDataDecoder::ConversionRequired::kNeedNone:
                needsConversion = "false";
                break;
              case MediaDataDecoder::ConversionRequired::kNeedAVCC:
                needsConversion = "AVCC";
                break;
              case MediaDataDecoder::ConversionRequired::kNeedAnnexB:
                needsConversion = "AnnexB";
                break;
              default:
                needsConversion = "Unknown";
            }
            nsCString dummy;
            MOZ_LOG_FMT(sFormatDecoderLog, mozilla::LogLevel::Debug,
                        "Decoder init finished for "
                        "{} codec: \"{}\", "
                        "description: \"{}\", "
                        "process: \"{}\", "
                        "hw: \"{}\", "
                        "needs conversion: \"{}\"",
                        (aTrack == TrackInfo::kVideoTrack) ? "video" : "audio",
                        ownerData.mDecoder->GetCodecName(),
                        ownerData.mDecoder->GetDescriptionName(),
                        ownerData.mDecoder->GetProcessName(),
                        ownerData.mDecoder->IsHardwareAccelerated(dummy),
                        needsConversion);
          },
          [this, &aData, &ownerData](const MediaResult& aError) {
            AUTO_PROFILER_LABEL("DecoderFactory::DoInitDecoder:Rejected",
                                MEDIA_PLAYBACK);
            aData.mInitRequest.Complete();
            MOZ_RELEASE_ASSERT(!ownerData.mDecoder,
                               "Can't have a decoder already set");
            aData.mStage = Stage::None;
            mOwner->mShutdownPromisePool->ShutdownDecoder(
                aData.mDecoder.forget());
            DDLOGEX2("MediaFormatReader::DecoderFactory"this,
                     DDLogCategory::Log, "initialize_decoder_error", aError);
            mOwner->NotifyError(aData.mTrack, aError);
          })
      ->Track(aData.mInitRequest);
}

// DemuxerProxy ensures that the original main demuxer is only ever accessed
// via its own dedicated task queue.
// This ensure that the reader's taskqueue will never blocked while a demuxer
// is itself blocked attempting to access the MediaCache or the MediaResource.
class MediaFormatReader::DemuxerProxy {
  using TrackType = TrackInfo::TrackType;
  class Wrapper;

 public:
  explicit DemuxerProxy(MediaDataDemuxer* aDemuxer)
      : mTaskQueue(TaskQueue::Create(
            GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
            "DemuxerProxy::mTaskQueue")),
        mData(new Data(aDemuxer)) {
    MOZ_COUNT_CTOR(DemuxerProxy);
  }

  MOZ_COUNTED_DTOR(DemuxerProxy)

  RefPtr<ShutdownPromise> Shutdown() {
    RefPtr<Data> data = std::move(mData);
    return InvokeAsync(mTaskQueue, __func__, [data]() {
      // We need to clear our reference to the demuxer now. So that in the event
      // the init promise wasn't resolved, such as what can happen with the
      // mediasource demuxer that is waiting on more data, it will force the
      // init promise to be rejected.
      data->mDemuxer = nullptr;
      data->mAudioDemuxer = nullptr;
      data->mVideoDemuxer = nullptr;
      return ShutdownPromise::CreateAndResolve(true, __func__);
    });
  }

  RefPtr<MediaDataDemuxer::InitPromise> Init();

  Wrapper* GetTrackDemuxer(TrackType aTrack, uint32_t aTrackNumber) {
    MOZ_RELEASE_ASSERT(mData && mData->mInitDone);

    switch (aTrack) {
      case TrackInfo::kAudioTrack:
        return mData->mAudioDemuxer;
      case TrackInfo::kVideoTrack:
        return mData->mVideoDemuxer;
      default:
        return nullptr;
    }
  }

  uint32_t GetNumberTracks(TrackType aTrack) const {
    MOZ_RELEASE_ASSERT(mData && mData->mInitDone);

    switch (aTrack) {
      case TrackInfo::kAudioTrack:
        return mData->mNumAudioTrack;
      case TrackInfo::kVideoTrack:
        return mData->mNumVideoTrack;
      default:
        return 0;
    }
  }

  bool IsSeekable() const {
    MOZ_RELEASE_ASSERT(mData && mData->mInitDone);

    return mData->mSeekable;
  }

  bool IsSeekableOnlyInBufferedRanges() const {
    MOZ_RELEASE_ASSERT(mData && mData->mInitDone);

    return mData->mSeekableOnlyInBufferedRange;
  }

  UniquePtr<EncryptionInfo> GetCrypto() const {
    MOZ_RELEASE_ASSERT(mData && mData->mInitDone);

    if (!mData->mCrypto) {
      return nullptr;
    }
    auto crypto = MakeUnique<EncryptionInfo>();
    *crypto = *mData->mCrypto;
    return crypto;
  }

  RefPtr<NotifyDataArrivedPromise> NotifyDataArrived();

  bool ShouldComputeStartTime() const {
    MOZ_RELEASE_ASSERT(mData && mData->mInitDone);

    return mData->mShouldComputeStartTime;
  }

 private:
  const RefPtr<TaskQueue> mTaskQueue;
  struct Data {
    NS_INLINE_DECL_THREADSAFE_REFCOUNTING(Data)

    explicit Data(MediaDataDemuxer* aDemuxer)
        : mInitDone(false), mDemuxer(aDemuxer) {}

    Atomic<bool> mInitDone;
    // Only ever accessed over mTaskQueue once.
    RefPtr<MediaDataDemuxer> mDemuxer;
    // Only accessed once InitPromise has been resolved and immutable after.
    // So we can safely access them without the use of the mutex.
    uint32_t mNumAudioTrack = 0;
    RefPtr<Wrapper> mAudioDemuxer;
    uint32_t mNumVideoTrack = 0;
    RefPtr<Wrapper> mVideoDemuxer;
    bool mSeekable = false;
    bool mSeekableOnlyInBufferedRange = false;
    bool mShouldComputeStartTime = true;
    UniquePtr<EncryptionInfo> mCrypto;

   private:
    ~Data() = default;
  };
  RefPtr<Data> mData;
};

class MediaFormatReader::DemuxerProxy::Wrapper : public MediaTrackDemuxer {
 public:
  Wrapper(MediaTrackDemuxer* aTrackDemuxer, TaskQueue* aTaskQueue)
      : mMutex("TrackDemuxer Mutex"),
        mTaskQueue(aTaskQueue),
        mGetSamplesMayBlock(aTrackDemuxer->GetSamplesMayBlock()),
        mInfo(aTrackDemuxer->GetInfo()),
        mTrackDemuxer(aTrackDemuxer) {
    DecoderDoctorLogger::LogConstructionAndBase(
        "MediaFormatReader::DemuxerProxy::Wrapper"this,
        static_cast<const MediaTrackDemuxer*>(this));
    DecoderDoctorLogger::LinkParentAndChild(
        "MediaFormatReader::DemuxerProxy::Wrapper"this"track demuxer",
        aTrackDemuxer);
  }

  UniquePtr<TrackInfo> GetInfo() const override {
    if (!mInfo) {
      return nullptr;
    }
    return mInfo->Clone();
  }

  RefPtr<SeekPromise> Seek(const TimeUnit& aTime) override {
    RefPtr<Wrapper> self = this;
    return InvokeAsync(
               mTaskQueue, __func__,
               [self, aTime]() { return self->mTrackDemuxer->Seek(aTime); })
        ->Then(
            mTaskQueue, __func__,
            [self](const TimeUnit& aTime) {
              self->UpdateRandomAccessPoint();
              return SeekPromise::CreateAndResolve(aTime, __func__);
            },
            [self](const MediaResult& aError) {
              self->UpdateRandomAccessPoint();
              return SeekPromise::CreateAndReject(aError, __func__);
            });
  }

  RefPtr<SamplesPromise> GetSamples(int32_t aNumSamples) override {
    RefPtr<Wrapper> self = this;
    return InvokeAsync(mTaskQueue, __func__,
                       [self, aNumSamples]() {
                         return self->mTrackDemuxer->GetSamples(aNumSamples);
                       })
        ->Then(
            mTaskQueue, __func__,
            [self](RefPtr<SamplesHolder> aSamples) {
              self->UpdateRandomAccessPoint();
              return SamplesPromise::CreateAndResolve(aSamples.forget(),
                                                      __func__);
            },
            [self](const MediaResult& aError) {
              self->UpdateRandomAccessPoint();
              return SamplesPromise::CreateAndReject(aError, __func__);
            });
  }

  bool GetSamplesMayBlock() const override { return mGetSamplesMayBlock; }

  void Reset() override {
    RefPtr<Wrapper> self = this;
    nsresult rv = mTaskQueue->Dispatch(NS_NewRunnableFunction(
        "MediaFormatReader::DemuxerProxy::Wrapper::Reset",
        [self]() { self->mTrackDemuxer->Reset(); }));
    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    Unused << rv;
  }

  nsresult GetNextRandomAccessPoint(TimeUnit* aTime) override {
    MutexAutoLock lock(mMutex);
    if (NS_SUCCEEDED(mNextRandomAccessPointResult)) {
      *aTime = mNextRandomAccessPoint;
    }
    return mNextRandomAccessPointResult;
  }

  RefPtr<SkipAccessPointPromise> SkipToNextRandomAccessPoint(
      const TimeUnit& aTimeThreshold) override {
    RefPtr<Wrapper> self = this;
    return InvokeAsync(
               mTaskQueue, __func__,
               [self, aTimeThreshold]() {
                 return self->mTrackDemuxer->SkipToNextRandomAccessPoint(
                     aTimeThreshold);
               })
        ->Then(
            mTaskQueue, __func__,
            [self](uint32_t aVal) {
              self->UpdateRandomAccessPoint();
              return SkipAccessPointPromise::CreateAndResolve(aVal, __func__);
            },
            [self](const SkipFailureHolder& aError) {
              self->UpdateRandomAccessPoint();
              return SkipAccessPointPromise::CreateAndReject(aError, __func__);
            });
  }

  TimeIntervals GetBuffered() override {
    MutexAutoLock lock(mMutex);
    return mBuffered;
  }

  void BreakCycles() override {}

 private:
  Mutex mMutex MOZ_UNANNOTATED;
  const RefPtr<TaskQueue> mTaskQueue;
  const bool mGetSamplesMayBlock;
  const UniquePtr<TrackInfo> mInfo;
  // mTrackDemuxer is only ever accessed on demuxer's task queue.
  RefPtr<MediaTrackDemuxer> mTrackDemuxer;
  // All following members are protected by mMutex
  nsresult mNextRandomAccessPointResult = NS_OK;
  TimeUnit mNextRandomAccessPoint;
  TimeIntervals mBuffered;
  friend class DemuxerProxy;

  ~Wrapper() {
    RefPtr<MediaTrackDemuxer> trackDemuxer = std::move(mTrackDemuxer);
    nsresult rv = mTaskQueue->Dispatch(NS_NewRunnableFunction(
        "MediaFormatReader::DemuxerProxy::Wrapper::~Wrapper",
        [trackDemuxer]() { trackDemuxer->BreakCycles(); }));
    MOZ_DIAGNOSTIC_ASSERT(NS_SUCCEEDED(rv));
    Unused << rv;
    DecoderDoctorLogger::LogDestruction(
        "MediaFormatReader::DemuxerProxy::Wrapper"this);
  }

  void UpdateRandomAccessPoint() {
    MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
    if (!mTrackDemuxer) {
      // Detached.
      return;
    }
    MutexAutoLock lock(mMutex);
    mNextRandomAccessPointResult =
        mTrackDemuxer->GetNextRandomAccessPoint(&mNextRandomAccessPoint);
  }

  void UpdateBuffered() {
    MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
    if (!mTrackDemuxer) {
      // Detached.
      return;
    }
    MutexAutoLock lock(mMutex);
    mBuffered = mTrackDemuxer->GetBuffered();
  }
};

RefPtr<MediaDataDemuxer::InitPromise> MediaFormatReader::DemuxerProxy::Init() {
  AUTO_PROFILER_LABEL("DemuxerProxy::Init", MEDIA_PLAYBACK);
  using InitPromise = MediaDataDemuxer::InitPromise;

  RefPtr<Data> data = mData;
  RefPtr<TaskQueue> taskQueue = mTaskQueue;
  return InvokeAsync(mTaskQueue, __func__,
                     [data, taskQueue]() {
                       if (!data->mDemuxer) {
                         return InitPromise::CreateAndReject(
                             NS_ERROR_DOM_MEDIA_CANCELED, __func__);
                       }
                       return data->mDemuxer->Init();
                     })
      ->Then(
          taskQueue, __func__,
          [data, taskQueue]() {
            AUTO_PROFILER_LABEL("DemuxerProxy::Init:Resolved", MEDIA_PLAYBACK);
            if (!data->mDemuxer) {  // Was shutdown.
              return InitPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED,
                                                  __func__);
            }
            data->mNumAudioTrack =
                data->mDemuxer->GetNumberTracks(TrackInfo::kAudioTrack);
            if (data->mNumAudioTrack) {
              RefPtr<MediaTrackDemuxer> d =
                  data->mDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
              if (d) {
                RefPtr<Wrapper> wrapper =
                    new DemuxerProxy::Wrapper(d, taskQueue);
                wrapper->UpdateBuffered();
                data->mAudioDemuxer = wrapper;
                DecoderDoctorLogger::LinkParentAndChild(
                    data->mDemuxer.get(), "decoder factory wrapper",
                    "MediaFormatReader::DecoderFactory::Wrapper",
                    wrapper.get());
              }
            }
            data->mNumVideoTrack =
                data->mDemuxer->GetNumberTracks(TrackInfo::kVideoTrack);
            if (data->mNumVideoTrack) {
              RefPtr<MediaTrackDemuxer> d =
                  data->mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
              if (d) {
                RefPtr<Wrapper> wrapper =
                    new DemuxerProxy::Wrapper(d, taskQueue);
                wrapper->UpdateBuffered();
                data->mVideoDemuxer = wrapper;
                DecoderDoctorLogger::LinkParentAndChild(
                    data->mDemuxer.get(), "decoder factory wrapper",
                    "MediaFormatReader::DecoderFactory::Wrapper",
                    wrapper.get());
              }
            }
            data->mCrypto = data->mDemuxer->GetCrypto();
            data->mSeekable = data->mDemuxer->IsSeekable();
            data->mSeekableOnlyInBufferedRange =
                data->mDemuxer->IsSeekableOnlyInBufferedRanges();
            data->mShouldComputeStartTime =
                data->mDemuxer->ShouldComputeStartTime();
            data->mInitDone = true;
            return InitPromise::CreateAndResolve(NS_OK, __func__);
          },
          [](const MediaResult& aError) {
            return InitPromise::CreateAndReject(aError, __func__);
          });
}

RefPtr<MediaFormatReader::NotifyDataArrivedPromise>
MediaFormatReader::DemuxerProxy::NotifyDataArrived() {
  RefPtr<Data> data = mData;
  return InvokeAsync(mTaskQueue, __func__, [data]() {
    if (!data->mDemuxer) {
      // Was shutdown.
      return NotifyDataArrivedPromise::CreateAndReject(
          NS_ERROR_DOM_MEDIA_CANCELED, __func__);
    }
    data->mDemuxer->NotifyDataArrived();
    if (data->mAudioDemuxer) {
      data->mAudioDemuxer->UpdateBuffered();
    }
    if (data->mVideoDemuxer) {
      data->mVideoDemuxer->UpdateBuffered();
    }
    return NotifyDataArrivedPromise::CreateAndResolve(true, __func__);
  });
}

MediaFormatReader::MediaFormatReader(MediaFormatReaderInit& aInit,
                                     MediaDataDemuxer* aDemuxer)
    : mTaskQueue(
          TaskQueue::Create(GetMediaThreadPool(MediaThreadType::SUPERVISOR),
                            "MediaFormatReader::mTaskQueue",
                            /* aSupportsTailDispatch = */ true)),
      mAudio(this, MediaData::Type::AUDIO_DATA,
             StaticPrefs::media_audio_max_decode_error()),
      mVideo(this, MediaData::Type::VIDEO_DATA,
             StaticPrefs::media_video_max_decode_error()),
      mWorkingInfoChanged(false"MediaFormatReader::mWorkingInfoChanged"),
      mWatchManager(this, OwnerThread()),
      mIsWatchingWorkingInfo(false),
      mDemuxer(new DemuxerProxy(aDemuxer)),
      mDemuxerInitDone(false),
      mPendingNotifyDataArrived(false),
      mLastReportedNumDecodedFrames(0),
      mPreviousDecodedKeyframeTime_us(sNoPreviousDecodedKeyframe),
      mKnowsCompositor(aInit.mKnowsCompositor),
      mInitDone(false),
      mTrackDemuxersMayBlock(false),
      mSeekScheduled(false),
      mVideoFrameContainer(aInit.mVideoFrameContainer),
      mCrashHelper(aInit.mCrashHelper),
      mDecoderFactory(new DecoderFactory(this)),
      mShutdownPromisePool(new ShutdownPromisePool()),
      mBuffered(mTaskQueue, TimeIntervals(),
                "MediaFormatReader::mBuffered (Canonical)"),
      mFrameStats(aInit.mFrameStats),
      mMediaDecoderOwnerID(aInit.mMediaDecoderOwnerID),
      mTrackingId(std::move(aInit.mTrackingId)),
      mReadMetadataStartTime(Nothing()),
      mReadMetaDataTime(TimeDuration::Zero()),
      mTotalWaitingForVideoDataTime(TimeDuration::Zero()),
      mEncryptedCustomIdent(false) {
  MOZ_ASSERT(aDemuxer);
  MOZ_COUNT_CTOR(MediaFormatReader);
  DDLINKCHILD("audio decoder data""MediaFormatReader::DecoderDataWithPromise",
              &mAudio);
  DDLINKCHILD("video decoder data""MediaFormatReader::DecoderDataWithPromise",
              &mVideo);
  DDLINKCHILD("demuxer", aDemuxer);
  mOnTrackWaitingForKeyListener = OnTrackWaitingForKey().Connect(
      mTaskQueue, this, &MediaFormatReader::NotifyWaitingForKey);
}

MediaFormatReader::~MediaFormatReader() {
  MOZ_COUNT_DTOR(MediaFormatReader);
  MOZ_ASSERT(mShutdown);
}

RefPtr<ShutdownPromise> MediaFormatReader::Shutdown() {
  MOZ_ASSERT(OnTaskQueue());
  LOG("");

  mDemuxerInitRequest.DisconnectIfExists();
  mNotifyDataArrivedPromise.DisconnectIfExists();
  mMetadataPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
  mSeekPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
  mSkipRequest.DisconnectIfExists();
  mSetCDMPromise.RejectIfExists(
      MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
                  "MediaFormatReader is shutting down"),
      __func__);

  if (mIsWatchingWorkingInfo) {
    mWatchManager.Unwatch(mWorkingInfoChanged,
                          &MediaFormatReader::NotifyTrackInfoUpdated);
  }
  mWatchManager.Shutdown();

  if (mAudio.HasPromise()) {
    mAudio.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
  }
  if (mVideo.HasPromise()) {
    mVideo.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
  }

  if (HasAudio()) {
    mAudio.ResetDemuxer();
    mAudio.mTrackDemuxer->BreakCycles();
    {
      MutexAutoLock lock(mAudio.mMutex);
      mAudio.mTrackDemuxer = nullptr;
    }
    mAudio.ResetState();
    ShutdownDecoder(TrackInfo::kAudioTrack);
  }

  if (HasVideo()) {
    mVideo.ResetDemuxer();
    mVideo.mTrackDemuxer->BreakCycles();
    {
      MutexAutoLock lock(mVideo.mMutex);
      mVideo.mTrackDemuxer = nullptr;
    }
    mVideo.ResetState();
    ShutdownDecoder(TrackInfo::kVideoTrack);
  }

  mShutdownPromisePool->Track(mDemuxer->Shutdown());
  mDemuxer = nullptr;

  mOnTrackWaitingForKeyListener.Disconnect();

  mShutdown = true;
  return mShutdownPromisePool->Shutdown()->Then(
      OwnerThread(), __func__, this, &MediaFormatReader::TearDownDecoders,
      &MediaFormatReader::TearDownDecoders);
}

void MediaFormatReader::ShutdownDecoder(TrackType aTrack) {
  LOGV("%s", TrackTypeToStr(aTrack));

  // Shut down the pending decoder if any.
  mDecoderFactory->ShutdownDecoder(aTrack);

  auto& decoder = GetDecoderData(aTrack);
  // Flush the decoder if necessary.
  decoder.Flush();

  // Shut down the decoder if any.
  decoder.ShutdownDecoder();
}

void MediaFormatReader::NotifyDecoderBenchmarkStore() {
  MOZ_ASSERT(OnTaskQueue());
  if (!StaticPrefs::media_mediacapabilities_from_database()) {
    return;
  }
  auto& decoder = GetDecoderData(TrackInfo::kVideoTrack);
  if (decoder.GetCurrentInfo() && decoder.GetCurrentInfo()->GetAsVideoInfo()) {
    VideoInfo info = *(decoder.GetCurrentInfo()->GetAsVideoInfo());
    info.SetFrameRate(static_cast<int32_t>(ceil(decoder.mMeanRate.Mean())));
    mOnStoreDecoderBenchmark.Notify(std::move(info));
  }
}

void MediaFormatReader::NotifyTrackInfoUpdated() {
  MOZ_ASSERT(OnTaskQueue());
  if (mWorkingInfoChanged) {
    mWorkingInfoChanged = false;

    VideoInfo videoInfo;
    AudioInfo audioInfo;
    {
      MutexAutoLock lock(mVideo.mMutex);
      if (HasVideo()) {
        videoInfo = *mVideo.GetWorkingInfo()->GetAsVideoInfo();
      }
    }
    {
      MutexAutoLock lock(mAudio.mMutex);
      if (HasAudio()) {
        audioInfo = *mAudio.GetWorkingInfo()->GetAsAudioInfo();
      }
    }

    mTrackInfoUpdatedEvent.Notify(videoInfo, audioInfo);
  }
}

RefPtr<ShutdownPromise> MediaFormatReader::TearDownDecoders() {
  if (mAudio.mTaskQueue) {
    mAudio.mTaskQueue->BeginShutdown();
    mAudio.mTaskQueue->AwaitShutdownAndIdle();
    mAudio.mTaskQueue = nullptr;
  }
  if (mVideo.mTaskQueue) {
    mVideo.mTaskQueue->BeginShutdown();
    mVideo.mTaskQueue->AwaitShutdownAndIdle();
    mVideo.mTaskQueue = nullptr;
  }

  mDecoderFactory = nullptr;
  mVideoFrameContainer = nullptr;

  ReleaseResources();
  mBuffered.DisconnectAll();
  return mTaskQueue->BeginShutdown();
}

nsresult MediaFormatReader::Init() {
  MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");

  mAudio.mTaskQueue =
      TaskQueue::Create(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
                        "MFR::mAudio::mTaskQueue");

  mVideo.mTaskQueue =
      TaskQueue::Create(GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
                        "MFR::mVideo::mTaskQueue");

  return NS_OK;
}

bool MediaFormatReader::ResolveSetCDMPromiseIfDone(TrackType aTrack) {
  // When a CDM proxy is set, MFR would shutdown the existing MediaDataDecoder
  // and would create new one for specific track in the next Update.
  MOZ_ASSERT(OnTaskQueue());

  if (mSetCDMPromise.IsEmpty()) {
    return true;
  }

  MOZ_ASSERT(mCDMProxy);
  if (mSetCDMForTracks.contains(aTrack)) {
    mSetCDMForTracks -= aTrack;
  }

  if (mSetCDMForTracks.isEmpty()) {
    LOGV("%s : Done ", __func__);
    mSetCDMPromise.Resolve(/* aResolveValue = */ true, __func__);
    if (HasAudio()) {
      ScheduleUpdate(TrackInfo::kAudioTrack);
    }
    if (HasVideo()) {
      ScheduleUpdate(TrackInfo::kVideoTrack);
    }
    return true;
  }
  LOGV("%s : %s track is ready.", __func__, TrackTypeToStr(aTrack));
  return false;
}

void MediaFormatReader::PrepareToSetCDMForTrack(TrackType aTrack) {
  MOZ_ASSERT(OnTaskQueue());
  LOGV("%s : %s", __func__, TrackTypeToStr(aTrack));

  mSetCDMForTracks += aTrack;
  if (mCDMProxy) {
    // An old cdm proxy exists, so detaching old cdm proxy by shutting down
    // MediaDataDecoder.
    ShutdownDecoder(aTrack);
  }
  ScheduleUpdate(aTrack);
}

bool MediaFormatReader::IsDecoderWaitingForCDM(TrackType aTrack) {
  MOZ_ASSERT(OnTaskQueue());
  return GetDecoderData(aTrack).IsEncrypted() &&
         mSetCDMForTracks.contains(aTrack) && !mCDMProxy;
}

RefPtr<SetCDMPromise> MediaFormatReader::SetCDMProxy(CDMProxy* aProxy) {
  MOZ_ASSERT(OnTaskQueue());
  LOGV("SetCDMProxy (%p)", aProxy);

  if (mShutdown) {
    return SetCDMPromise::CreateAndReject(
        MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
                    "MediaFormatReader is shutting down"),
        __func__);
  }

  mSetCDMPromise.RejectIfExists(
      MediaResult(NS_ERROR_DOM_INVALID_STATE_ERR,
                  "Another new CDM proxy is being set."),
      __func__);

  // Shutdown all decoders as switching CDM proxy indicates that it's
  // inappropriate for the existing decoders to continue decoding via the old
  // CDM proxy.
  if (HasAudio()) {
    PrepareToSetCDMForTrack(TrackInfo::kAudioTrack);
  }
  if (HasVideo()) {
    PrepareToSetCDMForTrack(TrackInfo::kVideoTrack);
  }

  mCDMProxy = aProxy;

  if (!mInitDone || mSetCDMForTracks.isEmpty() || !mCDMProxy) {
    // 1) MFR is not initialized yet or
    // 2) Demuxer is initialized without active audio and video or
    // 3) A null cdm proxy is set
    // the promise can be resolved directly.
    mSetCDMForTracks.clear();
    return SetCDMPromise::CreateAndResolve(/* aResolveValue = */ true,
                                           __func__);
  }

  RefPtr<SetCDMPromise> p = mSetCDMPromise.Ensure(__func__);
  return p;
}

bool MediaFormatReader::IsWaitingOnCDMResource() {
  MOZ_ASSERT(OnTaskQueue());
  return IsEncrypted() && !mCDMProxy;
}

RefPtr<MediaFormatReader::MetadataPromise>
MediaFormatReader::AsyncReadMetadata() {
  AUTO_PROFILER_LABEL("MediaFormatReader::AsyncReadMetadata", MEDIA_PLAYBACK);
  MOZ_ASSERT(OnTaskQueue());

  MOZ_DIAGNOSTIC_ASSERT(mMetadataPromise.IsEmpty());

  if (mInitDone) {
    // We are returning from dormant.
    MetadataHolder metadata;
    metadata.mInfo = MakeUnique<MediaInfo>(mInfo);
    return MetadataPromise::CreateAndResolve(std::move(metadata), __func__);
  }

  if (!mReadMetadataStartTime) {
    mReadMetadataStartTime = Some(TimeStamp::Now());
  }

  RefPtr<MetadataPromise> p = mMetadataPromise.Ensure(__func__);

  mDemuxer->Init()
      ->Then(OwnerThread(), __func__, this,
             &MediaFormatReader::OnDemuxerInitDone,
             &MediaFormatReader::OnDemuxerInitFailed)
      ->Track(mDemuxerInitRequest);
  return p;
}

void MediaFormatReader::OnDemuxerInitDone(const MediaResult& aResult) {
  AUTO_PROFILER_LABEL("MediaFormatReader::OnDemuxerInitDone", MEDIA_PLAYBACK);
  MOZ_ASSERT(OnTaskQueue());
  mDemuxerInitRequest.Complete();

  if (NS_FAILED(aResult) && StaticPrefs::media_playback_warnings_as_errors()) {
    mMetadataPromise.Reject(aResult, __func__);
    return;
  }

  mDemuxerInitDone = true;

  UniquePtr<MetadataTags> tags(MakeUnique<MetadataTags>());

  RefPtr<PDMFactory> platform;
  if (!IsWaitingOnCDMResource()) {
    platform = new PDMFactory();
  }

  // To decode, we need valid video and a place to put it.
  bool videoActive = !!mDemuxer->GetNumberTracks(TrackInfo::kVideoTrack) &&
                     GetImageContainer();

  if (videoActive) {
    // We currently only handle the first video track.
    MutexAutoLock lock(mVideo.mMutex);
    mVideo.mTrackDemuxer = mDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
    if (!mVideo.mTrackDemuxer) {
      LOG("No video track demuxer");
      mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
      return;
    }

    UniquePtr<TrackInfo> videoInfo = mVideo.mTrackDemuxer->GetInfo();
    videoActive = videoInfo && videoInfo->IsValid();
    if (videoActive) {
      if (platform &&
          platform->SupportsMimeType(videoInfo->mMimeType).isEmpty()) {
        // We have no decoder for this track. Error.
        LOG("No supported decoder for video track (%s)",
            videoInfo->mMimeType.get());
        if (!videoInfo->mMimeType.IsEmpty()) {
          mozilla::glean::media_playback::not_supported_video_per_mime_type
              .Get(videoInfo->mMimeType)
              .Add(1);
        }
        mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
        return;
      }
      mInfo.mVideo = *videoInfo->GetAsVideoInfo();
      mVideo.mWorkingInfo = MakeUnique<VideoInfo>(mInfo.mVideo);
      for (const MetadataTag& tag : videoInfo->mTags) {
        tags->InsertOrUpdate(tag.mKey, tag.mValue);
      }
      mWorkingInfoChanged = true;
      mVideo.mOriginalInfo = std::move(videoInfo);
      mTrackDemuxersMayBlock |= mVideo.mTrackDemuxer->GetSamplesMayBlock();
    } else {
      mVideo.mTrackDemuxer->BreakCycles();
      mVideo.mTrackDemuxer = nullptr;
    }
  }

  bool audioActive = !!mDemuxer->GetNumberTracks(TrackInfo::kAudioTrack);
  if (audioActive) {
    MutexAutoLock lock(mAudio.mMutex);
    mAudio.mTrackDemuxer = mDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
    if (!mAudio.mTrackDemuxer) {
      LOG("No audio track demuxer");
      mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
      return;
    }

    UniquePtr<TrackInfo> audioInfo = mAudio.mTrackDemuxer->GetInfo();
    // We actively ignore audio tracks that we know we can't play.
    audioActive = audioInfo && audioInfo->IsValid() &&
                  (!platform ||
                   !platform->SupportsMimeType(audioInfo->mMimeType).isEmpty());

    if (audioActive) {
      mInfo.mAudio = *audioInfo->GetAsAudioInfo();
      mAudio.mWorkingInfo = MakeUnique<AudioInfo>(mInfo.mAudio);
      for (const MetadataTag& tag : audioInfo->mTags) {
        tags->InsertOrUpdate(tag.mKey, tag.mValue);
      }
      mWorkingInfoChanged = true;
      mAudio.mOriginalInfo = std::move(audioInfo);
      mTrackDemuxersMayBlock |= mAudio.mTrackDemuxer->GetSamplesMayBlock();
    } else {
      mAudio.mTrackDemuxer->BreakCycles();
      mAudio.mTrackDemuxer = nullptr;
    }
  }

  UniquePtr<EncryptionInfo> crypto = mDemuxer->GetCrypto();
  if (crypto && crypto->IsEncrypted()) {
    // Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING.
    for (uint32_t i = 0; i < crypto->mInitDatas.Length(); i++) {
      mOnEncrypted.Notify(crypto->mInitDatas[i].mInitData,
                          crypto->mInitDatas[i].mType);
    }
    mInfo.mCrypto = *crypto;
  }

  auto videoDuration = HasVideo() ? mInfo.mVideo.mDuration : TimeUnit::Zero();
  auto audioDuration = HasAudio() ? mInfo.mAudio.mDuration : TimeUnit::Zero();

  // If the duration is 0 on both audio and video, it mMetadataDuration is to be
  // Nothing(). Duration will use buffered ranges.
  LOG("videoDuration=%" PRId64 ", audioDuration=%" PRId64,
      videoDuration.ToMicroseconds(), audioDuration.ToMicroseconds());
  if (videoDuration.IsPositive() || audioDuration.IsPositive()) {
    auto duration = std::max(videoDuration, audioDuration);
    LOG("Determine mMetadataDuration=%" PRId64, duration.ToMicroseconds());
    mInfo.mMetadataDuration = Some(duration);
  }

  mInfo.mMediaSeekable = mDemuxer->IsSeekable();
  mInfo.mMediaSeekableOnlyInBufferedRanges =
      mDemuxer->IsSeekableOnlyInBufferedRanges();

  if (!videoActive && !audioActive) {
    LOG("No active audio or video track");
    mMetadataPromise.Reject(NS_ERROR_DOM_MEDIA_METADATA_ERR, __func__);
    return;
  }

  mTags = std::move(tags);
  mInitDone = true;

  // Try to get the start time.
  // For MSE case, the start time of each track is assumed to be 0.
  // For others, we must demux the first sample to know the start time for each
  // track.
  if (!mDemuxer->ShouldComputeStartTime()) {
    mAudio.mFirstDemuxedSampleTime.emplace(TimeUnit::Zero());
    mVideo.mFirstDemuxedSampleTime.emplace(TimeUnit::Zero());
  } else {
    if (HasAudio()) {
      RequestDemuxSamples(TrackInfo::kAudioTrack);
    }

    if (HasVideo()) {
      RequestDemuxSamples(TrackInfo::kVideoTrack);
    }
  }

  if (aResult != NS_OK) {
    mOnDecodeWarning.Notify(aResult);
  }

  MaybeResolveMetadataPromise();
}

void MediaFormatReader::MaybeResolveMetadataPromise() {
  MOZ_ASSERT(OnTaskQueue());

  if ((HasAudio() && mAudio.mFirstDemuxedSampleTime.isNothing()) ||
      (HasVideo() && mVideo.mFirstDemuxedSampleTime.isNothing())) {
    return;
  }

  TimeUnit startTime =
      std::min(mAudio.mFirstDemuxedSampleTime.refOr(TimeUnit::FromInfinity()),
               mVideo.mFirstDemuxedSampleTime.refOr(TimeUnit::FromInfinity()));

  if (!startTime.IsInfinite()) {
    mInfo.mStartTime = startTime;  // mInfo.mStartTime is initialized to 0.
    LOG("Set start time=%s", mInfo.mStartTime.ToString().get());
  }

  MetadataHolder metadata;
  metadata.mInfo = MakeUnique<MediaInfo>(mInfo);
  metadata.mTags = mTags->Count() ? std::move(mTags) : nullptr;

  // We now have all the informations required to calculate the initial buffered
  // range.
  mHasStartTime = true;
  UpdateBuffered();

  mWatchManager.Watch(mWorkingInfoChanged,
                      &MediaFormatReader::NotifyTrackInfoUpdated);
  mIsWatchingWorkingInfo = true;

  if (mReadMetadataStartTime) {
    mReadMetaDataTime = TimeStamp::Now() - *mReadMetadataStartTime;
    mReadMetadataStartTime.reset();
  }

  mMetadataPromise.Resolve(std::move(metadata), __func__);
}

bool MediaFormatReader::IsEncrypted() const {
  return (HasAudio() && mAudio.GetCurrentInfo()->mCrypto.IsEncrypted()) ||
         (HasVideo() && mVideo.GetCurrentInfo()->mCrypto.IsEncrypted());
}

void MediaFormatReader::OnDemuxerInitFailed(const MediaResult& aError) {
  mDemuxerInitRequest.Complete();
  mMetadataPromise.Reject(aError, __func__);
}

void MediaFormatReader::ReadUpdatedMetadata(MediaInfo* aInfo) {
  // Called on the MDSM's TaskQueue.
  {
    MutexAutoLock lock(mVideo.mMutex);
    if (HasVideo()) {
      aInfo->mVideo = *mVideo.GetWorkingInfo()->GetAsVideoInfo();
    }
  }
  {
    MutexAutoLock lock(mAudio.mMutex);
    if (HasAudio()) {
      aInfo->mAudio = *mAudio.GetWorkingInfo()->GetAsAudioInfo();
    }
  }
}

MediaFormatReader::DecoderData& MediaFormatReader::GetDecoderData(
    TrackType aTrack) {
  MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack ||
             aTrack == TrackInfo::kVideoTrack);
  if (aTrack == TrackInfo::kAudioTrack) {
    return mAudio;
  }
  return mVideo;
}

Maybe<TimeUnit> MediaFormatReader::ShouldSkip(TimeUnit aTimeThreshold,
                                              bool aRequestNextVideoKeyFrame) {
  MOZ_ASSERT(OnTaskQueue());
  MOZ_ASSERT(HasVideo());

  if (!StaticPrefs::media_decoder_skip_to_next_key_frame_enabled()) {
    return Nothing();
  }

  // Ensure we have no pending seek going as skip-to-keyframe could return out
  // of date information.
  if (mVideo.HasInternalSeekPending()) {
    return Nothing();
  }

  TimeUnit nextKeyframe;
  nsresult rv = mVideo.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe);
  if (NS_FAILED(rv)) {
    // Only OggTrackDemuxer with video type gets into here.
    // We don't support skip-to-next-frame for this case.
    return Nothing();
  }

  const bool isNextKeyframeValid =
      nextKeyframe.ToMicroseconds() >= 0 && !nextKeyframe.IsInfinite();
  // If we request the next keyframe, only return times greater than
  // aTimeThreshold. Otherwise, data will be already behind the threshold and
  // will be eventually discarded somewhere in the media pipeline.
  if (aRequestNextVideoKeyFrame && isNextKeyframeValid &&
      nextKeyframe > aTimeThreshold) {
    return Some(nextKeyframe);
  }

  const bool isNextVideoBehindTheThreshold =
      (isNextKeyframeValid && nextKeyframe <= aTimeThreshold) ||
      GetInternalSeekTargetEndTime() < aTimeThreshold;
  return isNextVideoBehindTheThreshold ? Some(aTimeThreshold) : Nothing();
}

RefPtr<MediaFormatReader::VideoDataPromise> MediaFormatReader::RequestVideoData(
    const TimeUnit& aTimeThreshold, bool aRequestNextVideoKeyFrame) {
  MOZ_ASSERT(OnTaskQueue());
  MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise(), "No duplicate sample requests");
  // Requesting video can be done independently from audio, even during audio
  // seeking. But it shouldn't happen if we're doing video seek.
  if (!IsAudioOnlySeeking()) {
    MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty(),
                          "No sample requests allowed while seeking");
    MOZ_DIAGNOSTIC_ASSERT(!mVideo.mSeekRequest.Exists() ||
                          mVideo.mTimeThreshold.isSome());
    MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek");
  }
  LOGV("RequestVideoData(%" PRId64 "), requestNextKeyFrame=%d",
       aTimeThreshold.ToMicroseconds(), aRequestNextVideoKeyFrame);

  if (!HasVideo()) {
    LOG("called with no video track");
    return VideoDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                                             __func__);
  }

  if (IsSeeking()) {
    LOG("called mid-seek. Rejecting.");
    return VideoDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED,
                                             __func__);
  }

  if (mShutdown) {
    NS_WARNING("RequestVideoData on shutdown MediaFormatReader!");
    return VideoDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED,
                                             __func__);
  }

  if (Maybe<TimeUnit> target =
          ShouldSkip(aTimeThreshold, aRequestNextVideoKeyFrame)) {
    PROFILER_MARKER_UNTYPED("RequestVideoData SkipVideoDemuxToNextKeyFrame",
                            MEDIA_PLAYBACK);
    RefPtr<VideoDataPromise> p = mVideo.EnsurePromise(__func__);
    SkipVideoDemuxToNextKeyFrame(*target);
    return p;
  }

  RefPtr<VideoDataPromise> p = mVideo.EnsurePromise(__func__);
  ScheduleUpdate(TrackInfo::kVideoTrack);

  return p;
}

void MediaFormatReader::OnDemuxFailed(TrackType aTrack,
                                      const MediaResult& aError) {
  AUTO_PROFILER_LABEL("MediaFormatReader::OnDemuxFailed", MEDIA_PLAYBACK);
  MOZ_ASSERT(OnTaskQueue());
  LOG("Failed to demux %s, failure:%s",
      aTrack == TrackType::kVideoTrack ? "video" : "audio",
      aError.ErrorName().get());
  auto& decoder = GetDecoderData(aTrack);
  decoder.mDemuxRequest.Complete();
  switch (aError.Code()) {
    case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
      DDLOG(DDLogCategory::Log,
            aTrack == TrackType::kVideoTrack ? "video_demux_interruption"
                                             : "audio_demux_interruption",
            aError);
      if (!decoder.mWaitingForDataStartTime) {
        decoder.RequestDrain();
      }
      NotifyEndOfStream(aTrack);
      break;
    case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
      DDLOG(DDLogCategory::Log,
            aTrack == TrackType::kVideoTrack ? "video_demux_interruption"
                                             : "audio_demux_interruption",
            aError);
      if (!decoder.mWaitingForDataStartTime) {
        decoder.RequestDrain();
      } else {
        // mWaitingForDataStartTime was already set.  The decoder is being
        // primed after an internal seek.  A drain has already been performed.
        MOZ_ASSERT(decoder.mTimeThreshold.isSome() ||
                   decoder.mNumSamplesInput == decoder.mNumSamplesOutput);
      }
      NotifyWaitingForData(aTrack);
      break;
    case NS_ERROR_DOM_MEDIA_CANCELED:
      DDLOG(DDLogCategory::Log,
            aTrack == TrackType::kVideoTrack ? "video_demux_interruption"
                                             : "audio_demux_interruption",
            aError);
      if (decoder.HasPromise()) {
        decoder.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
      }
      break;
    default:
      DDLOG(DDLogCategory::Log,
            aTrack == TrackType::kVideoTrack ? "video_demux_error"
                                             : "audio_demux_error",
            aError);
      NotifyError(aTrack, aError);
      break;
  }
}

void MediaFormatReader::DoDemuxVideo() {
  AUTO_PROFILER_LABEL("MediaFormatReader::DoDemuxVideo", MEDIA_PLAYBACK);
  using SamplesPromise = MediaTrackDemuxer::SamplesPromise;

  DDLOG(DDLogCategory::Log, "video_demuxing", DDNoValue{});
  PerformanceRecorder<PlaybackStage> perfRecorder(
      MediaStage::RequestDemux,
      mVideo.GetCurrentInfo()->GetAsVideoInfo()->mImage.height);
  auto p = mVideo.mTrackDemuxer->GetSamples(1);

  RefPtr<MediaFormatReader> self = this;
  if (mVideo.mFirstDemuxedSampleTime.isNothing()) {
    p = p->Then(
        OwnerThread(), __func__,
        [self](RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples) {
          AUTO_PROFILER_LABEL("MediaFormatReader::DoDemuxVideo:Resolved",
                              MEDIA_PLAYBACK);
          DDLOGEX(self.get(), DDLogCategory::Log, "video_first_demuxed",
                  DDNoValue{});
          self->OnFirstDemuxCompleted(TrackInfo::kVideoTrack, aSamples);
          return SamplesPromise::CreateAndResolve(aSamples.forget(), __func__);
        },
        [self](const MediaResult& aError) {
          AUTO_PROFILER_LABEL("MediaFormatReader::DoDemuxVideo:Rejected",
                              MEDIA_PLAYBACK);
          DDLOGEX(self.get(), DDLogCategory::Log, "video_first_demuxing_error",
                  aError);
          self->OnFirstDemuxFailed(TrackInfo::kVideoTrack, aError);
          return SamplesPromise::CreateAndReject(aError, __func__);
        });
  }

  p->Then(
       OwnerThread(), __func__,
       [self, perfRecorder(std::move(perfRecorder))](
           const RefPtr<MediaTrackDemuxer::SamplesHolder>& aSamples) mutable {
         perfRecorder.Record();
         self->OnVideoDemuxCompleted(aSamples);
       },
       [self](const MediaResult& aError) { self->OnVideoDemuxFailed(aError); })
      ->Track(mVideo.mDemuxRequest);
}

void MediaFormatReader::OnVideoDemuxCompleted(
    const RefPtr<MediaTrackDemuxer::SamplesHolder>& aSamples) {
  AUTO_PROFILER_LABEL("MediaFormatReader::OnVideoDemuxCompleted",
                      MEDIA_PLAYBACK);
  LOGV("%zu video samples demuxed (sid:%d)", aSamples->GetSamples().Length(),
       aSamples->GetSamples()[0]->mTrackInfo
           ? aSamples->GetSamples()[0]->mTrackInfo->GetID()
           : 0);
  DDLOG(DDLogCategory::Log, "video_demuxed_samples",
        uint64_t(aSamples->GetSamples().Length()));
  mVideo.mDemuxRequest.Complete();
  MOZ_ASSERT(mVideo.mQueuedSamples.IsEmpty());
  mVideo.mQueuedSamples = aSamples->GetMovableSamples();
  ScheduleUpdate(TrackInfo::kVideoTrack);
}

RefPtr<MediaFormatReader::AudioDataPromise>
MediaFormatReader::RequestAudioData() {
  MOZ_ASSERT(OnTaskQueue());
  MOZ_DIAGNOSTIC_ASSERT(!mAudio.HasPromise(), "No duplicate sample requests");
  // Requesting audio can be done independently from video, even during video
  // seeking. But it shouldn't happen if we're doing audio seek.
  if (!IsVideoOnlySeeking()) {
    MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty(),
                          "No sample requests allowed while seeking");
    MOZ_DIAGNOSTIC_ASSERT(!mAudio.mSeekRequest.Exists() ||
                          mAudio.mTimeThreshold.isSome());
    MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek");
  }
  LOGV("");

  if (!HasAudio()) {
    LOG("called with no audio track");
    return AudioDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR,
                                             __func__);
  }

  if (IsSeeking()) {
    LOG("called mid-seek. Rejecting.");
    return AudioDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED,
                                             __func__);
  }

  if (mShutdown) {
    NS_WARNING("RequestAudioData on shutdown MediaFormatReader!");
    return AudioDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED,
                                             __func__);
  }

  RefPtr<AudioDataPromise> p = mAudio.EnsurePromise(__func__);
  ScheduleUpdate(TrackInfo::kAudioTrack);

  return p;
}

void MediaFormatReader::DoDemuxAudio() {
  AUTO_PROFILER_LABEL("MediaFormatReader::DoDemuxAudio", MEDIA_PLAYBACK);
  using SamplesPromise = MediaTrackDemuxer::SamplesPromise;

  DDLOG(DDLogCategory::Log, "audio_demuxing", DDNoValue{});
  PerformanceRecorder<PlaybackStage> perfRecorder(MediaStage::RequestDemux);
  auto p = mAudio.mTrackDemuxer->GetSamples(1);

  RefPtr<MediaFormatReader> self = this;
  if (mAudio.mFirstDemuxedSampleTime.isNothing()) {
    p = p->Then(
        OwnerThread(), __func__,
        [self](RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples) {
          AUTO_PROFILER_LABEL("MediaFormatReader::DoDemuxAudio:Resolved",
                              MEDIA_PLAYBACK);
          DDLOGEX(self.get(), DDLogCategory::Log, "audio_first_demuxed",
                  DDNoValue{});
          self->OnFirstDemuxCompleted(TrackInfo::kAudioTrack, aSamples);
          return SamplesPromise::CreateAndResolve(aSamples.forget(), __func__);
        },
        [self](const MediaResult& aError) {
          AUTO_PROFILER_LABEL("MediaFormatReader::DoDemuxAudio:Rejected",
                              MEDIA_PLAYBACK);
          DDLOGEX(self.get(), DDLogCategory::Log, "audio_first_demuxing_error",
                  aError);
          self->OnFirstDemuxFailed(TrackInfo::kAudioTrack, aError);
          return SamplesPromise::CreateAndReject(aError, __func__);
        });
  }

  p->Then(
       OwnerThread(), __func__,
       [self, perfRecorder(std::move(perfRecorder))](
           const RefPtr<MediaTrackDemuxer::SamplesHolder>& aSamples) mutable {
         perfRecorder.Record();
         self->OnAudioDemuxCompleted(aSamples);
       },
       [self](const MediaResult& aError) { self->OnAudioDemuxFailed(aError); })
      ->Track(mAudio.mDemuxRequest);
}

void MediaFormatReader::OnAudioDemuxCompleted(
    const RefPtr<MediaTrackDemuxer::SamplesHolder>& aSamples) {
  LOGV("%zu audio samples demuxed (sid:%d)", aSamples->GetSamples().Length(),
       aSamples->GetSamples()[0]->mTrackInfo
           ? aSamples->GetSamples()[0]->mTrackInfo->GetID()
           : 0);
  DDLOG(DDLogCategory::Log, "audio_demuxed_samples",
        uint64_t(aSamples->GetSamples().Length()));
  mAudio.mDemuxRequest.Complete();
  MOZ_ASSERT(mAudio.mQueuedSamples.IsEmpty());
  mAudio.mQueuedSamples = aSamples->GetMovableSamples();
  ScheduleUpdate(TrackInfo::kAudioTrack);
}

void MediaFormatReader::NotifyNewOutput(
    TrackType aTrack, MediaDataDecoder::DecodedData&& aResults) {
  AUTO_PROFILER_LABEL("MediaFormatReader::NotifyNewOutput", MEDIA_PLAYBACK);
  MOZ_ASSERT(OnTaskQueue());
  auto& decoder = GetDecoderData(aTrack);
  if (aResults.IsEmpty()) {
    DDLOG(DDLogCategory::Log,
          aTrack == TrackInfo::kAudioTrack ? "decoded_audio" : "decoded_video",
          "no output samples");
  } else {
    for (auto&& sample : aResults) {
      if (DecoderDoctorLogger::IsDDLoggingEnabled()) {
        switch (sample->mType) {
          case MediaData::Type::AUDIO_DATA:
            DDLOGPR(DDLogCategory::Log,
                    aTrack == TrackInfo::kAudioTrack ? "decoded_audio"
                                                     : "decoded_got_audio!?",
                    "{\"type\":\"AudioData\", \"offset\":%" PRIi64
                    ", \"time_us\":%" PRIi64 ", \"timecode_us\":%" PRIi64
                    ", \"duration_us\":%" PRIi64 ", \"frames\":%" PRIu32
                    ", \"channels\":%" PRIu32 ", \"rate\":%" PRIu32
                    ", \"bytes\":%zu}",
                    sample->mOffset, sample->mTime.ToMicroseconds(),
                    sample->mTimecode.ToMicroseconds(),
                    sample->mDuration.ToMicroseconds(),
                    sample->As<AudioData>()->Frames(),
                    sample->As<AudioData>()->mChannels,
                    sample->As<AudioData>()->mRate,
                    sample->As<AudioData>()->Data().Length());
            break;
          case MediaData::Type::VIDEO_DATA:
            DDLOGPR(DDLogCategory::Log,
                    aTrack == TrackInfo::kVideoTrack ? "decoded_video"
                                                     : "decoded_got_video!?",
                    "{\"type\":\"VideoData\", \"offset\":%" PRIi64
                    ", \"time_us\":%" PRIi64 ", \"timecode_us\":%" PRIi64
                    ", \"duration_us\":%" PRIi64
                    ", \"kf\":%s, \"size\":[%" PRIi32 ",%" PRIi32 "]}",
                    sample->mOffset, sample->mTime.ToMicroseconds(),
                    sample->mTimecode.ToMicroseconds(),
                    sample->mDuration.ToMicroseconds(),
                    sample->mKeyframe ? "true" : "false",
                    sample->As<VideoData>()->mDisplay.width,
                    sample->As<VideoData>()->mDisplay.height);
            break;
          case MediaData::Type::RAW_DATA:
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=89 H=96 G=92

¤ Dauer der Verarbeitung: 0.19 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.