Bug 1319992: P1. Run demuxing operations on its own task queue. r?jwwang draft
authorJean-Yves Avenard <jyavenard@mozilla.com>
Mon, 28 Nov 2016 21:08:01 +1100
changeset 449464 df75909607697aa4e9bf0eb38cb149e06e404fed
parent 449463 ccc5bedd778cc0396a9d9ca03bf2393f40f2ca41
child 449465 fd2c3d6090c233e41925ebf9cba4e0c0df72839a
push id38568
push userbmo:jyavenard@mozilla.com
push dateWed, 14 Dec 2016 06:36:13 +0000
reviewersjwwang
bugs1319992
milestone53.0a1
Bug 1319992: P1. Run demuxing operations on its own task queue. r?jwwang We runs all demuxing operations on a dedicated task queue. MediaDataDemuxer's members using a synchronous API are handled via thread-safe copy that are updated along the operations. The buffered range calculation is now handled separately and the entire operation is made asynchronous. MozReview-Commit-ID: Gd4DCC8Ix6n
dom/media/MediaDecoderReader.h
dom/media/MediaFormatReader.cpp
dom/media/MediaFormatReader.h
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -189,21 +189,20 @@ public:
 
   // Returns the number of bytes of memory allocated by structures/frames in
   // the audio queue.
   size_t SizeOfAudioQueueInBytes() const;
 
   virtual size_t SizeOfVideoQueueInFrames();
   virtual size_t SizeOfAudioQueueInFrames();
 
