Bug 1108701 - Use MediaPromises for RequestAudioData and RequestVideoData. r=cpearce, a=sledru
authorBobby Holley <bobbyholley@gmail.com>
Wed, 10 Dec 2014 14:03:56 -0800
changeset 242720 daaeccd4975e4b06a4c2dbb9b5b78cbbfa4df079
parent 242719 47c66f96853d93351945c1c8d818acff4c56953c
child 242721 e39c7a02ccd1a26f518bf57856fc0554ced33357
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce, sledru
bugs1108701
milestone36.0a2
Bug 1108701 - Use MediaPromises for RequestAudioData and RequestVideoData. r=cpearce, a=sledru
dom/media/MediaDataDecodedListener.h
dom/media/MediaDecoderReader.cpp
dom/media/MediaDecoderReader.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/fmp4/MP4Reader.cpp
dom/media/fmp4/MP4Reader.h
dom/media/mediasource/MediaSourceReader.cpp
dom/media/mediasource/MediaSourceReader.h
dom/media/omx/MediaCodecReader.cpp
dom/media/omx/MediaCodecReader.h
dom/media/omx/RtspMediaCodecReader.cpp
dom/media/omx/RtspMediaCodecReader.h
dom/media/webaudio/MediaBufferDecoder.cpp
dom/media/webm/WebMReader.cpp
--- a/dom/media/MediaDataDecodedListener.h
+++ b/dom/media/MediaDataDecodedListener.h
@@ -25,47 +25,16 @@ public:
     : mMonitor("MediaDataDecodedListener")
     , mTaskQueue(aTaskQueue)
     , mTarget(aTarget)
   {
     MOZ_ASSERT(aTarget);
     MOZ_ASSERT(aTaskQueue);
   }
 
-  virtual void OnAudioDecoded(AudioData* aSample) MOZ_OVERRIDE {
-    MonitorAutoLock lock(mMonitor);
-    if (!mTarget || !mTaskQueue) {
-      // We've been shutdown, abort.
-      return;
-    }
-    RefPtr<nsIRunnable> task(new DeliverAudioTask(aSample, mTarget));
-    mTaskQueue->Dispatch(task);
-  }
-
-  virtual void OnVideoDecoded(VideoData* aSample) MOZ_OVERRIDE {
-    MonitorAutoLock lock(mMonitor);
-    if (!mTarget || !mTaskQueue) {
-      // We've been shutdown, abort.
-      return;
-    }
-    RefPtr<nsIRunnable> task(new DeliverVideoTask(aSample, mTarget));
-    mTaskQueue->Dispatch(task);
-  }
-
-  virtual void OnNotDecoded(MediaData::Type aType,
-                            MediaDecoderReader::NotDecodedReason aReason) MOZ_OVERRIDE {
-    MonitorAutoLock lock(mMonitor);
-    if (!mTarget || !mTaskQueue) {
-      // We've been shutdown, abort.
-      return;
-    }
-    RefPtr<nsIRunnable> task(new DeliverNotDecodedTask(aType, aReason, mTarget));
-    mTaskQueue->Dispatch(task);
-  }
-
   void BreakCycles() {
     MonitorAutoLock lock(mMonitor);
     mTarget = nullptr;
     mTaskQueue = nullptr;
   }
 
   virtual void OnSeekCompleted(nsresult aResult) MOZ_OVERRIDE {
     MonitorAutoLock lock(mMonitor);
@@ -77,90 +46,16 @@ public:
                                                                    &Target::OnSeekCompleted,
                                                                    aResult));
     if (NS_FAILED(mTaskQueue->Dispatch(task))) {
       NS_WARNING("Failed to dispatch OnSeekCompleted task");
     }
   }
 
 private:
-
-  class DeliverAudioTask : public nsRunnable {
-  public:
-    DeliverAudioTask(AudioData* aSample, Target* aTarget)
-      : mSample(aSample)
-      , mTarget(aTarget)
-    {
-      MOZ_COUNT_CTOR(DeliverAudioTask);
-    }
-  protected:
-    ~DeliverAudioTask()
-    {
-      MOZ_COUNT_DTOR(DeliverAudioTask);
-    }
-  public:
-    NS_METHOD Run() {
-      mTarget->OnAudioDecoded(mSample);
-      return NS_OK;
-    }
-  private:
-    nsRefPtr<AudioData> mSample;
-    RefPtr<Target> mTarget;
-  };
-
-  class DeliverVideoTask : public nsRunnable {
-  public:
-    DeliverVideoTask(VideoData* aSample, Target* aTarget)
-      : mSample(aSample)
-      , mTarget(aTarget)
-    {
-      MOZ_COUNT_CTOR(DeliverVideoTask);
-    }
-  protected:
-    ~DeliverVideoTask()
-    {
-      MOZ_COUNT_DTOR(DeliverVideoTask);
-    }
-  public:
-    NS_METHOD Run() {
-      mTarget->OnVideoDecoded(mSample);
-      return NS_OK;
-    }
-  private:
-    nsRefPtr<VideoData> mSample;
-    RefPtr<Target> mTarget;
-  };
-
-  class DeliverNotDecodedTask : public nsRunnable {
-  public:
-    DeliverNotDecodedTask(MediaData::Type aType,
-                          MediaDecoderReader::NotDecodedReason aReason,
-                          Target* aTarget)
-      : mType(aType)
-      , mReason(aReason)
-      , mTarget(aTarget)
-    {
-      MOZ_COUNT_CTOR(DeliverNotDecodedTask);
-    }
-  protected:
-    ~DeliverNotDecodedTask()
-    {
-      MOZ_COUNT_DTOR(DeliverNotDecodedTask);
-    }
-  public:
-    NS_METHOD Run() {
-      mTarget->OnNotDecoded(mType, mReason);
-      return NS_OK;
-    }
-  private:
-    MediaData::Type mType;
-    MediaDecoderReader::NotDecodedReason mReason;
-    RefPtr<Target> mTarget;
-  };
-
   Monitor mMonitor;
   RefPtr<MediaTaskQueue> mTaskQueue;
   RefPtr<Target> mTarget;
 };
 
 } /* namespace mozilla */
 
 #endif // MediaDataDecodedListener_h_
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -68,16 +68,17 @@ public:
   size_t mSize;
 };
 
 MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder)
   : mAudioCompactor(mAudioQueue)
   , mDecoder(aDecoder)
   , mIgnoreAudioOutputFormat(false)
   , mStartTime(-1)
+  , mHitAudioDecodeError(false)
   , mTaskQueueIsBorrowed(false)
   , mAudioDiscontinuity(false)
   , mVideoDiscontinuity(false)
   , mShutdown(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderReader);
   EnsureMediaPromiseLog();
 }
@@ -183,79 +184,88 @@ public:
     mReader->RequestVideoData(skip, mTimeThreshold);
     return NS_OK;
   }
 private:
   nsRefPtr<MediaDecoderReader> mReader;
   int64_t mTimeThreshold;
 };
 
-void
+nsRefPtr<MediaDecoderReader::VideoDataPromise>
 MediaDecoderReader::RequestVideoData(bool aSkipToNextKeyframe,
                                      int64_t aTimeThreshold)
 {
+  nsRefPtr<VideoDataPromise> p = mBaseVideoPromise.Ensure(__func__);
   bool skip = aSkipToNextKeyframe;
   while (VideoQueue().GetSize() == 0 &&
          !VideoQueue().IsFinished()) {
     if (!DecodeVideoFrame(skip, aTimeThreshold)) {
       VideoQueue().Finish();
     } else if (skip) {
       // We still need to decode more data in order to skip to the next
       // keyframe. Post another task to the decode task queue to decode
       // again. We don't just decode straight in a loop here, as that
       // would hog the decode task queue.
       RefPtr<nsIRunnable> task(new RequestVideoWithSkipTask(this, aTimeThreshold));
       mTaskQueue->Dispatch(task);
-      return;
+      return p;
     }
   }
   if (VideoQueue().GetSize() > 0) {
     nsRefPtr<VideoData> v = VideoQueue().PopFront();
     if (v && mVideoDiscontinuity) {
       v->mDiscontinuity = true;
       mVideoDiscontinuity = false;
     }
-    GetCallback()->OnVideoDecoded(v);
+    mBaseVideoPromise.Resolve(v, __func__);
   } else if (VideoQueue().IsFinished()) {
-    GetCallback()->OnNotDecoded(MediaData::VIDEO_DATA, END_OF_STREAM);
+    mBaseVideoPromise.Reject(END_OF_STREAM, __func__);
+  } else {
+    MOZ_ASSERT(false, "Dropping this promise on the floor");
   }
+
+  return p;
 }
 
-void
+nsRefPtr<MediaDecoderReader::AudioDataPromise>
 MediaDecoderReader::RequestAudioData()
 {
+  nsRefPtr<AudioDataPromise> p = mBaseAudioPromise.Ensure(__func__);
   while (AudioQueue().GetSize() == 0 &&
          !AudioQueue().IsFinished()) {
     if (!DecodeAudioData()) {
       AudioQueue().Finish();
       break;
     }
     // AudioQueue size is still zero, post a task to try again. Don't spin
     // waiting in this while loop since it somehow prevents audio EOS from
     // coming in gstreamer 1.x when there is still video buffer waiting to be
     // consumed. (|mVideoSinkBufferCount| > 0)
     if (AudioQueue().GetSize() == 0 && mTaskQueue) {
       RefPtr<nsIRunnable> task(NS_NewRunnableMethod(
           this, &MediaDecoderReader::RequestAudioData));
       mTaskQueue->Dispatch(task.forget());
-      return;
+      return p;
     }
   }
   if (AudioQueue().GetSize() > 0) {
     nsRefPtr<AudioData> a = AudioQueue().PopFront();
     if (mAudioDiscontinuity) {
       a->mDiscontinuity = true;
       mAudioDiscontinuity = false;
     }
-    GetCallback()->OnAudioDecoded(a);
-    return;
+    mBaseAudioPromise.Resolve(a, __func__);
   } else if (AudioQueue().IsFinished()) {
-    GetCallback()->OnNotDecoded(MediaData::AUDIO_DATA, END_OF_STREAM);
-    return;
+    mBaseAudioPromise.Reject(mHitAudioDecodeError ? DECODE_ERROR : END_OF_STREAM, __func__);
+    mHitAudioDecodeError = false;
+  } else {
+    MOZ_ASSERT(false, "Dropping this promise on the floor");
   }
+
+  return p;
 }
 
 void
 MediaDecoderReader::SetCallback(RequestSampleCallback* aCallback)
 {
   mSampleDecodedCallback = aCallback;
 }
 
@@ -283,16 +293,20 @@ MediaDecoderReader::BreakCycles()
   mTaskQueue = nullptr;
 }
 
 nsRefPtr<ShutdownPromise>
 MediaDecoderReader::Shutdown()
 {
   MOZ_ASSERT(OnDecodeThread());
   mShutdown = true;
+
+  mBaseAudioPromise.RejectIfExists(END_OF_STREAM, __func__);
+  mBaseVideoPromise.RejectIfExists(END_OF_STREAM, __func__);
+
   ReleaseMediaResources();
   nsRefPtr<ShutdownPromise> p;
 
   // Spin down the task queue if necessary. We wait until BreakCycles to null
   // out mTaskQueue, since otherwise any remaining tasks could crash when they
   // invoke GetTaskQueue()->IsCurrentThreadIn().
   if (mTaskQueue && !mTaskQueueIsBorrowed) {
     // If we own our task queue, shutdown ends when the task queue is done.
@@ -302,18 +316,19 @@ MediaDecoderReader::Shutdown()
     // asynchronously).
     p = new ShutdownPromise(__func__);
     p->Resolve(true, __func__);
   }
 
   return p;
 }
 
-AudioDecodeRendezvous::AudioDecodeRendezvous()
-  : mMonitor("AudioDecodeRendezvous")
+AudioDecodeRendezvous::AudioDecodeRendezvous(MediaDecoderReader *aReader)
+  : mReader(aReader)
+  , mMonitor("AudioDecodeRendezvous")
   , mHaveResult(false)
 {
 }
 
 AudioDecodeRendezvous::~AudioDecodeRendezvous()
 {
 }
 
@@ -323,20 +338,18 @@ AudioDecodeRendezvous::OnAudioDecoded(Au
   MonitorAutoLock mon(mMonitor);
   mSample = aSample;
   mStatus = NS_OK;
   mHaveResult = true;
   mon.NotifyAll();
 }
 
 void
-AudioDecodeRendezvous::OnNotDecoded(MediaData::Type aType,
-                                    MediaDecoderReader::NotDecodedReason aReason)
+AudioDecodeRendezvous::OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
 {
-  MOZ_ASSERT(aType == MediaData::AUDIO_DATA);
   MonitorAutoLock mon(mMonitor);
   mSample = nullptr;
   mStatus = aReason == MediaDecoderReader::DECODE_ERROR ? NS_ERROR_FAILURE : NS_OK;
   mHaveResult = true;
   mon.NotifyAll();
 }
 
 void
@@ -344,29 +357,29 @@ AudioDecodeRendezvous::Reset()
 {
   MonitorAutoLock mon(mMonitor);
   mHaveResult = false;
   mStatus = NS_OK;
   mSample = nullptr;
 }
 
 nsresult
-AudioDecodeRendezvous::Await(nsRefPtr<AudioData>& aSample)
+AudioDecodeRendezvous::RequestAndWait(nsRefPtr<AudioData>& aSample)
 {
   MonitorAutoLock mon(mMonitor);
+  // XXXbholley: We hackily use the main thread for calling back the rendezvous,
+  // since the decode thread is blocked. This is fine for correctness but not
+  // for jank, and so it goes away in a subsequent patch.
+  nsCOMPtr<nsIThread> mainThread;
+  nsresult rv = NS_GetMainThread(getter_AddRefs(mainThread));
+  NS_ENSURE_SUCCESS(rv, rv);
+  mReader->RequestAudioData()->Then(mainThread.get(), __func__, this,
+                                    &AudioDecodeRendezvous::OnAudioDecoded,
+                                    &AudioDecodeRendezvous::OnAudioNotDecoded);
   while (!mHaveResult) {
     mon.Wait();
   }
   mHaveResult = false;
   aSample = mSample;
   return mStatus;
 }
 
-void
-AudioDecodeRendezvous::Cancel()
-{
-  MonitorAutoLock mon(mMonitor);
-  mStatus = NS_ERROR_ABORT;
-  mHaveResult = true;
-  mon.NotifyAll();
-}
-
 } // namespace mozilla
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -4,16 +4,17 @@
  * 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/. */
 #if !defined(MediaDecoderReader_h_)
 #define MediaDecoderReader_h_
 
 #include "AbstractMediaDecoder.h"
 #include "MediaInfo.h"
 #include "MediaData.h"
+#include "MediaPromise.h"
 #include "MediaQueue.h"
 #include "AudioCompactor.h"
 
 namespace mozilla {
 
 namespace dom {
 class TimeRanges;
 }
@@ -32,16 +33,19 @@ class MediaDecoderReader {
 public:
   enum NotDecodedReason {
     END_OF_STREAM,
     DECODE_ERROR,
     WAITING_FOR_DATA,
     CANCELED
   };
 
+  typedef MediaPromise<nsRefPtr<AudioData>, NotDecodedReason> AudioDataPromise;
+  typedef MediaPromise<nsRefPtr<VideoData>, NotDecodedReason> VideoDataPromise;
+
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReader)
 
   explicit MediaDecoderReader(AbstractMediaDecoder* aDecoder);
 
   // Initializes the reader, returns NS_OK on success, or NS_ERROR_FAILURE
   // on failure.
   virtual nsresult Init(MediaDecoderReader* aCloneDonor) = 0;
 
@@ -88,32 +92,32 @@ public:
   // outstanding Request*Data() calls after this is called. Calls to
   // Request*Data() made after this should be processed as usual.
   // Normally this call preceedes a Seek() call, or shutdown.
   // The first samples of every stream produced after a ResetDecode() call
   // *must* be marked as "discontinuities". If it's not, seeking work won't
   // properly!
   virtual nsresult ResetDecode();
 
-  // Requests the Reader to call OnAudioDecoded() on aCallback with one
-  // audio sample. The decode should be performed asynchronously, and
-  // the callback can be performed on any thread. Don't hold the decoder
+  // Requests one audio sample from the reader.
+  //
+  // The decode should be performed asynchronously, and the promise should
+  // be resolved when it is complete. Don't hold the decoder
   // monitor while calling this, as the implementation may try to wait
   // on something that needs the monitor and deadlock.
-  virtual void RequestAudioData();
+  virtual nsRefPtr<AudioDataPromise> RequestAudioData();
 
-  // Requests the Reader to call OnVideoDecoded() on aCallback with one
-  // video sample. The decode should be performed asynchronously, and
-  // the callback can be performed on any thread. Don't hold the decoder
-  // monitor while calling this, as the implementation may try to wait
-  // on something that needs the monitor and deadlock.
+  // Requests one video sample from the reader.
+  //
+  // Don't hold the decoder monitor while calling this, as the implementation
+  // may try to wait on something that needs the monitor and deadlock.
   // If aSkipToKeyframe is true, the decode should skip ahead to the
   // the next keyframe at or after aTimeThreshold microseconds.
-  virtual void RequestVideoData(bool aSkipToNextKeyframe,
-                                int64_t aTimeThreshold);
+  virtual nsRefPtr<VideoDataPromise>
+  RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold);
 
   virtual bool HasAudio() = 0;
   virtual bool HasVideo() = 0;
 
   // A function that is called before ReadMetadata() call.
   virtual void PreReadMetadata() {};
 
   // Read header data for all bitstreams in the file. Fills aInfo with
@@ -270,20 +274,31 @@ protected:
   // what we support.
   bool mIgnoreAudioOutputFormat;
 
   // The start time of the media, in microseconds. This is the presentation
   // time of the first frame decoded from the media. This is initialized to -1,
   // and then set to a value >= by MediaDecoderStateMachine::SetStartTime(),
   // after which point it never changes.
   int64_t mStartTime;
-private:
+
+  // This is a quick-and-dirty way for DecodeAudioData implementations to
+  // communicate the presence of a decoding error to RequestAudioData. We should
+  // replace this with a promise-y mechanism as we make this stuff properly
+  // async.
+  bool mHitAudioDecodeError;
 
+private:
   nsRefPtr<RequestSampleCallback> mSampleDecodedCallback;
 