-  void NotifyDataArrived()
+  virtual void NotifyDataArrived()
   {
     MOZ_ASSERT(OnTaskQueue());
     NS_ENSURE_TRUE_VOID(!mShutdown);
-    NotifyDataArrivedInternal();
     UpdateBuffered();
   }
 
   virtual MediaQueue<AudioData>& AudioQueue() { return mAudioQueue; }
   virtual MediaQueue<VideoData>& VideoQueue() { return mVideoQueue; }
 
   AbstractCanonical<media::TimeIntervals>* CanonicalBuffered()
   {
@@ -246,37 +245,16 @@ public:
   // Switch the video decoder to BlankDecoderModule. It might takes effective
   // since a few samples later depends on how much demuxed samples are already
   // queued in the original video decoder.
   virtual void SetVideoBlankDecode(bool aIsBlankDecode) {}
 
 protected:
   virtual ~MediaDecoderReader();
 
-  // Recomputes mBuffered.
-  virtual void UpdateBuffered();
-
-  // Populates aBuffered with the time ranges which are buffered. This may only
-  // be called on the decode task queue, and should only be used internally by
-  // UpdateBuffered - mBuffered (or mirrors of it) should be used for everything
-  // else.
-  //
-  // This base implementation in MediaDecoderReader estimates the time ranges
-  // buffered by interpolating the cached byte ranges with the duration
-  // of the media. Reader subclasses should override this method if they
-  // can quickly calculate the buffered ranges more accurately.
-  //
-  // The primary advantage of this implementation in the reader base class
-  // is that it's a fast approximation, which does not perform any I/O.
-  //
-  // The OggReader relies on this base implementation not performing I/O,
-  // since in FirefoxOS we can't do I/O on the main thread, where this is
-  // called.
-  virtual media::TimeIntervals GetBuffered();
-
   RefPtr<MediaDataPromise> DecodeToFirstVideoData();
 
   // Queue of audio frames. This queue is threadsafe, and is accessed from
   // the audio, decoder, state machine, and main threads.
   MediaQueue<AudioData> mAudioQueue;
 
   // Queue of video frames. This queue is threadsafe, and is accessed from
   // the decoder, state machine, and main threads.
@@ -339,18 +317,16 @@ private:
   // Returns NS_OK on success, or NS_ERROR_FAILURE on failure.
   virtual nsresult ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
   {
     MOZ_CRASH();
   }
 
   virtual void VisibilityChanged();
 
-  virtual void NotifyDataArrivedInternal() {}
-
   // Overrides of this function should decodes an unspecified amount of
   // audio data, enqueuing the audio data in mAudioQueue. Returns true
   // when there's more audio to decode, false if the audio is finished,
   // end of file has been reached, or an un-recoverable read error has
   // occured. This function blocks until the decode is complete.
   virtual bool DecodeAudioData()
   {
     return false;
@@ -361,16 +337,37 @@ private:
   // (unless they're not keyframes and aKeyframeSkip is true), but will
   // not be added to the queue. This function blocks until the decode
   // is complete.
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold)
   {
     return false;
   }
 
+  // Recomputes mBuffered.
+  void UpdateBuffered();
+
+  // Populates aBuffered with the time ranges which are buffered. This may only
+  // be called on the decode task queue, and should only be used internally by
+  // UpdateBuffered - mBuffered (or mirrors of it) should be used for everything
+  // else.
+  //
+  // This base implementation in MediaDecoderReader estimates the time ranges
+  // buffered by interpolating the cached byte ranges with the duration
+  // of the media. Reader subclasses should override this method if they
+  // can quickly calculate the buffered ranges more accurately.
+  //
+  // The primary advantage of this implementation in the reader base class
+  // is that it's a fast approximation, which does not perform any I/O.
+  //
+  // The OggReader relies on this base implementation not performing I/O,
+  // since in FirefoxOS we can't do I/O on the main thread, where this is
+  // called.
+  media::TimeIntervals GetBuffered();
+
   // Promises used only for the base-class (sync->async adapter) implementation
   // of Request{Audio,Video}Data.
   MozPromiseHolder<MediaDataPromise> mBaseAudioPromise;
   MozPromiseHolder<MediaDataPromise> mBaseVideoPromise;
 
   MediaEventListener mDataArrivedListener;
 };
 
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -1,32 +1,35 @@
 /* -*- 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 "mozilla/CDMProxy.h"
-#include "mozilla/ClearOnShutdown.h"
-#include "mozilla/dom/HTMLMediaElement.h"
-#include "mozilla/Preferences.h"
-#include "mozilla/Telemetry.h"
-#include "nsContentUtils.h"
-#include "nsPrintfCString.h"
-#include "nsSize.h"
+#include "AutoTaskQueue.h"
 #include "Layers.h"
 #include "MediaData.h"
 #include "MediaInfo.h"
 #include "MediaFormatReader.h"
 #include "MediaPrefs.h"
 #include "MediaResource.h"
-#include "mozilla/SharedThreadPool.h"
 #include "VideoUtils.h"
 #include "VideoFrameContainer.h"
+#include "mozilla/dom/HTMLMediaElement.h"
 #include "mozilla/layers/ShadowLayers.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/SharedThreadPool.h"
+#include "mozilla/SyncRunnable.h"
+#include "nsContentUtils.h"
+#include "nsPrintfCString.h"
+#include "nsSize.h"
 
 #include <algorithm>
 #include <queue>
 
 using namespace mozilla::media;
 
 using mozilla::layers::Image;
 using mozilla::layers::LayerManager;
@@ -422,16 +425,344 @@ MediaFormatReader::DecoderFactory::DoIni
       data.mInitPromise.Complete();
       data.mStage = Stage::None;
       data.mDecoder->Shutdown();
       data.mDecoder = nullptr;
       mOwner->NotifyError(aTrack, aError);
     }));
 }
 
+// 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;
+
+public:
+  explicit DemuxerProxy(MediaDataDemuxer* aDemuxer)
+    : mTaskQueue(new AutoTaskQueue(
+                   GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER)))
+    , mData(new Data(aDemuxer))
+  { }
+
+  ~DemuxerProxy()
+  {
+    RefPtr<Data> data = mData.forget();
+    mTaskQueue->Dispatch(
+      // 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.
+      NS_NewRunnableFunction([data]() { data->mDemuxer = nullptr; }));
+  }
+
+  RefPtr<MediaDataDemuxer::InitPromise> Init();
+
+  MediaTrackDemuxer* 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)
+  {
+    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()
+  {
+    MOZ_RELEASE_ASSERT(mData && mData->mInitDone);
+
+    return mData->mSeekable;
+  }
+
+  bool IsSeekableOnlyInBufferedRanges()
+  {
+    MOZ_RELEASE_ASSERT(mData && mData->mInitDone);
+
+    return mData->mSeekableOnlyInBufferedRange;
+  }
+
+  UniquePtr<EncryptionInfo> GetCrypto()
+  {
+    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<AutoTaskQueue> mTaskQueue;
+  class Wrapper;
+  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 monitor.
+    bool mNumVideoTrack = 0;
+    RefPtr<MediaTrackDemuxer> mVideoDemuxer;
+    bool mNumAudioTrack = 0;
+    RefPtr<MediaTrackDemuxer> mAudioDemuxer;
+    bool mSeekable = false;
+    bool mSeekableOnlyInBufferedRange = false;
+    bool mShouldComputeStartTime = true;
+    UniquePtr<EncryptionInfo> mCrypto;
+  private:
+    ~Data() { }
+  };
+  RefPtr<Data> mData;
+};
+
+class MediaFormatReader::DemuxerProxy::Wrapper : public MediaTrackDemuxer
+{
+public:
+  Wrapper(MediaTrackDemuxer* aTrackDemuxer, AutoTaskQueue* aTaskQueue)
+    : mMutex("TrackDemuxer Mutex")
+    , mTaskQueue(aTaskQueue)
+    , mGetSamplesMayBlock(aTrackDemuxer->GetSamplesMayBlock())
+    , mInfo(aTrackDemuxer->GetInfo())
+    , mTrackDemuxer(aTrackDemuxer)
+  { }
+
+  UniquePtr<TrackInfo> GetInfo() const override
+  {
+    if (!mInfo) {
+      return nullptr;
+    }
+    return mInfo->Clone();
+  }
+
+  RefPtr<SeekPromise> Seek(const media::TimeUnit& aTime) override
+  {
+    RefPtr<Wrapper> self = this;
+    return InvokeAsync(
+             mTaskQueue, __func__,
+             [self, aTime]() { return self->mTrackDemuxer->Seek(aTime); })
+      ->ThenPromise(mTaskQueue, __func__,
+                    [self]() { self->UpdateRandomAccessPoint(); },
+                    [self]() { self->UpdateRandomAccessPoint(); });
+  }
+
+  RefPtr<SamplesPromise>
+  GetSamples(int32_t aNumSamples) override
+  {
+    RefPtr<Wrapper> self = this;
+    return InvokeAsync(mTaskQueue, __func__,
+                      [self, aNumSamples]() {
+                        return self->mTrackDemuxer->GetSamples(aNumSamples);
+                      })
+      ->ThenPromise(mTaskQueue, __func__,
+                    [self]() { self->UpdateRandomAccessPoint(); },
+                    [self]() { self->UpdateRandomAccessPoint(); });
+  }
+
+  bool
+  GetSamplesMayBlock() const override
+  {
+    return mGetSamplesMayBlock;
+  }
+
+  void Reset() override
+  {
+    RefPtr<Wrapper> self = this;
+    mTaskQueue->Dispatch(NS_NewRunnableFunction([self]() {
+      self->mTrackDemuxer->Reset();
+    }));
+  }
+
+  nsresult GetNextRandomAccessPoint(TimeUnit* aTime) override
+  {
+    MutexAutoLock lock(mMutex);
+    if (NS_SUCCEEDED(mNextRandomAccessPointResult)) {
+      *aTime = mNextRandomAccessPoint;
+    }
+    return mNextRandomAccessPointResult;
+  }
+
+  RefPtr<SkipAccessPointPromise>
+  SkipToNextRandomAccessPoint(const media::TimeUnit& aTimeThreshold) override
+  {
+    RefPtr<Wrapper> self = this;
+    return InvokeAsync(
+             mTaskQueue, __func__,
+             [self, aTimeThreshold]() {
+               return self->mTrackDemuxer->SkipToNextRandomAccessPoint(
+                 aTimeThreshold);
+             })
+      ->ThenPromise(mTaskQueue, __func__,
+                    [self]() { self->UpdateRandomAccessPoint(); },
+                    [self]() { self->UpdateRandomAccessPoint(); });
+  }
+
+  TimeIntervals
+  GetBuffered() override
+  {
+    MutexAutoLock lock(mMutex);
+    return mBuffered;
+  }
+
+  void BreakCycles() override
+  {
+    RefPtr<Wrapper> self = this;
+    nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction([self]() {
+      self->mTrackDemuxer->BreakCycles();
+      self->mTrackDemuxer = nullptr;
+    });
+    SyncRunnable::DispatchToThread(mTaskQueue, runnable);
+  }
+
+private:
+  Mutex mMutex;
+  const RefPtr<AutoTaskQueue> mTaskQueue;
+  const bool mGetSamplesMayBlock;
+  const UniquePtr<TrackInfo> mInfo;
+  // mTrackDemuxer is only ever accessed on the parent's task queue.
+  RefPtr<MediaTrackDemuxer> mTrackDemuxer;
+  // All following members are protected by mMutex
+  nsresult mNextRandomAccessPointResult = NS_OK;
+  TimeUnit mNextRandomAccessPoint;
+  void UpdateRandomAccessPoint()
+  {
+    MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+    if (!mTrackDemuxer) {
+      // Detached.
+      return;
+    }
+    MutexAutoLock lock(mMutex);
+    mNextRandomAccessPointResult =
+      mTrackDemuxer->GetNextRandomAccessPoint(&mNextRandomAccessPoint);
+  }
+  TimeIntervals mBuffered;
+  friend class DemuxerProxy;
+  void UpdateBuffered()
+  {
+    MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
+    if (!mTrackDemuxer) {
+      // Detached.
+      return;
+    }
+    MutexAutoLock lock(mMutex);
+    mBuffered = mTrackDemuxer->GetBuffered();
+  }
+};
+
+RefPtr<MediaDataDemuxer::InitPromise>
+MediaFormatReader::DemuxerProxy::Init()
+{
+  RefPtr<Data> data = mData;
+  RefPtr<AutoTaskQueue> taskQueue = mTaskQueue;
+  return InvokeAsync(mTaskQueue, __func__,
+                     [data, taskQueue]() {
+                       if (!data->mDemuxer) {
+                         return MediaDataDemuxer::InitPromise::CreateAndReject(
+                           NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+                       }
+                       return data->mDemuxer->Init();
+                     })
+    ->ThenPromise(
+      taskQueue, __func__,
+      [data, taskQueue]() {
+        if (!data->mDemuxer) { // Was shutdown.
+          return;
+        }
+        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;
+          }
+        }
+        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;
+          }
+        }
+        data->mCrypto = data->mDemuxer->GetCrypto();
+        data->mSeekable = data->mDemuxer->IsSeekable();
+        data->mSeekableOnlyInBufferedRange =
+          data->mDemuxer->IsSeekableOnlyInBufferedRanges();
+        data->mShouldComputeStartTime =
+          data->mDemuxer->ShouldComputeStartTime();
+        data->mInitDone = true;
+      },
+      []() {});
+}
+
+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->mVideoDemuxer) {
+      static_cast<Wrapper*>(data->mVideoDemuxer.get())->UpdateBuffered();
+    }
+    if (data->mAudioDemuxer) {
+      static_cast<Wrapper*>(data->mAudioDemuxer.get())->UpdateBuffered();
+    }
+    return NotifyDataArrivedPromise::CreateAndResolve(true, __func__);
+  });
+}
+
 static const char*
 TrackTypeToStr(TrackInfo::TrackType aTrack)
 {
   MOZ_ASSERT(aTrack == TrackInfo::kAudioTrack ||
              aTrack == TrackInfo::kVideoTrack ||
              aTrack == TrackInfo::kTextTrack);
   switch (aTrack) {
   case TrackInfo::kAudioTrack:
@@ -448,17 +779,17 @@ TrackTypeToStr(TrackInfo::TrackType aTra
 MediaFormatReader::MediaFormatReader(AbstractMediaDecoder* aDecoder,
                                      MediaDataDemuxer* aDemuxer,
                                      VideoFrameContainer* aVideoFrameContainer)
   : MediaDecoderReader(aDecoder)
   , mAudio(this, MediaData::AUDIO_DATA,
            Preferences::GetUint("media.audio-max-decode-error", 3))
   , mVideo(this, MediaData::VIDEO_DATA,
            Preferences::GetUint("media.video-max-decode-error", 2))
-  , mDemuxer(aDemuxer)
+  , mDemuxer(new DemuxerProxy(aDemuxer))
   , mDemuxerInitDone(false)
   , mLastReportedNumDecodedFrames(0)
   , mPreviousDecodedKeyframeTime_us(sNoPreviousDecodedKeyframe)
   , mInitDone(false)
   , mTrackDemuxersMayBlock(false)
   , mSeekScheduled(false)
   , mVideoFrameContainer(aVideoFrameContainer)
   , mDecoderFactory(new DecoderFactory(this))
@@ -684,17 +1015,16 @@ MediaFormatReader::OnDemuxerInitDone(nsr
         return;
       }
       mInfo.mVideo = *videoInfo->GetAsVideoInfo();
       for (const MetadataTag& tag : videoInfo->mTags) {
         tags->Put(tag.mKey, tag.mValue);
       }
       mVideo.mOriginalInfo = Move(videoInfo);
       mVideo.mCallback = new DecoderCallback(this, TrackInfo::kVideoTrack);
-      mVideo.mTimeRanges = mVideo.mTrackDemuxer->GetBuffered();
       mTrackDemuxersMayBlock |= mVideo.mTrackDemuxer->GetSamplesMayBlock();
     } else {
       mVideo.mTrackDemuxer->BreakCycles();
       mVideo.mTrackDemuxer = nullptr;
     }
   }
 
   bool audioActive = !!mDemuxer->GetNumberTracks(TrackInfo::kAudioTrack);
@@ -713,17 +1043,16 @@ MediaFormatReader::OnDemuxerInitDone(nsr
 
     if (audioActive) {
       mInfo.mAudio = *audioInfo->GetAsAudioInfo();
       for (const MetadataTag& tag : audioInfo->mTags) {
         tags->Put(tag.mKey, tag.mValue);
       }
       mAudio.mOriginalInfo = Move(audioInfo);
       mAudio.mCallback = new DecoderCallback(this, TrackInfo::kAudioTrack);
-      mAudio.mTimeRanges = mAudio.mTrackDemuxer->GetBuffered();
       mTrackDemuxersMayBlock |= mAudio.mTrackDemuxer->GetSamplesMayBlock();
     } else {
       mAudio.mTrackDemuxer->BreakCycles();
       mAudio.mTrackDemuxer = nullptr;
     }
   }
 
   UniquePtr<EncryptionInfo> crypto = mDemuxer->GetCrypto();
@@ -789,22 +1118,25 @@ MediaFormatReader::MaybeResolveMetadataP
   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.
   }
 
+  RefPtr<MetadataHolder> metadata = new MetadataHolder();
+  metadata->mInfo = mInfo;
+  metadata->mTags = mTags->Count() ? mTags.release() : nullptr;
+
+  // We now have all the informations required to calculate the initial buffered
+  // range.
   mHasStartTime = true;
   UpdateBuffered();
 
-  RefPtr<MetadataHolder> metadata = new MetadataHolder();
-  metadata->mInfo = mInfo;
-  metadata->mTags = mTags->Count() ? mTags.release() : nullptr;
   mMetadataPromise.Resolve(metadata, __func__);
 }
 
 bool
 MediaFormatReader::IsEncrypted() const
 {
   return (HasAudio() && mInfo.mAudio.mCrypto.mValid) ||
          (HasVideo() && mInfo.mVideo.mCrypto.mValid);
@@ -1150,19 +1482,16 @@ MediaFormatReader::UpdateReceivedNewData
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
 
   if (!decoder.mReceivedNewData) {
     return false;
   }
 
-  // Update our cached TimeRange.
-  decoder.mTimeRanges = decoder.mTrackDemuxer->GetBuffered();
-
   // We do not want to clear mWaitingForData while there are pending
   // demuxing or seeking operations that could affect the value of this flag.
   // This is in order to ensure that we will retry once they complete as we may
   // now have new data that could potentially allow those operations to
   // successfully complete if tried again.
   if (decoder.mSeekRequest.Exists()) {
     // Nothing more to do until this operation complete.
     return true;
@@ -1180,26 +1509,16 @@ MediaFormatReader::UpdateReceivedNewData
   }
 
   if (decoder.HasPendingDrain()) {
     // We do not want to clear mWaitingForData or mDemuxEOS while
     // a drain is in progress in order to properly complete the operation.
     return false;
   }
 
-  bool hasLastEnd;
-  media::TimeUnit lastEnd = decoder.mTimeRanges.GetEnd(&hasLastEnd);
-  if (hasLastEnd) {
-    if (decoder.mLastTimeRangesEnd && decoder.mLastTimeRangesEnd.ref() < lastEnd) {
-      // New data was added after our previous end, we can clear the EOS flag.
-      decoder.mDemuxEOS = false;
-    }
-    decoder.mLastTimeRangesEnd = Some(lastEnd);
-  }
-
   decoder.mReceivedNewData = false;
   if (decoder.mTimeThreshold) {
     decoder.mTimeThreshold.ref().mWaiting = false;
   }
   decoder.mWaitingForData = false;
 
   if (decoder.HasFatalError()) {
     return false;
@@ -2181,101 +2500,129 @@ MediaFormatReader::OnAudioSeekCompleted(
 }
 
 void
 MediaFormatReader::OnAudioSeekFailed(const MediaResult& aError)
 {
   OnSeekFailed(TrackType::kAudioTrack, aError);
 }
 
-media::TimeIntervals
-MediaFormatReader::GetBuffered()
-{
-  MOZ_ASSERT(OnTaskQueue());
-  media::TimeIntervals videoti;
-  media::TimeIntervals audioti;
-  media::TimeIntervals intervals;
-
-  if (!mInitDone || !mHasStartTime) {
-    return intervals;
-  }
-
-  // Ensure we have up to date buffered time range.
-  if (HasVideo()) {
-    UpdateReceivedNewData(TrackType::kVideoTrack);
-  }
-  if (HasAudio()) {
-    UpdateReceivedNewData(TrackType::kAudioTrack);
-  }
-  if (HasVideo()) {
-    videoti = mVideo.mTimeRanges;
-  }
-  if (HasAudio()) {
-    audioti = mAudio.mTimeRanges;
-  }
-  if (HasAudio() && HasVideo()) {
-    intervals = media::Intersection(Move(videoti), Move(audioti));
-  } else if (HasAudio()) {
-    intervals = Move(audioti);
-  } else if (HasVideo()) {
-    intervals = Move(videoti);
-  }
-
-  if (!intervals.Length() ||
-      intervals.GetStart() == media::TimeUnit::FromMicroseconds(0)) {
-    // IntervalSet already starts at 0 or is empty, nothing to shift.
-    return intervals;
-  }
-  return intervals.Shift(media::TimeUnit() - mInfo.mStartTime);
-}
-
 void MediaFormatReader::ReleaseResources()
 {
   mVideo.ShutdownDecoder();
   mAudio.ShutdownDecoder();
 }
 
 bool
 MediaFormatReader::VideoIsHardwareAccelerated() const
 {
   return mVideo.mIsHardwareAccelerated;
 }
 
 void
-MediaFormatReader::NotifyDemuxer()
+MediaFormatReader::NotifyTrackDemuxers()
 {
   MOZ_ASSERT(OnTaskQueue());
 
-  if (mShutdown || !mDemuxer ||
-      (!mDemuxerInitDone && !mDemuxerInitRequest.Exists())) {
-    return;
-  }
-
   LOGV("");
 
-  mDemuxer->NotifyDataArrived();
-
   if (!mInitDone) {
     return;
   }
+
   if (HasVideo()) {
     mVideo.mReceivedNewData = true;
     ScheduleUpdate(TrackType::kVideoTrack);
   }
   if (HasAudio()) {
     mAudio.mReceivedNewData = true;
     ScheduleUpdate(TrackType::kAudioTrack);
   }
 }
 
 void
-MediaFormatReader::NotifyDataArrivedInternal()
+MediaFormatReader::NotifyDataArrived()
+{
+  MOZ_ASSERT(OnTaskQueue());
+
+  if (mShutdown || !mDemuxer ||
+      (!mDemuxerInitDone && !mDemuxerInitRequest.Exists())) {
+    return;
+  }
+
+  RefPtr<MediaFormatReader> self = this;
+  mDemuxer->NotifyDataArrived()->Then(
+    OwnerThread(), __func__,
+    [self]() {
+      self->UpdateBuffered();
+      self->NotifyTrackDemuxers();
+    },
+    []() {});
+}
+
+void
+MediaFormatReader::UpdateBuffered()
 {
   MOZ_ASSERT(OnTaskQueue());
-  NotifyDemuxer();
+
+  if (mShutdown) {
+    return;
+  }
+
+  if (!mInitDone || !mHasStartTime) {
+    mBuffered = TimeIntervals();
+    return;
+  }
+
+  if (HasVideo()) {
+    mVideo.mTimeRanges = mVideo.mTrackDemuxer->GetBuffered();
+    bool hasLastEnd;
+    media::TimeUnit lastEnd = mVideo.mTimeRanges.GetEnd(&hasLastEnd);
+    if (hasLastEnd) {
+      if (mVideo.mLastTimeRangesEnd
+          && mVideo.mLastTimeRangesEnd.ref() < lastEnd) {
+        // New data was added after our previous end, we can clear the EOS flag.
+        mVideo.mDemuxEOS = false;
+        ScheduleUpdate(TrackInfo::kVideoTrack);
+      }
+      mVideo.mLastTimeRangesEnd = Some(lastEnd);
+    }
+  }
+  if (HasAudio()) {
+    mAudio.mTimeRanges = mAudio.mTrackDemuxer->GetBuffered();
+    bool hasLastEnd;
+    media::TimeUnit lastEnd = mAudio.mTimeRanges.GetEnd(&hasLastEnd);
+    if (hasLastEnd) {
+      if (mAudio.mLastTimeRangesEnd
+          && mAudio.mLastTimeRangesEnd.ref() < lastEnd) {
+        // New data was added after our previous end, we can clear the EOS flag.
+        mAudio.mDemuxEOS = false;
+        ScheduleUpdate(TrackInfo::kAudioTrack);
+      }
+      mAudio.mLastTimeRangesEnd = Some(lastEnd);
+    }
+  }
+
+  media::TimeIntervals intervals;
+  if (HasAudio() && HasVideo()) {
+    intervals = media::Intersection(mVideo.mTimeRanges, mAudio.mTimeRanges);
+  } else if (HasAudio()) {
+    intervals = mAudio.mTimeRanges;
+  } else if (HasVideo()) {
+    intervals = mVideo.mTimeRanges;
+  }
+
+  if (!intervals.Length() ||
+      intervals.GetStart() == media::TimeUnit::FromMicroseconds(0)) {
+    // IntervalSet already starts at 0 or is empty, nothing to shift.
+    mBuffered = intervals;
+  } else {
+    mBuffered =
+      intervals.Shift(media::TimeUnit() - mInfo.mStartTime);
+  }
 }
 
 bool
 MediaFormatReader::ForceZeroStartTime() const
 {
   return !mDemuxer->ShouldComputeStartTime();
 }
 
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -20,16 +20,17 @@
 
 namespace mozilla {
 
 class CDMProxy;
 
 class MediaFormatReader final : public MediaDecoderReader
 {
   typedef TrackInfo::TrackType TrackType;
+  typedef MozPromise<bool, MediaResult, /* IsExclusive = */ true> NotifyDataArrivedPromise;
 
 public:
   MediaFormatReader(AbstractMediaDecoder* aDecoder,
                     MediaDataDemuxer* aDemuxer,
                     VideoFrameContainer* aVideoFrameContainer = nullptr);
 
   virtual ~MediaFormatReader();
 