+  // Promises used only for the base-class (sync->async adapter) implementation
+  // of Request{Audio,Video}Data.
+  MediaPromiseHolder<AudioDataPromise> mBaseAudioPromise;
+  MediaPromiseHolder<VideoDataPromise> mBaseVideoPromise;
+
   nsRefPtr<MediaTaskQueue> mTaskQueue;
   bool mTaskQueueIsBorrowed;
 
   // Flags whether a the next audio/video sample comes after a "gap" or
   // "discontinuity" in the stream. For example after a seek.
   bool mAudioDiscontinuity;
   bool mVideoDiscontinuity;
   bool mShutdown;
@@ -292,63 +307,51 @@ private:
 // Interface that callers to MediaDecoderReader::Request{Audio,Video}Data()
 // must implement to receive the requested samples asynchronously.
 // This object is refcounted, and cycles must be broken by calling
 // BreakCycles() during shutdown.
 class RequestSampleCallback {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RequestSampleCallback)
 
-  // Receives the result of a RequestAudioData() call.
-  virtual void OnAudioDecoded(AudioData* aSample) = 0;
-
-  // Receives the result of a RequestVideoData() call.
-  virtual void OnVideoDecoded(VideoData* aSample) = 0;
-
-  // Called when a RequestAudioData() or RequestVideoData() call can't be
-  // fulfiled. The reason is passed as aReason.
-  virtual void OnNotDecoded(MediaData::Type aType,
-                            MediaDecoderReader::NotDecodedReason aReason) = 0;
-
   virtual void OnSeekCompleted(nsresult aResult) = 0;
 
   // Called during shutdown to break any reference cycles.
   virtual void BreakCycles() = 0;
 
 protected:
   virtual ~RequestSampleCallback() {}
 };
 
 // A RequestSampleCallback implementation that can be passed to the
 // MediaDecoderReader to block the thread requesting an audio sample until
 // the audio decode is complete. This is used to adapt the asynchronous
 // model of the MediaDecoderReader to a synchronous model.
-class AudioDecodeRendezvous : public RequestSampleCallback {
+class AudioDecodeRendezvous {
 public:
-  AudioDecodeRendezvous();
-  ~AudioDecodeRendezvous();
+  AudioDecodeRendezvous(MediaDecoderReader *aReader);
 
-  // RequestSampleCallback implementation. Called when decode is complete.
-  // Note: aSample is null at end of stream.
-  virtual void OnAudioDecoded(AudioData* aSample) MOZ_OVERRIDE;
-  virtual void OnVideoDecoded(VideoData* aSample) MOZ_OVERRIDE {}
-  virtual void OnNotDecoded(MediaData::Type aType,
-                            MediaDecoderReader::NotDecodedReason aReason) MOZ_OVERRIDE;
-  virtual void OnSeekCompleted(nsresult aResult) MOZ_OVERRIDE {};
-  virtual void BreakCycles() MOZ_OVERRIDE {};
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(AudioDecodeRendezvous)
+
+  void OnAudioDecoded(AudioData* aSample);
+  void OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason);
   void Reset();
 
   // Returns failure on error, or NS_OK.
   // If *aSample is null, EOS has been reached.
-  nsresult Await(nsRefPtr<AudioData>& aSample);
+  nsresult RequestAndWait(nsRefPtr<AudioData>& aSample);
 
   // Interrupts a call to Wait().
   void Cancel();
 
+protected:
+  ~AudioDecodeRendezvous();
+
 private:
+  nsRefPtr<MediaDecoderReader> mReader;
   Monitor mMonitor;
   nsresult mStatus;
   nsRefPtr<AudioData> mSample;
   bool mHaveResult;
 };
 
 } // namespace mozilla
 
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -620,17 +620,20 @@ MediaDecoderStateMachine::DecodeVideo()
     currentTime = mState == DECODER_STATE_SEEKING ? 0 : GetMediaTime();
 
     // Time the video decode, so that if it's slow, we can increase our low
     // audio threshold to reduce the chance of an audio underrun while we're
     // waiting for a video decode to complete.
     mVideoDecodeStartTime = TimeStamp::Now();
   }
 
-  mReader->RequestVideoData(skipToNextKeyFrame, currentTime);
+  mReader->RequestVideoData(skipToNextKeyFrame, currentTime)
+         ->Then(DecodeTaskQueue(), __func__, this,
+                &MediaDecoderStateMachine::OnVideoDecoded,
+                &MediaDecoderStateMachine::OnVideoNotDecoded);
 }
 
 bool
 MediaDecoderStateMachine::NeedToDecodeAudio()
 {
   AssertCurrentThreadInMonitor();
   SAMPLE_LOG("NeedToDecodeAudio() isDec=%d decToTar=%d minPrl=%d seek=%d enufAud=%d",
              IsAudioDecoding(), mDecodeToSeekTarget, mMinimizePreroll,
@@ -663,17 +666,19 @@ MediaDecoderStateMachine::DecodeAudio()
     // We don't want to consider skipping to the next keyframe if we've
     // only just started up the decode loop, so wait until we've decoded
     // some audio data before enabling the keyframe skip logic on audio.
     if (mIsAudioPrerolling &&
         GetDecodedAudioDuration() >= mAudioPrerollUsecs * mPlaybackRate) {
       mIsAudioPrerolling = false;
     }
   }
-  mReader->RequestAudioData();
+  mReader->RequestAudioData()->Then(DecodeTaskQueue(), __func__, this,
+                                    &MediaDecoderStateMachine::OnAudioDecoded,
+                                    &MediaDecoderStateMachine::OnAudioNotDecoded);
 }
 
 bool
 MediaDecoderStateMachine::IsAudioSeekComplete()
 {
   AssertCurrentThreadInMonitor();
   SAMPLE_LOG("IsAudioSeekComplete() curTarVal=%d mAudDis=%d aqFin=%d aqSz=%d",
     mCurrentSeekTarget.IsValid(), mDropAudioUntilNextDiscontinuity, AudioQueue().IsFinished(), AudioQueue().GetSize());
@@ -912,16 +917,17 @@ MediaDecoderStateMachine::MaybeFinishDec
   if (NS_FAILED(FinishDecodeFirstFrame())) {
     DecodeError();
   }
 }
 
 void
 MediaDecoderStateMachine::OnVideoDecoded(VideoData* aVideoSample)
 {
+  MOZ_ASSERT(OnDecodeThread());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   nsRefPtr<VideoData> video(aVideoSample);
   mVideoRequestPending = false;
 
   SAMPLE_LOG("OnVideoDecoded [%lld,%lld] disc=%d",
              (video ? video->mTime : -1),
              (video ? video->GetEndTime() : -1),
              (video ? video->mDiscontinuity : 0));
@@ -2114,22 +2120,27 @@ MediaDecoderStateMachine::DecodeFirstFra
     NS_ENSURE_SUCCESS(res, res);
   } else if (mDecodingFrozenAtStateMetadata) {
     SetStartTime(mStartTime);
     nsresult res = FinishDecodeFirstFrame();
     NS_ENSURE_SUCCESS(res, res);
   } else {
     if (HasAudio()) {
       ReentrantMonitorAutoExit unlock(mDecoder->GetReentrantMonitor());
-      mReader->RequestAudioData();
+      mReader->RequestAudioData()->Then(DecodeTaskQueue(), __func__, this,
+                                        &MediaDecoderStateMachine::OnAudioDecoded,
+                                        &MediaDecoderStateMachine::OnAudioNotDecoded);
     }
     if (HasVideo()) {
       mVideoDecodeStartTime = TimeStamp::Now();
       ReentrantMonitorAutoExit unlock(mDecoder->GetReentrantMonitor());
-      mReader->RequestVideoData(false, 0);
+      mReader->RequestVideoData(false, 0)
+             ->Then(DecodeTaskQueue(), __func__, this,
+                    &MediaDecoderStateMachine::OnVideoDecoded,
+                    &MediaDecoderStateMachine::OnVideoNotDecoded);
     }
   }
 
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::FinishDecodeFirstFrame()
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -366,16 +366,27 @@ public:
   // the state machine is free to return to prerolling normally. Note
   // "prerolling" in this context refers to when we decode and buffer decoded
   // samples in advance of when they're needed for playback.
   void SetMinimizePrerollUntilPlaybackStarts();
 
   void OnAudioDecoded(AudioData* aSample);
   void OnVideoDecoded(VideoData* aSample);
   void OnNotDecoded(MediaData::Type aType, MediaDecoderReader::NotDecodedReason aReason);
+  void OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
+  {
+    MOZ_ASSERT(OnDecodeThread());
+    OnNotDecoded(MediaData::AUDIO_DATA, aReason);
+  }
+  void OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
+  {
+    MOZ_ASSERT(OnDecodeThread());
+    OnNotDecoded(MediaData::VIDEO_DATA, aReason);
+  }
+
   void OnSeekCompleted(nsresult aResult);
 
 private:
   void AcquireMonitorAndInvokeDecodeError();
 
 protected:
   virtual ~MediaDecoderStateMachine();
 
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -60,18 +60,18 @@ TrackTypeToStr(TrackType aTrack)
   default:
     return "Unknown";
   }
 }
 #endif
 
 MP4Reader::MP4Reader(AbstractMediaDecoder* aDecoder)
   : MediaDecoderReader(aDecoder)
-  , mAudio("MP4 audio decoder data", Preferences::GetUint("media.mp4-audio-decode-ahead", 2))
-  , mVideo("MP4 video decoder data", Preferences::GetUint("media.mp4-video-decode-ahead", 2))
+  , mAudio(MediaData::AUDIO_DATA, Preferences::GetUint("media.mp4-audio-decode-ahead", 2))
+  , mVideo(MediaData::VIDEO_DATA, Preferences::GetUint("media.mp4-video-decode-ahead", 2))
   , mLastReportedNumDecodedFrames(0)
   , mLayersBackendType(layers::LayersBackend::LAYERS_NONE)
   , mDemuxerInitialized(false)
   , mIsEncrypted(false)
   , mIndexReady(false)
   , mIndexMonitor("MP4 index")
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
@@ -93,26 +93,31 @@ MP4Reader::Shutdown()
     mAudio.mDecoder->Shutdown();
     mAudio.mDecoder = nullptr;
   }
   if (mAudio.mTaskQueue) {
     mAudio.mTaskQueue->BeginShutdown();
     mAudio.mTaskQueue->AwaitShutdownAndIdle();
     mAudio.mTaskQueue = nullptr;
   }
+  mAudio.mPromise.SetMonitor(nullptr);
+  MOZ_ASSERT(mAudio.mPromise.IsEmpty());
+
   if (mVideo.mDecoder) {
     Flush(kVideo);
     mVideo.mDecoder->Shutdown();
     mVideo.mDecoder = nullptr;
   }
   if (mVideo.mTaskQueue) {
     mVideo.mTaskQueue->BeginShutdown();
     mVideo.mTaskQueue->AwaitShutdownAndIdle();
     mVideo.mTaskQueue = nullptr;
   }
+  mVideo.mPromise.SetMonitor(nullptr);
+  MOZ_ASSERT(mVideo.mPromise.IsEmpty());
   // Dispose of the queued sample before shutting down the demuxer
   mQueuedVideoSample = nullptr;
 
   if (mPlatform) {
     mPlatform->Shutdown();
     mPlatform = nullptr;
   }
 
@@ -446,61 +451,70 @@ MP4Reader::HasVideo()
 {
   return mVideo.mActive;
 }
 
 MP4Reader::DecoderData&
 MP4Reader::GetDecoderData(TrackType aTrack)
 {
   MOZ_ASSERT(aTrack == kAudio || aTrack == kVideo);
-  return (aTrack == kAudio) ? mAudio : mVideo;
+  if (aTrack == kAudio) {
+    return mAudio;
+  }
+  return mVideo;
 }
 
-void
+nsRefPtr<MediaDecoderReader::VideoDataPromise>
 MP4Reader::RequestVideoData(bool aSkipToNextKeyframe,
                             int64_t aTimeThreshold)
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
   VLOG("RequestVideoData skip=%d time=%lld", aSkipToNextKeyframe, aTimeThreshold);
 
   // Record number of frames decoded and parsed. Automatically update the
   // stats counters using the AutoNotifyDecoded stack-based class.
   uint32_t parsed = 0, decoded = 0;
   AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded);
 
   MOZ_ASSERT(HasVideo() && mPlatform && mVideo.mDecoder);
 
+  bool eos = false;
   if (aSkipToNextKeyframe) {
-    if (!SkipVideoDemuxToNextKeyFrame(aTimeThreshold, parsed) ||
-        NS_FAILED(mVideo.mDecoder->Flush())) {
+    eos = !SkipVideoDemuxToNextKeyFrame(aTimeThreshold, parsed);
+    if (!eos && NS_FAILED(mVideo.mDecoder->Flush())) {
       NS_WARNING("Failed to skip/flush video when skipping-to-next-keyframe.");
     }
   }
 
-  auto& decoder = GetDecoderData(kVideo);
-  MonitorAutoLock lock(decoder.mMonitor);
-  decoder.mOutputRequested = true;
-  ScheduleUpdate(kVideo);
+  MonitorAutoLock lock(mVideo.mMonitor);
+  nsRefPtr<VideoDataPromise> p = mVideo.mPromise.Ensure(__func__);
+  if (eos) {
+    mVideo.mPromise.Reject(END_OF_STREAM, __func__);
+  } else {
+    ScheduleUpdate(kVideo);
+  }
 
   // Report the number of "decoded" frames as the difference in the
   // mNumSamplesOutput field since the last time we were called.
   uint64_t delta = mVideo.mNumSamplesOutput - mLastReportedNumDecodedFrames;
   decoded = static_cast<uint32_t>(delta);
   mLastReportedNumDecodedFrames = mVideo.mNumSamplesOutput;
+
+  return p;
 }
 
-void
+nsRefPtr<MediaDecoderReader::AudioDataPromise>
 MP4Reader::RequestAudioData()
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
   VLOG("RequestAudioData");
-  auto& decoder = GetDecoderData(kAudio);
-  MonitorAutoLock lock(decoder.mMonitor);
-  decoder.mOutputRequested = true;
+  MonitorAutoLock lock(mAudio.mMonitor);
+  nsRefPtr<AudioDataPromise> p = mAudio.mPromise.Ensure(__func__);
   ScheduleUpdate(kAudio);