@@ -44,21 +45,19 @@ public:
   RefPtr<MetadataPromise> AsyncReadMetadata() override;
 
   void ReadUpdatedMetadata(MediaInfo* aInfo) override;
 
   RefPtr<SeekPromise>
   Seek(const SeekTarget& aTarget, int64_t aUnused) override;
 
 protected:
-  void NotifyDataArrivedInternal() override;
+  void NotifyDataArrived() override;
 
 public:
-  media::TimeIntervals GetBuffered() override;
-
   bool ForceZeroStartTime() const override;
 
   // For Media Resource Management
   void ReleaseResources() override;
 
   nsresult ResetDecode(TrackSet aTracks) override;
 
   RefPtr<ShutdownPromise> Shutdown() override;
@@ -87,20 +86,18 @@ private:
   nsresult InitInternal() override;
 
   bool HasVideo() const { return mVideo.mTrackDemuxer; }
   bool HasAudio() const { return mAudio.mTrackDemuxer; }
 
   bool IsWaitingOnCDMResource();
 
   bool InitDemuxer();
-  // Notify the demuxer that new data has been received.
-  // The next queued task calling GetBuffered() is guaranteed to have up to date
-  // buffered ranges.
-  void NotifyDemuxer();
+  // Notify the track demuxers that new data has been received.
+  void NotifyTrackDemuxers();
   void ReturnOutput(MediaData* aData, TrackType aTrack);
 
   // Enqueues a task to call Update(aTrack) on the decoder task queue.
   // Lock for corresponding track must be held.
   void ScheduleUpdate(TrackType aTrack);
   void Update(TrackType aTrack);
   // Handle actions should more data be received.
   // Returns true if no more action is required.
@@ -477,21 +474,23 @@ private:
   DecoderDataWithPromise mVideo;
 
   // Returns true when the decoder for this track needs input.
   bool NeedInput(DecoderData& aDecoder);
 
   DecoderData& GetDecoderData(TrackType aTrack);
 
   // Demuxer objects.
-  RefPtr<MediaDataDemuxer> mDemuxer;
+  class DemuxerProxy;
+  UniquePtr<DemuxerProxy> mDemuxer;
   bool mDemuxerInitDone;
   void OnDemuxerInitDone(nsresult);
   void OnDemuxerInitFailed(const MediaResult& aError);
   MozPromiseRequestHolder<MediaDataDemuxer::InitPromise> mDemuxerInitRequest;
+  void UpdateBuffered();
   void OnDemuxFailed(TrackType aTrack, const MediaResult& aError);
 
   void DoDemuxVideo();
   void OnVideoDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples);
   void OnVideoDemuxFailed(const MediaResult& aError)
   {
     OnDemuxFailed(TrackType::kVideoTrack, aError);
   }