+  return p;
 }
 
 void
 MP4Reader::ScheduleUpdate(TrackType aTrack)
 {
   auto& decoder = GetDecoderData(aTrack);
   decoder.mMonitor.AssertCurrentThreadOwns();
   if (decoder.mUpdateScheduled) {
@@ -520,115 +534,101 @@ MP4Reader::NeedInput(DecoderData& aDecod
   // We try to keep a few more compressed samples input than decoded samples
   // have been output, provided the state machine has requested we send it a
   // decoded sample. To account for H.264 streams which may require a longer
   // run of input than we input, decoders fire an "input exhausted" callback,
   // which overrides our "few more samples" threshold.
   return
     !aDecoder.mError &&
     !aDecoder.mDemuxEOS &&
-    aDecoder.mOutputRequested &&
+    aDecoder.HasPromise() &&
     aDecoder.mOutput.IsEmpty() &&
     (aDecoder.mInputExhausted ||
      aDecoder.mNumSamplesInput - aDecoder.mNumSamplesOutput < aDecoder.mDecodeAhead);
 }
 
 void
 MP4Reader::Update(TrackType aTrack)
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
 
   bool needInput = false;
   bool needOutput = false;
-  bool eos = false;
   auto& decoder = GetDecoderData(aTrack);
-  nsRefPtr<MediaData> output;
   {
     MonitorAutoLock lock(decoder.mMonitor);
     decoder.mUpdateScheduled = false;
     if (NeedInput(decoder)) {
       needInput = true;
       decoder.mInputExhausted = false;
       decoder.mNumSamplesInput++;
     }
-    needOutput = decoder.mOutputRequested;
-    if (needOutput && !decoder.mOutput.IsEmpty()) {
-      output = decoder.mOutput[0];
-      decoder.mOutput.RemoveElementAt(0);
+    if (decoder.HasPromise()) {
+      needOutput = true;
+      if (!decoder.mOutput.IsEmpty()) {
+        nsRefPtr<MediaData> output = decoder.mOutput[0];
+        decoder.mOutput.RemoveElementAt(0);
+        ReturnOutput(output, aTrack);
+      } else if (decoder.mDrainComplete) {
+        decoder.RejectPromise(END_OF_STREAM, __func__);
+      }
     }
-    eos = decoder.mDrainComplete;
   }
-  VLOG("Update(%s) ni=%d no=%d iex=%d or=%d fl=%d",
+
+  VLOG("Update(%s) ni=%d no=%d iex=%d fl=%d",
        TrackTypeToStr(aTrack),
        needInput,
        needOutput,
        decoder.mInputExhausted,
-       decoder.mOutputRequested,
        decoder.mIsFlushing);
 
   if (needInput) {
     MP4Sample* sample = PopSample(aTrack);
     if (sample) {
       decoder.mDecoder->Input(sample);
     } else {
       {
         MonitorAutoLock lock(decoder.mMonitor);
         MOZ_ASSERT(!decoder.mDemuxEOS);
         decoder.mDemuxEOS = true;
       }
       // DrainComplete takes care of reporting EOS upwards
       decoder.mDecoder->Drain();
     }
   }
-  if (needOutput) {
-    if (output) {
-      ReturnOutput(output, aTrack);
-    } else if (eos) {
-      ReturnEOS(aTrack);
-    }
-  }
 }
 
 void
 MP4Reader::ReturnOutput(MediaData* aData, TrackType aTrack)
 {
   auto& decoder = GetDecoderData(aTrack);
-  {
-    MonitorAutoLock lock(decoder.mMonitor);
-    MOZ_ASSERT(decoder.mOutputRequested);
-    decoder.mOutputRequested = false;
-    if (decoder.mDiscontinuity) {
-      decoder.mDiscontinuity = false;
-      aData->mDiscontinuity = true;
-    }
+  decoder.mMonitor.AssertCurrentThreadOwns();
+  MOZ_ASSERT(decoder.HasPromise());
+  if (decoder.mDiscontinuity) {
+    decoder.mDiscontinuity = false;
+    aData->mDiscontinuity = true;
   }
 
   if (aTrack == kAudio) {
     AudioData* audioData = static_cast<AudioData*>(aData);
 
     if (audioData->mChannels != mInfo.mAudio.mChannels ||
         audioData->mRate != mInfo.mAudio.mRate) {
       LOG("MP4Reader::ReturnOutput change of sampling rate:%d->%d",
           mInfo.mAudio.mRate, audioData->mRate);
       mInfo.mAudio.mRate = audioData->mRate;
       mInfo.mAudio.mChannels = audioData->mChannels;
     }
 
-    GetCallback()->OnAudioDecoded(audioData);
+    mAudio.mPromise.Resolve(audioData, __func__);
   } else if (aTrack == kVideo) {
-    GetCallback()->OnVideoDecoded(static_cast<VideoData*>(aData));
+    mVideo.mPromise.Resolve(static_cast<VideoData*>(aData), __func__);
   }
 }
 
-void
-MP4Reader::ReturnEOS(TrackType aTrack)
-{
-  GetCallback()->OnNotDecoded(aTrack == kAudio ? MediaData::AUDIO_DATA : MediaData::VIDEO_DATA, END_OF_STREAM);
-}
-
 MP4Sample*
 MP4Reader::PopSample(TrackType aTrack)
 {
   MonitorAutoLock mon(mIndexMonitor);
   switch (aTrack) {
     case kAudio:
       return mDemuxer->DemuxAudioSample();
 
@@ -674,17 +674,17 @@ MP4Reader::Output(TrackType aTrack, Medi
   if (decoder.mIsFlushing) {
     LOG("MP4Reader produced output while flushing, discarding.");
     mon.NotifyAll();
     return;
   }
 
   decoder.mOutput.AppendElement(aSample);
   decoder.mNumSamplesOutput++;
-  if (NeedInput(decoder) || decoder.mOutputRequested) {
+  if (NeedInput(decoder) || decoder.HasPromise()) {
     ScheduleUpdate(aTrack);
   }
 }
 
 void
 MP4Reader::DrainComplete(TrackType aTrack)
 {
   DecoderData& data = GetDecoderData(aTrack);
@@ -704,18 +704,20 @@ MP4Reader::InputExhausted(TrackType aTra
 
 void
 MP4Reader::Error(TrackType aTrack)
 {
   DecoderData& data = GetDecoderData(aTrack);
   {
     MonitorAutoLock mon(data.mMonitor);
     data.mError = true;
+    if (data.HasPromise()) {
+      data.RejectPromise(DECODE_ERROR, __func__);
+    }
   }
-  GetCallback()->OnNotDecoded(aTrack == kVideo ? MediaData::VIDEO_DATA : MediaData::AUDIO_DATA, DECODE_ERROR);
 }
 
 void
 MP4Reader::Flush(TrackType aTrack)
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
   VLOG("Flush(%s) BEGIN", TrackTypeToStr(aTrack));
   DecoderData& data = GetDecoderData(aTrack);
@@ -734,20 +736,19 @@ MP4Reader::Flush(TrackType aTrack)
   data.mDecoder->Flush();
   {
     MonitorAutoLock mon(data.mMonitor);
     data.mIsFlushing = false;
     data.mOutput.Clear();
     data.mNumSamplesInput = 0;
     data.mNumSamplesOutput = 0;
     data.mInputExhausted = false;
-    if (data.mOutputRequested) {
-      GetCallback()->OnNotDecoded(aTrack == kVideo ? MediaData::VIDEO_DATA : MediaData::AUDIO_DATA, CANCELED);
+    if (data.HasPromise()) {
+      data.RejectPromise(CANCELED, __func__);
     }
-    data.mOutputRequested = false;
     data.mDiscontinuity = true;
     data.mUpdateScheduled = false;
   }
   if (aTrack == kVideo) {
     mQueuedVideoSample = nullptr;
   }
   VLOG("Flush(%s) END", TrackTypeToStr(aTrack));
 }
@@ -760,22 +761,19 @@ MP4Reader::SkipVideoDemuxToNextKeyFrame(
   MOZ_ASSERT(mVideo.mDecoder);
 
   Flush(kVideo);
 
   // Loop until we reach the next keyframe after the threshold.
   while (true) {
     nsAutoPtr<MP4Sample> compressed(PopSample(kVideo));
     if (!compressed) {
-      // EOS, or error. Let the state machine know.
-      GetCallback()->OnNotDecoded(MediaData::VIDEO_DATA, END_OF_STREAM);
-      {
-        MonitorAutoLock mon(mVideo.mMonitor);
-        mVideo.mDemuxEOS = true;
-      }
+      // EOS, or error. This code assumes EOS, which may or may not be right.
+      MonitorAutoLock mon(mVideo.mMonitor);
+      mVideo.mDemuxEOS = true;
       return false;
     }
     parsed++;
     if (!compressed->is_sync_point ||
         compressed->composition_timestamp < aTimeThreshold) {
       continue;
     }
     mQueuedVideoSample = compressed;
--- a/dom/media/fmp4/MP4Reader.h
+++ b/dom/media/fmp4/MP4Reader.h
@@ -32,20 +32,20 @@ class MP4Reader MOZ_FINAL : public Media
 
 public:
   explicit MP4Reader(AbstractMediaDecoder* aDecoder);
 
   virtual ~MP4Reader();
 
   virtual nsresult Init(MediaDecoderReader* aCloneDonor) MOZ_OVERRIDE;
 
-  virtual void RequestVideoData(bool aSkipToNextKeyframe,
-                                int64_t aTimeThreshold) MOZ_OVERRIDE;
+  virtual nsRefPtr<VideoDataPromise>
+  RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) MOZ_OVERRIDE;
 
-  virtual void RequestAudioData() MOZ_OVERRIDE;
+  virtual nsRefPtr<AudioDataPromise> RequestAudioData() MOZ_OVERRIDE;
 
   virtual bool HasAudio() MOZ_OVERRIDE;
   virtual bool HasVideo() MOZ_OVERRIDE;
 
   // PreReadMetadata() is called by MediaDecoderStateMachine::DecodeMetadata()
   // before checking hardware resource. In Gonk, it requests hardware codec so
   // MediaDecoderStateMachine could go to DORMANT state if the hardware codec is
   // not available.
@@ -75,17 +75,16 @@ public:
     MOZ_OVERRIDE;
 
   virtual nsresult ResetDecode() MOZ_OVERRIDE;
 
   virtual nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
 
 private:
 
-  void ReturnEOS(TrackType aTrack);
   void ReturnOutput(MediaData* aData, TrackType aTrack);
 
   // Sends input to decoder for aTrack, and output to the state machine,
   // if necessary.
   void Update(TrackType aTrack);
 
   // Enqueues a task to call Update(aTrack) on the decoder task queue.
   // Lock for corresponding track must be held.
@@ -147,27 +146,28 @@ private:
       mReader->ReleaseMediaResources();
     }
   private:
     MP4Reader* mReader;
     mp4_demuxer::TrackType mType;
   };
 
   struct DecoderData {
-    DecoderData(const char* aMonitorName,
+    DecoderData(MediaData::Type aType,
                 uint32_t aDecodeAhead)
-      : mMonitor(aMonitorName)
+      : mType(aType)
+      , mMonitor(aType == MediaData::AUDIO_DATA ? "MP4 audio decoder data"
+                                                : "MP4 video decoder data")
       , mNumSamplesInput(0)
       , mNumSamplesOutput(0)
       , mDecodeAhead(aDecodeAhead)
       , mActive(false)
       , mInputExhausted(false)
       , mError(false)
       , mIsFlushing(false)
-      , mOutputRequested(false)
       , mUpdateScheduled(false)
       , mDemuxEOS(false)
       , mDrainComplete(false)
       , mDiscontinuity(false)
     {
     }
 
     // The platform decoder.
@@ -175,36 +175,62 @@ private:
     // TaskQueue on which decoder can choose to decode.
     // Only non-null up until the decoder is created.
     nsRefPtr<MediaTaskQueue> mTaskQueue;
     // Callback that receives output and error notifications from the decoder.
     nsAutoPtr<DecoderCallback> mCallback;
     // Decoded samples returned my mDecoder awaiting being returned to
     // state machine upon request.
     nsTArray<nsRefPtr<MediaData> > mOutput;
+    // Disambiguate Audio vs Video.
+    MediaData::Type mType;
+
+    // These get overriden in the templated concrete class.
+    virtual bool HasPromise() = 0;
+    virtual void RejectPromise(MediaDecoderReader::NotDecodedReason aReason,
+                               const char* aMethodName) = 0;
 
     // Monitor that protects all non-threadsafe state; the primitives
     // that follow.
     Monitor mMonitor;
     uint64_t mNumSamplesInput;
     uint64_t mNumSamplesOutput;
     uint32_t mDecodeAhead;
     // Whether this stream exists in the media.
     bool mActive;
     bool mInputExhausted;
     bool mError;
     bool mIsFlushing;
-    bool mOutputRequested;
     bool mUpdateScheduled;
     bool mDemuxEOS;
     bool mDrainComplete;
     bool mDiscontinuity;
   };
-  DecoderData mAudio;
-  DecoderData mVideo;
+
+  template<typename PromiseType>
+  struct DecoderDataWithPromise : public DecoderData {
+    DecoderDataWithPromise(MediaData::Type aType, uint32_t aDecodeAhead) :
+      DecoderData(aType, aDecodeAhead)
+    {
+      mPromise.SetMonitor(&mMonitor);
+    }
+
+    MediaPromiseHolder<PromiseType> mPromise;
+
+    bool HasPromise() MOZ_OVERRIDE { return !mPromise.IsEmpty(); }
+    void RejectPromise(MediaDecoderReader::NotDecodedReason aReason,
+                       const char* aMethodName) MOZ_OVERRIDE
+    {
+      mPromise.Reject(aReason, aMethodName);
+    }
+  };
+
+  DecoderDataWithPromise<AudioDataPromise> mAudio;
+  DecoderDataWithPromise<VideoDataPromise> mVideo;
+
   // Queued samples extracted by the demuxer, but not yet sent to the platform
   // decoder.
   nsAutoPtr<mp4_demuxer::MP4Sample> mQueuedVideoSample;
 
   // Returns true when the decoder for this track needs input.
   // aDecoder.mMonitor must be locked.
   bool NeedInput(DecoderData& aDecoder);
 
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -89,156 +89,213 @@ MediaSourceReader::IsWaitingMediaResourc
     if (!mEssentialTrackBuffers[i]->IsReady()) {
       return true;
     }
   }
 
   return !mHasEssentialTrackBuffers;
 }
 
-void
+nsRefPtr<MediaDecoderReader::AudioDataPromise>
 MediaSourceReader::RequestAudioData()
 {
+  nsRefPtr<AudioDataPromise> p = mAudioPromise.Ensure(__func__);
   MSE_DEBUGV("MediaSourceReader(%p)::RequestAudioData", this);
   if (!mAudioReader) {
     MSE_DEBUG("MediaSourceReader(%p)::RequestAudioData called with no audio reader", this);
-    GetCallback()->OnNotDecoded(MediaData::AUDIO_DATA, DECODE_ERROR);
-    return;
+    mAudioPromise.Reject(DECODE_ERROR, __func__);
+    return p;
   }
   mAudioIsSeeking = false;
   SwitchAudioReader(mLastAudioTime);
-  mAudioReader->RequestAudioData();
+  mAudioReader->RequestAudioData()->Then(GetTaskQueue(), __func__, this,
+                                         &MediaSourceReader::OnAudioDecoded,
+                                         &MediaSourceReader::OnAudioNotDecoded);
+  return p;
 }
 
 void
 MediaSourceReader::OnAudioDecoded(AudioData* aSample)
 {
   MSE_DEBUGV("MediaSourceReader(%p)::OnAudioDecoded [mTime=%lld mDuration=%lld mDiscontinuity=%d]",
              this, aSample->mTime, aSample->mDuration, aSample->mDiscontinuity);
   if (mDropAudioBeforeThreshold) {
     if (aSample->mTime < mTimeThreshold) {
       MSE_DEBUG("MediaSourceReader(%p)::OnAudioDecoded mTime=%lld < mTimeThreshold=%lld",
                 this, aSample->mTime, mTimeThreshold);
-      mAudioReader->RequestAudioData();
+      mAudioReader->RequestAudioData()->Then(GetTaskQueue(), __func__, this,
+                                             &MediaSourceReader::OnAudioDecoded,
+                                             &MediaSourceReader::OnAudioNotDecoded);
       return;
     }
     mDropAudioBeforeThreshold = false;
   }
 
   // Any OnAudioDecoded callbacks received while mAudioIsSeeking must be not
   // update our last used timestamp, as these are emitted by the reader we're
   // switching away from.
   if (!mAudioIsSeeking) {
     mLastAudioTime = aSample->mTime + aSample->mDuration;
   }
-  GetCallback()->OnAudioDecoded(aSample);
+
+  mAudioPromise.Resolve(aSample, __func__);
+}
+
+// Find the closest approximation to the end time for this stream.
+// mLast{Audio,Video}Time differs from the actual end time because of
+// Bug 1065207 - the duration of a WebM fragment is an estimate not the
+// actual duration. In the case of audio time an example of where they
+// differ would be the actual sample duration being small but the
+// previous sample being large. The buffered end time uses that last
+// sample duration as an estimate of the end time duration giving an end
+// time that is greater than mLastAudioTime, which is the actual sample
+// end time.
+// Reader switching is based on the buffered end time though so they can be
+// quite different. By using the EOS_FUZZ_US and the buffered end time we
+// attempt to account for this difference.
+static void
+AdjustEndTime(int64_t* aEndTime, MediaDecoderReader* aReader)
+{
+  if (aReader) {
+    nsRefPtr<dom::TimeRanges> ranges = new dom::TimeRanges();
+    aReader->GetBuffered(ranges);
+    if (ranges->Length() > 0) {
+      // End time is a double so we convert to nearest by adding 0.5.
+      int64_t end = ranges->GetEndTime() * USECS_PER_S + 0.5;
+      *aEndTime = std::max(*aEndTime, end);
+    }
+  }
 }
 
 void
+MediaSourceReader::OnAudioNotDecoded(NotDecodedReason aReason)
+{
+  MSE_DEBUG("MediaSourceReader(%p)::OnAudioNotDecoded aReason=%u IsEnded: %d", this, aReason, IsEnded());
+  if (aReason == DECODE_ERROR || aReason == CANCELED) {
+    mAudioPromise.Reject(aReason, __func__);
+    return;
+  }
+
+  // End of stream. Force switching past this stream to another reader by
+  // switching to the end of the buffered range.
+  MOZ_ASSERT(aReason == END_OF_STREAM);
+  if (mAudioReader) {
+    AdjustEndTime(&mLastAudioTime, mAudioReader);
+  }
+
+  // See if we can find a different reader that can pick up where we left off. We use the
+  // EOS_FUZZ_US to allow for the fact that our end time can be inaccurate due to bug
+  // 1065207.
+  if (SwitchAudioReader(mLastAudioTime + EOS_FUZZ_US)) {
+    mAudioReader->RequestAudioData()->Then(GetTaskQueue(), __func__, this,
+                                           &MediaSourceReader::OnAudioDecoded,
+                                           &MediaSourceReader::OnAudioNotDecoded);
+    return;
+  }
+
+  // If the entire MediaSource is done, generate an EndOfStream.
+  if (IsEnded()) {
+    mAudioPromise.Reject(END_OF_STREAM, __func__);
+    return;
+  }
+
+  // We don't have the data the caller wants. Tell that we're waiting for JS to
+  // give us more data.
+  mAudioPromise.Reject(WAITING_FOR_DATA, __func__);
+}
+
+
+nsRefPtr<MediaDecoderReader::VideoDataPromise>
 MediaSourceReader::RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold)
 {
+  nsRefPtr<VideoDataPromise> p = mVideoPromise.Ensure(__func__);
   MSE_DEBUGV("MediaSourceReader(%p)::RequestVideoData(%d, %lld)",
              this, aSkipToNextKeyframe, aTimeThreshold);
   if (!mVideoReader) {
     MSE_DEBUG("MediaSourceReader(%p)::RequestVideoData called with no video reader", this);
-    GetCallback()->OnNotDecoded(MediaData::VIDEO_DATA, DECODE_ERROR);
-    return;
+    mVideoPromise.Reject(DECODE_ERROR, __func__);
+    return p;
   }
   if (aSkipToNextKeyframe) {
     mTimeThreshold = aTimeThreshold;
     mDropAudioBeforeThreshold = true;
     mDropVideoBeforeThreshold = true;
   }
   mVideoIsSeeking = false;
   SwitchVideoReader(mLastVideoTime);
-  mVideoReader->RequestVideoData(aSkipToNextKeyframe, aTimeThreshold);
+
+  mVideoReader->RequestVideoData(aSkipToNextKeyframe, aTimeThreshold)
+              ->Then(GetTaskQueue(), __func__, this,
+                     &MediaSourceReader::OnVideoDecoded, &MediaSourceReader::OnVideoNotDecoded);
+  return p;
 }
 
 void
 MediaSourceReader::OnVideoDecoded(VideoData* aSample)
 {
   MSE_DEBUGV("MediaSourceReader(%p)::OnVideoDecoded [mTime=%lld mDuration=%lld mDiscontinuity=%d]",
              this, aSample->mTime, aSample->mDuration, aSample->mDiscontinuity);
   if (mDropVideoBeforeThreshold) {
     if (aSample->mTime < mTimeThreshold) {
       MSE_DEBUG("MediaSourceReader(%p)::OnVideoDecoded mTime=%lld < mTimeThreshold=%lld",
                 this, aSample->mTime, mTimeThreshold);
-      mVideoReader->RequestVideoData(false, 0);
+      mVideoReader->RequestVideoData(false, 0)->Then(GetTaskQueue(), __func__, this,
+                                                     &MediaSourceReader::OnVideoDecoded,
+                                                     &MediaSourceReader::OnVideoNotDecoded);
       return;
     }
     mDropVideoBeforeThreshold = false;
   }
 
   // Any OnVideoDecoded callbacks received while mVideoIsSeeking must be not
   // update our last used timestamp, as these are emitted by the reader we're
   // switching away from.
   if (!mVideoIsSeeking) {
     mLastVideoTime = aSample->mTime + aSample->mDuration;
   }
-  GetCallback()->OnVideoDecoded(aSample);
+
+  mVideoPromise.Resolve(aSample, __func__);
 }
 
 void
-MediaSourceReader::OnNotDecoded(MediaData::Type aType, NotDecodedReason aReason)
+MediaSourceReader::OnVideoNotDecoded(NotDecodedReason aReason)
 {
-  MSE_DEBUG("MediaSourceReader(%p)::OnNotDecoded aType=%u aReason=%u IsEnded: %d", this, aType, aReason, IsEnded());
+  MSE_DEBUG("MediaSourceReader(%p)::OnVideoNotDecoded aReason=%u IsEnded: %d", this, aReason, IsEnded());
   if (aReason == DECODE_ERROR || aReason == CANCELED) {
-    GetCallback()->OnNotDecoded(aType, aReason);
+    mVideoPromise.Reject(aReason, __func__);
     return;
   }
+
   // End of stream. Force switching past this stream to another reader by
   // switching to the end of the buffered range.
   MOZ_ASSERT(aReason == END_OF_STREAM);
-  nsRefPtr<MediaDecoderReader> reader = aType == MediaData::AUDIO_DATA ?
-                                          mAudioReader : mVideoReader;
-
-  // Find the closest approximation to the end time for this stream.
-  // mLast{Audio,Video}Time differs from the actual end time because of
-  // Bug 1065207 - the duration of a WebM fragment is an estimate not the
-  // actual duration. In the case of audio time an example of where they
-  // differ would be the actual sample duration being small but the
-  // previous sample being large. The buffered end time uses that last
-  // sample duration as an estimate of the end time duration giving an end
-  // time that is greater than mLastAudioTime, which is the actual sample
-  // end time.
-  // Reader switching is based on the buffered end time though so they can be
-  // quite different. By using the EOS_FUZZ_US and the buffered end time we
-  // attempt to account for this difference.
-  int64_t* time = aType == MediaData::AUDIO_DATA ? &mLastAudioTime : &mLastVideoTime;
-  if (reader) {
-    nsRefPtr<dom::TimeRanges> ranges = new dom::TimeRanges();
-    reader->GetBuffered(ranges);
-    if (ranges->Length() > 0) {
-      // End time is a double so we convert to nearest by adding 0.5.
-      int64_t end = ranges->GetEndTime() * USECS_PER_S + 0.5;
-      *time = std::max(*time, end);
-    }
+  if (mVideoReader) {
+    AdjustEndTime(&mLastVideoTime, mAudioReader);
   }
 
   // See if we can find a different reader that can pick up where we left off. We use the
   // EOS_FUZZ_US to allow for the fact that our end time can be inaccurate due to bug
-  // 1065207 - the duration of a WebM frame is an estimate.
-  if (aType == MediaData::AUDIO_DATA && SwitchAudioReader(*time + EOS_FUZZ_US)) {
-    RequestAudioData();
-    return;
-  }
-  if (aType == MediaData::VIDEO_DATA && SwitchVideoReader(*time + EOS_FUZZ_US)) {
-    RequestVideoData(false, 0);
+  // 1065207.
+  if (SwitchVideoReader(mLastVideoTime + EOS_FUZZ_US)) {
+    mVideoReader->RequestVideoData(false, 0)
+                ->Then(GetTaskQueue(), __func__, this,
+                       &MediaSourceReader::OnVideoDecoded,
+                       &MediaSourceReader::OnVideoNotDecoded);
     return;
   }
 
   // If the entire MediaSource is done, generate an EndOfStream.
   if (IsEnded()) {
-    GetCallback()->OnNotDecoded(aType, END_OF_STREAM);
+    mVideoPromise.Reject(END_OF_STREAM, __func__);
     return;
   }
 
   // We don't have the data the caller wants. Tell that we're waiting for JS to
   // give us more data.
-  GetCallback()->OnNotDecoded(aType, WAITING_FOR_DATA);
+  mVideoPromise.Reject(WAITING_FOR_DATA, __func__);
 }
 
 nsRefPtr<ShutdownPromise>
 MediaSourceReader::Shutdown()
 {
   MOZ_ASSERT(mMediaSourceShutdownPromise.IsEmpty());
   nsRefPtr<ShutdownPromise> p = mMediaSourceShutdownPromise.Ensure(__func__);
 
@@ -259,16 +316,19 @@ MediaSourceReader::ContinueShutdown(bool
     return;
   }
 
   mAudioTrack = nullptr;
   mAudioReader = nullptr;
   mVideoTrack = nullptr;
   mVideoReader = nullptr;
 
+  MOZ_ASSERT(mAudioPromise.IsEmpty());
+  MOZ_ASSERT(mVideoPromise.IsEmpty());
+
   MediaDecoderReader::Shutdown()->ChainTo(mMediaSourceShutdownPromise.Steal(), __func__);
 }
 
 void
 MediaSourceReader::BreakCycles()
 {
   MediaDecoderReader::BreakCycles();
 
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -41,25 +41,24 @@ public:
   }
 
   // Indicates the point in time at which the reader should consider
   // registered TrackBuffers essential for initialization.
   void PrepareInitialization();
 
   bool IsWaitingMediaResources() MOZ_OVERRIDE;
 
-  void RequestAudioData() MOZ_OVERRIDE;
+  nsRefPtr<AudioDataPromise> RequestAudioData() MOZ_OVERRIDE;
+  nsRefPtr<VideoDataPromise>
+  RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   void OnAudioDecoded(AudioData* aSample);
-
-  void RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) MOZ_OVERRIDE;
-
+  void OnAudioNotDecoded(NotDecodedReason aReason);
   void OnVideoDecoded(VideoData* aSample);
-
-  void OnNotDecoded(MediaData::Type aType, NotDecodedReason aReason);
+  void OnVideoNotDecoded(NotDecodedReason aReason);
 
   void OnSeekCompleted(nsresult aResult);
 
   bool HasVideo() MOZ_OVERRIDE
   {
     return mInfo.HasVideo();
   }
 
@@ -135,16 +134,19 @@ private:
   nsRefPtr<MediaDecoderReader> mVideoReader;
 
   nsTArray<nsRefPtr<TrackBuffer>> mTrackBuffers;
   nsTArray<nsRefPtr<TrackBuffer>> mShutdownTrackBuffers;
   nsTArray<nsRefPtr<TrackBuffer>> mEssentialTrackBuffers;
   nsRefPtr<TrackBuffer> mAudioTrack;
   nsRefPtr<TrackBuffer> mVideoTrack;
 
+  MediaPromiseHolder<AudioDataPromise> mAudioPromise;
+  MediaPromiseHolder<VideoDataPromise> mVideoPromise;
+
 #ifdef MOZ_EME
   nsRefPtr<CDMProxy> mCDMProxy;
 #endif
 
   // These are read and written on the decode task queue threads.
   int64_t mLastAudioTime;
   int64_t mLastVideoTime;
 
--- a/dom/media/omx/MediaCodecReader.cpp
+++ b/dom/media/omx/MediaCodecReader.cpp
@@ -342,16 +342,18 @@ MediaCodecReader::ReleaseMediaResources(
     mAudioTrack.mSourceIsStopped = true;
   }
   ReleaseCriticalResources();
 }
 
 nsRefPtr<ShutdownPromise>
 MediaCodecReader::Shutdown()
 {
+  MOZ_ASSERT(mAudioPromise.IsEmpty());
+  MOZ_ASSERT(mVideoPromise.IsEmpty());
   ReleaseResources();
   return MediaDecoderReader::Shutdown();
 }
 
 void
 MediaCodecReader::DispatchAudioTask()
 {
   if (mAudioTrack.mTaskQueue && mAudioTrack.mTaskQueue->IsEmpty()) {
@@ -369,41 +371,48 @@ MediaCodecReader::DispatchVideoTask(int6
     RefPtr<nsIRunnable> task =
       NS_NewRunnableMethodWithArg<int64_t>(this,
                                            &MediaCodecReader::DecodeVideoFrameTask,
                                            aTimeThreshold);
     mVideoTrack.mTaskQueue->Dispatch(task);
   }
 }
 
-void
+nsRefPtr<MediaDecoderReader::AudioDataPromise>
 MediaCodecReader::RequestAudioData()
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
   MOZ_ASSERT(HasAudio());
+
+  nsRefPtr<AudioDataPromise> p = mAudioPromise.Ensure(__func__);
   if (CheckAudioResources()) {
     DispatchAudioTask();
   }
+
+  return p;
 }
 
-void
+nsRefPtr<MediaDecoderReader::VideoDataPromise>
 MediaCodecReader::RequestVideoData(bool aSkipToNextKeyframe,
                                    int64_t aTimeThreshold)
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
   MOZ_ASSERT(HasVideo());
 
+  nsRefPtr<VideoDataPromise> p = mVideoPromise.Ensure(__func__);
   int64_t threshold = sInvalidTimestampUs;
   if (aSkipToNextKeyframe && IsValidTimestampUs(aTimeThreshold)) {
     mVideoTrack.mTaskQueue->Flush();
     threshold = aTimeThreshold;
   }
   if (CheckVideoResources()) {
     DispatchVideoTask(threshold);
   }
+
+  return p;
 }
 
 bool
 MediaCodecReader::DecodeAudioDataSync()
 {
   if (mAudioTrack.mCodec == nullptr || !mAudioTrack.mCodec->allocated() ||
       mAudioTrack.mOutputEndOfStream) {
     return false;
@@ -479,41 +488,41 @@ MediaCodecReader::DecodeAudioDataTask()
   bool result = DecodeAudioDataSync();
   if (AudioQueue().GetSize() > 0) {
     nsRefPtr<AudioData> a = AudioQueue().PopFront();
     if (a) {
       if (mAudioTrack.mDiscontinuity) {
         a->mDiscontinuity = true;
         mAudioTrack.mDiscontinuity = false;
       }
-      GetCallback()->OnAudioDecoded(a);
+      mAudioPromise.Resolve(a, __func__);
     }
   }
-  if (AudioQueue().AtEndOfStream()) {
-    GetCallback()->OnNotDecoded(MediaData::AUDIO_DATA, END_OF_STREAM);
+  else if (AudioQueue().AtEndOfStream()) {
+    mAudioPromise.Reject(END_OF_STREAM, __func__);
   }
   return result;
 }
 
 bool
 MediaCodecReader::DecodeVideoFrameTask(int64_t aTimeThreshold)
 {
   bool result = DecodeVideoFrameSync(aTimeThreshold);
   if (VideoQueue().GetSize() > 0) {
     nsRefPtr<VideoData> v = VideoQueue().PopFront();
     if (v) {
       if (mVideoTrack.mDiscontinuity) {
         v->mDiscontinuity = true;
         mVideoTrack.mDiscontinuity = false;
       }
-      GetCallback()->OnVideoDecoded(v);
+      mVideoPromise.Resolve(v, __func__);
     }
   }
-  if (VideoQueue().AtEndOfStream()) {
-    GetCallback()->OnNotDecoded(MediaData::VIDEO_DATA, END_OF_STREAM);
+  else if (VideoQueue().AtEndOfStream()) {
+    mVideoPromise.Reject(END_OF_STREAM, __func__);
   }
   return result;
 }
 
 bool
 MediaCodecReader::HasAudio()
 {
   return mInfo.mAudio.mHasAudio;
--- a/dom/media/omx/MediaCodecReader.h
+++ b/dom/media/omx/MediaCodecReader.h
@@ -74,21 +74,22 @@ public:
   // all contents have been continuously parsed. (ex. total duration of some
   // variable-bit-rate MP3 files.)
   virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
 
   // Flush the MediaTaskQueue, flush MediaCodec and raise the mDiscontinuity.
   virtual nsresult ResetDecode() MOZ_OVERRIDE;
 
   // Disptach a DecodeVideoFrameTask to decode video data.
-  virtual void RequestVideoData(bool aSkipToNextKeyframe,
-                                int64_t aTimeThreshold) MOZ_OVERRIDE;
+  virtual nsRefPtr<VideoDataPromise>
+  RequestVideoData(bool aSkipToNextKeyframe,
+                   int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   // Disptach a DecodeAduioDataTask to decode video data.
-  virtual void RequestAudioData() MOZ_OVERRIDE;
+  virtual nsRefPtr<AudioDataPromise> RequestAudioData() MOZ_OVERRIDE;
 
   virtual bool HasAudio();
   virtual bool HasVideo();
 
   virtual void PreReadMetadata() MOZ_OVERRIDE;
   // Read header data for all bitstreams in the file. Fills aInfo with
   // the data required to present the media, and optionally fills *aTags
   // with tag metadata from the file.
@@ -426,16 +427,19 @@ private:
   Mutex mTextureClientIndexesLock;
   nsDataHashtable<nsPtrHashKey<TextureClient>, size_t> mTextureClientIndexes;
 
   // media tracks
   AudioTrack mAudioTrack;
   VideoTrack mVideoTrack;
   AudioTrack mAudioOffloadTrack; // only Track::mSource is valid
 
+  MediaPromiseHolder<AudioDataPromise> mAudioPromise;
+  MediaPromiseHolder<VideoDataPromise> mVideoPromise;
+
   // color converter
   android::I420ColorConverterHelper mColorConverter;
   nsAutoArrayPtr<uint8_t> mColorConverterBuffer;
   size_t mColorConverterBufferSize;
 
   // incremental parser
   Monitor mParserMonitor;
   bool mParseDataFromCache;
--- a/dom/media/omx/RtspMediaCodecReader.cpp
+++ b/dom/media/omx/RtspMediaCodecReader.cpp
@@ -69,29 +69,29 @@ RtspMediaCodecReader::EnsureActive()
   nsIStreamingProtocolController* controller =
     mRtspResource->GetMediaStreamController();
   if (controller) {
     controller->Play();
   }
   mRtspResource->SetSuspend(false);
 }
 
-void
+nsRefPtr<MediaDecoderReader::AudioDataPromise>
 RtspMediaCodecReader::RequestAudioData()
 {
   EnsureActive();
-  MediaCodecReader::RequestAudioData();
+  return MediaCodecReader::RequestAudioData();
 }
 
-void
+nsRefPtr<MediaDecoderReader::VideoDataPromise>
 RtspMediaCodecReader::RequestVideoData(bool aSkipToNextKeyframe,
                                        int64_t aTimeThreshold)
 {
   EnsureActive();
-  MediaCodecReader::RequestVideoData(aSkipToNextKeyframe, aTimeThreshold);
+  return MediaCodecReader::RequestVideoData(aSkipToNextKeyframe, aTimeThreshold);
 }
 
 nsresult
 RtspMediaCodecReader::ReadMetadata(MediaInfo* aInfo,
                                    MetadataTags** aTags)
 {
   mRtspResource->DisablePlayoutDelay();
   EnsureActive();
--- a/dom/media/omx/RtspMediaCodecReader.h
+++ b/dom/media/omx/RtspMediaCodecReader.h
@@ -50,21 +50,22 @@ public:
   // data so the |GetBuffered| function can retrieve useful time ranges.
   virtual nsresult GetBuffered(dom::TimeRanges* aBuffered) MOZ_OVERRIDE {
     return NS_OK;
   }
 
   virtual void SetIdle() MOZ_OVERRIDE;
 
   // Disptach a DecodeVideoFrameTask to decode video data.
-  virtual void RequestVideoData(bool aSkipToNextKeyframe,
-                                int64_t aTimeThreshold) MOZ_OVERRIDE;
+  virtual nsRefPtr<VideoDataPromise>
+  RequestVideoData(bool aSkipToNextKeyframe,
+                   int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   // Disptach a DecodeAudioDataTask to decode audio data.
-  virtual void RequestAudioData() MOZ_OVERRIDE;
+  virtual nsRefPtr<AudioDataPromise> RequestAudioData() MOZ_OVERRIDE;
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) MOZ_OVERRIDE;
 
 private:
   // A pointer to RtspMediaResource for calling the Rtsp specific function.
   // The lifetime of mRtspResource is controlled by MediaDecoder. MediaDecoder
   // holds the MediaDecoderStateMachine and RtspMediaResource.
--- a/dom/media/webaudio/MediaBufferDecoder.cpp
+++ b/dom/media/webaudio/MediaBufferDecoder.cpp
@@ -256,22 +256,20 @@ MediaDecodeTask::Decode()
 
   if (!mDecoderReader->HasAudio()) {
     mDecoderReader->Shutdown();
     ReportFailureOnMainThread(WebAudioDecodeJob::NoAudio);
     return;
   }
 
   MediaQueue<AudioData> audioQueue;
-  nsRefPtr<AudioDecodeRendezvous> barrier(new AudioDecodeRendezvous());
-  mDecoderReader->SetCallback(barrier);
+  nsRefPtr<AudioDecodeRendezvous> barrier(new AudioDecodeRendezvous(mDecoderReader));
   while (1) {
-    mDecoderReader->RequestAudioData();
     nsRefPtr<AudioData> audio;
-    if (NS_FAILED(barrier->Await(audio))) {
+    if (NS_FAILED(barrier->RequestAndWait(audio))) {
       mDecoderReader->Shutdown();
       ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
       return;
     }
     if (!audio) {
       // End of stream.
       break;
     }
--- a/dom/media/webm/WebMReader.cpp
+++ b/dom/media/webm/WebMReader.cpp
@@ -753,17 +753,17 @@ bool WebMReader::DecodeOpus(const unsign
   if (channels > 8) {
     return false;
   }
 
   if (mPaddingDiscarded) {
     // Discard padding should be used only on the final packet, so
     // decoding after a padding discard is invalid.
     LOG(PR_LOG_DEBUG, ("Opus error, discard padding on interstitial packet"));
-    GetCallback()->OnNotDecoded(MediaData::AUDIO_DATA, DECODE_ERROR);
+    mHitAudioDecodeError = true;
     return false;
   }
 
   // Maximum value is 63*2880, so there's no chance of overflow.
   int32_t frames_number = opus_packet_get_nb_frames(aData, aLength);
   if (frames_number <= 0) {
     return false; // Invalid packet header.
   }
@@ -807,30 +807,29 @@ bool WebMReader::DecodeOpus(const unsign
     mSkip -= skipFrames;
   }
 
   int64_t discardPadding = 0;
   (void) nestegg_packet_discard_padding(aPacket, &discardPadding);
   if (discardPadding < 0) {
     // Negative discard padding is invalid.
     LOG(PR_LOG_DEBUG, ("Opus error, negative discard padding"));
-    GetCallback()->OnNotDecoded(MediaData::AUDIO_DATA, DECODE_ERROR);
-    return false;
+    mHitAudioDecodeError = true;
   }
   if (discardPadding > 0) {
     CheckedInt64 discardFrames = UsecsToFrames(discardPadding / NS_PER_USEC,
                                                mInfo.mAudio.mRate);
     if (!discardFrames.isValid()) {
       NS_WARNING("Int overflow in DiscardPadding");
       return false;
     }
     if (discardFrames.value() > frames) {
       // Discarding more than the entire packet is invalid.
       LOG(PR_LOG_DEBUG, ("Opus error, discard padding larger than packet"));
-      GetCallback()->OnNotDecoded(MediaData::AUDIO_DATA, DECODE_ERROR);
+      mHitAudioDecodeError = true;
       return false;
     }
     LOG(PR_LOG_DEBUG, ("Opus decoder discarding %d of %d frames",
                        int32_t(discardFrames.value()), frames));
     // Padding discard is only supposed to happen on the final packet.
     // Record the discard so we can return an error if another packet is
     // decoded.
     mPaddingDiscarded = true;