Bug 1299072: P6. Pass decoding error details to MDSM and relatives. r=jwwang
authorJean-Yves Avenard <jyavenard@mozilla.com>
Sat, 10 Sep 2016 16:48:53 +1000
changeset 313791 933eb4ed527194aebe95fee02b7e01fedef151a0
parent 313790 182713015ae358a09cb516ffd45855bd1be516f2
child 313792 59b96d2ec6db2e11e568e17fa85f3d9c0a5c9972
push id30698
push usercbook@mozilla.com
push dateWed, 14 Sep 2016 10:07:43 +0000
treeherdermozilla-central@501e27643a52 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwwang
bugs1299072
milestone51.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1299072: P6. Pass decoding error details to MDSM and relatives. r=jwwang MozReview-Commit-ID: 4ow2nF6Syz
dom/media/AccurateSeekTask.cpp
dom/media/AccurateSeekTask.h
dom/media/MediaDecoderReader.cpp
dom/media/MediaDecoderReader.h
dom/media/MediaDecoderReaderWrapper.cpp
dom/media/MediaDecoderReaderWrapper.h
dom/media/MediaDecoderStateMachine.cpp
dom/media/MediaDecoderStateMachine.h
dom/media/MediaFormatReader.cpp
dom/media/MediaFormatReader.h
dom/media/NextFrameSeekTask.cpp
dom/media/NextFrameSeekTask.h
dom/media/gtest/TestMediaFormatReader.cpp
dom/media/webaudio/MediaBufferDecoder.cpp
--- a/dom/media/AccurateSeekTask.cpp
+++ b/dom/media/AccurateSeekTask.cpp
@@ -318,66 +318,64 @@ AccurateSeekTask::OnAudioDecoded(MediaDa
     RequestAudioData();
     return;
   }
   MaybeFinishSeek();
 }
 
 void
 AccurateSeekTask::OnNotDecoded(MediaData::Type aType,
-                               MediaDecoderReader::NotDecodedReason aReason)
+                               const MediaResult& aError)
 {
   AssertOwnerThread();
   MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
 
-  SAMPLE_LOG("OnNotDecoded type=%d reason=%u", aType, aReason);
+  SAMPLE_LOG("OnNotDecoded type=%d reason=%u", aType, aError.Code());
 
   // Ignore pending requests from video-only seek.
   if (aType == MediaData::AUDIO_DATA && mTarget.IsVideoOnly()) {
     return;
   }
 
-  if (aReason == MediaDecoderReader::DECODE_ERROR) {
-    // If this is a decode error, delegate to the generic error path.
-    CancelCallbacks();
-    RejectIfExist(__func__);
-    return;
-  }
-
   // If the decoder is waiting for data, we tell it to call us back when the
   // data arrives.
-  if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
+  if (aError == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
     mReader->WaitForData(aType);
     return;
   }
 
-  if (aReason == MediaDecoderReader::CANCELED) {
+  if (aError == NS_ERROR_DOM_MEDIA_CANCELED) {
     if (aType == MediaData::AUDIO_DATA) {
       RequestAudioData();
     } else {
       RequestVideoData();
     }
     return;
   }
 
-  if (aReason == MediaDecoderReader::END_OF_STREAM) {
+  if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
     if (aType == MediaData::AUDIO_DATA) {
       mIsAudioQueueFinished = true;
       mDoneAudioSeeking = true;
     } else {
       mIsVideoQueueFinished = true;
       mDoneVideoSeeking = true;
       if (mFirstVideoFrameAfterSeek) {
         // Hit the end of stream. Move mFirstVideoFrameAfterSeek into
         // mSeekedVideoData so we have something to display after seeking.
         mSeekedVideoData = mFirstVideoFrameAfterSeek.forget();
       }
     }
     MaybeFinishSeek();
+    return;
   }
+
+  // This is a decode error, delegate to the generic error path.
+  CancelCallbacks();
+  RejectIfExist(__func__);
 }
 
 void
 AccurateSeekTask::OnVideoDecoded(MediaData* aVideoSample)
 {
   AssertOwnerThread();
   MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
 
@@ -414,28 +412,28 @@ AccurateSeekTask::SetCallbacks()
   AssertOwnerThread();
 
   mAudioCallback = mReader->AudioCallback().Connect(
     OwnerThread(), [this] (AudioCallbackData aData) {
     if (aData.is<MediaData*>()) {
       OnAudioDecoded(aData.as<MediaData*>());
     } else {
       OnNotDecoded(MediaData::AUDIO_DATA,
-        aData.as<MediaDecoderReader::NotDecodedReason>());
+        aData.as<MediaResult>());
     }
   });
 
   mVideoCallback = mReader->VideoCallback().Connect(
     OwnerThread(), [this] (VideoCallbackData aData) {
     typedef Tuple<MediaData*, TimeStamp> Type;
     if (aData.is<Type>()) {
       OnVideoDecoded(Get<0>(aData.as<Type>()));
     } else {
       OnNotDecoded(MediaData::VIDEO_DATA,
-        aData.as<MediaDecoderReader::NotDecodedReason>());
+        aData.as<MediaResult>());
     }
   });
 
   mAudioWaitCallback = mReader->AudioWaitCallback().Connect(
     OwnerThread(), [this] (WaitCallbackData aData) {
     // Ignore pending requests from video-only seek.
     if (mTarget.IsVideoOnly()) {
       return;
--- a/dom/media/AccurateSeekTask.h
+++ b/dom/media/AccurateSeekTask.h
@@ -45,17 +45,17 @@ private:
   void OnSeekResolved(media::TimeUnit);
 
   void OnSeekRejected(nsresult aResult);
 
   void OnAudioDecoded(MediaData* aAudioSample);
 
   void OnVideoDecoded(MediaData* aVideoSample);
 
-  void OnNotDecoded(MediaData::Type, MediaDecoderReader::NotDecodedReason);
+  void OnNotDecoded(MediaData::Type, const MediaResult&);
 
   void SetCallbacks();
 
   void CancelCallbacks();
 
   void AdjustFastSeekIfNeeded(MediaData* aSample);
 
   /*
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -284,22 +284,22 @@ size_t MediaDecoderReader::SizeOfAudioQu
 {
   return mAudioQueue.GetSize();
 }
 
 nsresult MediaDecoderReader::ResetDecode(TrackSet aTracks)
 {
   if (aTracks.contains(TrackInfo::kVideoTrack)) {
     VideoQueue().Reset();
-    mBaseVideoPromise.RejectIfExists(CANCELED, __func__);
+    mBaseVideoPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   }
 
   if (aTracks.contains(TrackInfo::kAudioTrack)) {
     AudioQueue().Reset();
-    mBaseAudioPromise.RejectIfExists(CANCELED, __func__);
+    mBaseAudioPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   }
 
   return NS_OK;
 }
 
 RefPtr<MediaDecoderReader::MediaDataPromise>
 MediaDecoderReader::DecodeToFirstVideoData()
 {
@@ -318,17 +318,17 @@ MediaDecoderReader::DecodeToFirstVideoDa
     return true;
   }, [self] () -> bool {
     MOZ_ASSERT(self->OnTaskQueue());
     return self->VideoQueue().GetSize();
   })->Then(OwnerThread(), __func__, [self, p] () {
     p->Resolve(self->VideoQueue().PeekFront(), __func__);
   }, [p] () {
     // We don't have a way to differentiate EOS, error, and shutdown here. :-(
-    p->Reject(END_OF_STREAM, __func__);
+    p->Reject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
   });
 
   return p.forget();
 }
 
 void
 MediaDecoderReader::UpdateBuffered()
 {
@@ -451,17 +451,17 @@ MediaDecoderReader::RequestVideoData(boo
       mTaskQueue->Dispatch(task.forget());
       return p;
     }
   }
   if (VideoQueue().GetSize() > 0) {
     RefPtr<VideoData> v = VideoQueue().PopFront();
     mBaseVideoPromise.Resolve(v, __func__);
   } else if (VideoQueue().IsFinished()) {
-    mBaseVideoPromise.Reject(END_OF_STREAM, __func__);
+    mBaseVideoPromise.Reject(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
   } else {
     MOZ_ASSERT(false, "Dropping this promise on the floor");
   }
 
   return p;
 }
 
 RefPtr<MediaDecoderReader::MediaDataPromise>
@@ -483,33 +483,35 @@ MediaDecoderReader::RequestAudioData()
       mTaskQueue->Dispatch(task.forget());
       return p;
     }
   }
   if (AudioQueue().GetSize() > 0) {
     RefPtr<AudioData> a = AudioQueue().PopFront();
     mBaseAudioPromise.Resolve(a, __func__);
   } else if (AudioQueue().IsFinished()) {
-    mBaseAudioPromise.Reject(mHitAudioDecodeError ? DECODE_ERROR : END_OF_STREAM, __func__);
+    mBaseAudioPromise.Reject(mHitAudioDecodeError
+                             ? NS_ERROR_DOM_MEDIA_FATAL_ERR
+                             : NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
     mHitAudioDecodeError = false;
   } else {
     MOZ_ASSERT(false, "Dropping this promise on the floor");
   }
 
   return p;
 }
 
 RefPtr<ShutdownPromise>
 MediaDecoderReader::Shutdown()
 {
   MOZ_ASSERT(OnTaskQueue());
   mShutdown = true;
 
-  mBaseAudioPromise.RejectIfExists(END_OF_STREAM, __func__);
-  mBaseVideoPromise.RejectIfExists(END_OF_STREAM, __func__);
+  mBaseAudioPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
+  mBaseVideoPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
 
   mDataArrivedListener.DisconnectIfExists();
 
   ReleaseResources();
   mDuration.DisconnectIfConnected();
   mBuffered.DisconnectAll();
   mIsSuspended.DisconnectAll();
 
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -8,16 +8,17 @@
 
 #include "mozilla/EnumSet.h"
 #include "mozilla/MozPromise.h"
 #include "nsAutoPtr.h"
 
 #include "AbstractMediaDecoder.h"
 #include "MediaInfo.h"
 #include "MediaData.h"
+#include "MediaResult.h"
 #include "MediaMetadataManager.h"
 #include "MediaQueue.h"
 #include "MediaTimer.h"
 #include "AudioCompactor.h"
 #include "Intervals.h"
 #include "TimeUnits.h"
 #include "SeekTarget.h"
 
@@ -63,29 +64,22 @@ enum class ReadMetadataFailureReason : i
 // be accessed on the decode task queue.
 class MediaDecoderReader {
   friend class ReRequestVideoWithSkipTask;
   friend class ReRequestAudioTask;
 
   static const bool IsExclusive = true;
 
 public:
-  enum NotDecodedReason {
-    END_OF_STREAM,
-    DECODE_ERROR,
-    WAITING_FOR_DATA,
-    CANCELED
-  };
-
   using TrackSet = EnumSet<TrackInfo::TrackType>;
 
   using MetadataPromise =
     MozPromise<RefPtr<MetadataHolder>, ReadMetadataFailureReason, IsExclusive>;
   using MediaDataPromise =
-    MozPromise<RefPtr<MediaData>, NotDecodedReason, IsExclusive>;
+    MozPromise<RefPtr<MediaData>, MediaResult, IsExclusive>;
   using SeekPromise = MozPromise<media::TimeUnit, nsresult, IsExclusive>;
 
   // Note that, conceptually, WaitForData makes sense in a non-exclusive sense.
   // But in the current architecture it's only ever used exclusively (by MDSM),
   // so we mark it that way to verify our assumptions. If you have a use-case
   // for multiple WaitForData consumers, feel free to flip the exclusivity here.
   using WaitForDataPromise =
     MozPromise<MediaData::Type, WaitForDataRejectValue, IsExclusive>;
--- a/dom/media/MediaDecoderReaderWrapper.cpp
+++ b/dom/media/MediaDecoderReaderWrapper.cpp
@@ -71,32 +71,32 @@ public:
     RefPtr<StartTimeRendezvous> self = this;
     AwaitStartTime()->Then(
       mOwnerThread, __func__,
       [p, data, self] () {
         MOZ_ASSERT(self->mOwnerThread->IsCurrentThreadIn());
         p->Resolve(data, __func__);
       },
       [p] () {
-        p->Reject(MediaDecoderReader::CANCELED, __func__);
+        p->Reject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
       });
 
     return p.forget();
   }
 
   template<MediaData::Type SampleType>
-  void FirstSampleRejected(MediaDecoderReader::NotDecodedReason aReason)
+  void FirstSampleRejected(const MediaResult& aError)
   {
     MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
-    if (aReason == MediaDecoderReader::DECODE_ERROR) {
-      mHaveStartTimePromise.RejectIfExists(false, __func__);
-    } else if (aReason == MediaDecoderReader::END_OF_STREAM) {
+    if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
       LOG("StartTimeRendezvous=%p SampleType(%d) Has no samples.",
            this, SampleType);
       MaybeSetChannelStartTime<SampleType>(INT64_MAX);
+    } else if (aError != NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
+      mHaveStartTimePromise.RejectIfExists(false, __func__);
     }
   }
 
   bool HaveStartTime() const
   {
     return mAudioStartTime.isSome() && mVideoStartTime.isSome();
   }
 
@@ -195,19 +195,19 @@ MediaDecoderReaderWrapper::RequestAudioD
 
   RefPtr<MediaDecoderReaderWrapper> self = this;
   mAudioDataRequest.Begin(p->Then(mOwnerThread, __func__,
     [self] (MediaData* aAudioSample) {
       self->mAudioDataRequest.Complete();
       aAudioSample->AdjustForStartTime(self->StartTime().ToMicroseconds());
       self->mAudioCallback.Notify(AsVariant(aAudioSample));
     },
-    [self] (MediaDecoderReader::NotDecodedReason aReason) {
+    [self] (const MediaResult& aError) {
       self->mAudioDataRequest.Complete();
-      self->mAudioCallback.Notify(AsVariant(aReason));
+      self->mAudioCallback.Notify(AsVariant(aError));
     }));
 }
 
 void
 MediaDecoderReaderWrapper::RequestVideoData(bool aSkipToNextKeyframe,
                                             media::TimeUnit aTimeThreshold)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
@@ -235,19 +235,19 @@ MediaDecoderReaderWrapper::RequestVideoD
 
   RefPtr<MediaDecoderReaderWrapper> self = this;
   mVideoDataRequest.Begin(p->Then(mOwnerThread, __func__,
     [self, videoDecodeStartTime] (MediaData* aVideoSample) {
       self->mVideoDataRequest.Complete();
       aVideoSample->AdjustForStartTime(self->StartTime().ToMicroseconds());
       self->mVideoCallback.Notify(AsVariant(MakeTuple(aVideoSample, videoDecodeStartTime)));
     },
-    [self] (MediaDecoderReader::NotDecodedReason aReason) {
+    [self] (const MediaResult& aError) {
       self->mVideoDataRequest.Complete();
-      self->mVideoCallback.Notify(AsVariant(aReason));
+      self->mVideoCallback.Notify(AsVariant(aError));
     }));
 }
 
 bool
 MediaDecoderReaderWrapper::IsRequestingAudioData() const
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   return mAudioDataRequest.Exists();
--- a/dom/media/MediaDecoderReaderWrapper.h
+++ b/dom/media/MediaDecoderReaderWrapper.h
@@ -16,18 +16,18 @@
 #include "MediaEventSource.h"
 
 namespace mozilla {
 
 class StartTimeRendezvous;
 
 typedef MozPromise<bool, bool, /* isExclusive = */ false> HaveStartTimePromise;
 
-typedef Variant<MediaData*, MediaDecoderReader::NotDecodedReason> AudioCallbackData;
-typedef Variant<Tuple<MediaData*, TimeStamp>, MediaDecoderReader::NotDecodedReason> VideoCallbackData;
+typedef Variant<MediaData*, MediaResult> AudioCallbackData;
+typedef Variant<Tuple<MediaData*, TimeStamp>, MediaResult> VideoCallbackData;
 typedef Variant<MediaData::Type, WaitForDataRejectValue> WaitCallbackData;
 
 /**
  * A wrapper around MediaDecoderReader to offset the timestamps of Audio/Video
  * samples by the start time to ensure MDSM can always assume zero start time.
  * It also adjusts the seek target passed to Seek() to ensure correct seek time
  * is passed to the underlying reader.
  */
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -974,66 +974,65 @@ MediaDecoderStateMachine::OnVideoPopped(
   MOZ_ASSERT(OnTaskQueue());
   mPlaybackOffset = std::max(mPlaybackOffset.Ref(), aSample->mOffset);
   UpdateNextFrameStatus();
   DispatchVideoDecodeTaskIfNeeded();
 }
 
 void
 MediaDecoderStateMachine::OnNotDecoded(MediaData::Type aType,
-                                       MediaDecoderReader::NotDecodedReason aReason)
+                                       const MediaResult& aError)
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(mState != DECODER_STATE_SEEKING);
 
-  SAMPLE_LOG("OnNotDecoded (aType=%u, aReason=%u)", aType, aReason);
+  SAMPLE_LOG("OnNotDecoded (aType=%u, aError=%u)", aType, aError.Code());
   bool isAudio = aType == MediaData::AUDIO_DATA;
   MOZ_ASSERT_IF(!isAudio, aType == MediaData::VIDEO_DATA);
 
   if (IsShutdown()) {
     // Already shutdown;
     return;
   }
 
-  // If this is a decode error, delegate to the generic error path.
-  if (aReason == MediaDecoderReader::DECODE_ERROR) {
-    DecodeError();
-    return;
-  }
-
   // If the decoder is waiting for data, we tell it to call us back when the
   // data arrives.
-  if (aReason == MediaDecoderReader::WAITING_FOR_DATA) {
+  if (aError == NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA) {
     MOZ_ASSERT(mReader->IsWaitForDataSupported(),
                "Readers that send WAITING_FOR_DATA need to implement WaitForData");
     mReader->WaitForData(aType);
 
     // We are out of data to decode and will enter buffering mode soon.
     // We want to play the frames we have already decoded, so we stop pre-rolling
     // and ensure that loadeddata is fired as required.
     if (isAudio) {
       StopPrerollingAudio();
     } else {
       StopPrerollingVideo();
     }
     return;
   }
 
-  if (aReason == MediaDecoderReader::CANCELED) {
+  if (aError == NS_ERROR_DOM_MEDIA_CANCELED) {
     if (isAudio) {
       EnsureAudioDecodeTaskQueued();
     } else {
       EnsureVideoDecodeTaskQueued();
     }
     return;
   }
 
+  // If this is a decode error, delegate to the generic error path.
+  if (aError != NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
+    DecodeError();
+    return;
+  }
+
   // This is an EOS. Finish off the queue, and then handle things based on our
   // state.
-  MOZ_ASSERT(aReason == MediaDecoderReader::END_OF_STREAM);
   if (isAudio) {
     AudioQueue().Finish();
     StopPrerollingAudio();
   } else {
     VideoQueue().Finish();
     StopPrerollingVideo();
   }
   switch (mState) {
@@ -1214,28 +1213,28 @@ MediaDecoderStateMachine::SetMediaDecode
 {
   MOZ_ASSERT(OnTaskQueue());
 
   mAudioCallback = mReader->AudioCallback().Connect(
     mTaskQueue, [this] (AudioCallbackData aData) {
     if (aData.is<MediaData*>()) {
       OnAudioDecoded(aData.as<MediaData*>());
     } else {
-      OnNotDecoded(MediaData::AUDIO_DATA, aData.as<MediaDecoderReader::NotDecodedReason>());
+      OnNotDecoded(MediaData::AUDIO_DATA, aData.as<MediaResult>());
     }
   });
 
   mVideoCallback = mReader->VideoCallback().Connect(
     mTaskQueue, [this] (VideoCallbackData aData) {
     typedef Tuple<MediaData*, TimeStamp> Type;
     if (aData.is<Type>()) {
       auto&& v = aData.as<Type>();
       OnVideoDecoded(Get<0>(v), Get<1>(v));
     } else {
-      OnNotDecoded(MediaData::VIDEO_DATA, aData.as<MediaDecoderReader::NotDecodedReason>());
+      OnNotDecoded(MediaData::VIDEO_DATA, aData.as<MediaResult>());
     }
   });
 
   mAudioWaitCallback = mReader->AudioWaitCallback().Connect(
     mTaskQueue, [this] (WaitCallbackData aData) {
     if (aData.is<MediaData::Type>()) {
       EnsureAudioDecodeTaskQueued();
     }
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -339,17 +339,17 @@ private:
   // Returns true if we're currently playing. The decoder monitor must
   // be held.
   bool IsPlaying() const;
 
   // TODO: Those callback function may receive demuxed-only data.
   // Need to figure out a suitable API name for this case.
   void OnAudioDecoded(MediaData* aAudioSample);
   void OnVideoDecoded(MediaData* aVideoSample, TimeStamp aDecodeStartTime);
-  void OnNotDecoded(MediaData::Type aType, MediaDecoderReader::NotDecodedReason aReason);
+  void OnNotDecoded(MediaData::Type aType, const MediaResult& aError);
 
   // Resets all state related to decoding and playback, emptying all buffers
   // and aborting all pending operations on the decode task queue.
   void Reset(TrackSet aTracks = TrackSet(TrackInfo::kAudioTrack,
                                          TrackInfo::kVideoTrack));
 
 protected:
   virtual ~MediaDecoderStateMachine();
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -89,23 +89,23 @@ MediaFormatReader::~MediaFormatReader()
 
 RefPtr<ShutdownPromise>
 MediaFormatReader::Shutdown()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   mDemuxerInitRequest.DisconnectIfExists();
   mMetadataPromise.RejectIfExists(ReadMetadataFailureReason::METADATA_ERROR, __func__);
-  mSeekPromise.RejectIfExists(NS_ERROR_FAILURE, __func__);
+  mSeekPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   mSkipRequest.DisconnectIfExists();
 
   if (mAudio.mDecoder) {
     Reset(TrackInfo::kAudioTrack);
     if (mAudio.HasPromise()) {
-      mAudio.RejectPromise(CANCELED, __func__);
+      mAudio.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
     }
     mAudio.ShutdownDecoder();
   }
   if (mAudio.mTrackDemuxer) {
     mAudio.ResetDemuxer();
     mAudio.mTrackDemuxer->BreakCycles();
     mAudio.mTrackDemuxer = nullptr;
   }
@@ -114,17 +114,17 @@ MediaFormatReader::Shutdown()
     mAudio.mTaskQueue->AwaitShutdownAndIdle();
     mAudio.mTaskQueue = nullptr;
   }
   MOZ_ASSERT(!mAudio.HasPromise());
 
   if (mVideo.mDecoder) {
     Reset(TrackInfo::kVideoTrack);
     if (mVideo.HasPromise()) {
-      mVideo.RejectPromise(CANCELED, __func__);
+      mVideo.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
     }
     mVideo.ShutdownDecoder();
   }
   if (mVideo.mTrackDemuxer) {
     mVideo.ResetDemuxer();
     mVideo.mTrackDemuxer->BreakCycles();
     mVideo.mTrackDemuxer = nullptr;
   }
@@ -529,31 +529,31 @@ MediaFormatReader::RequestVideoData(bool
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise(), "No duplicate sample requests");
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.mSeekRequest.Exists() ||
                         mVideo.mTimeThreshold.isSome());
   MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek");
   LOGV("RequestVideoData(%d, %lld)", aSkipToNextKeyframe, aTimeThreshold);
 
   if (!HasVideo()) {
     LOG("called with no video track");
-    return MediaDataPromise::CreateAndReject(DECODE_ERROR, __func__);
+    return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
   }
 
   if (IsSeeking()) {
     LOG("called mid-seek. Rejecting.");
-    return MediaDataPromise::CreateAndReject(CANCELED, __func__);
+    return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   }
 
   if (mShutdown) {
     NS_WARNING("RequestVideoData on shutdown MediaFormatReader!");
-    return MediaDataPromise::CreateAndReject(CANCELED, __func__);
+    return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   }
 
   if (IsSuspended()) {
-    return MediaDataPromise::CreateAndReject(CANCELED, __func__);
+    return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   }
 
   media::TimeUnit timeThreshold{media::TimeUnit::FromMicroseconds(aTimeThreshold)};
   // Ensure we have no pending seek going as ShouldSkip could return out of date
   // information.
   if (!mVideo.HasInternalSeekPending() &&
       ShouldSkip(aSkipToNextKeyframe, timeThreshold)) {
     RefPtr<MediaDataPromise> p = mVideo.EnsurePromise(__func__);
@@ -589,17 +589,17 @@ MediaFormatReader::OnDemuxFailed(TrackTy
       if (!decoder.mWaitingForData) {
         decoder.mNeedDraining = true;
       }
       NotifyWaitingForData(aTrack);
       break;
     case DemuxerFailureReason::CANCELED: MOZ_FALLTHROUGH;
     case DemuxerFailureReason::SHUTDOWN:
       if (decoder.HasPromise()) {
-        decoder.RejectPromise(CANCELED, __func__);
+        decoder.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
       }
       break;
     default:
       MOZ_ASSERT(false);
       break;
   }
 }
 
@@ -633,31 +633,31 @@ MediaFormatReader::RequestAudioData()
   MOZ_DIAGNOSTIC_ASSERT(IsVideoSeeking() ||
                         !mAudio.mSeekRequest.Exists() ||
                         mAudio.mTimeThreshold.isSome());
   MOZ_DIAGNOSTIC_ASSERT(IsVideoSeeking() || !IsSeeking(), "called mid-seek");
   LOGV("");
 
   if (!HasAudio()) {
     LOG("called with no audio track");
-    return MediaDataPromise::CreateAndReject(DECODE_ERROR, __func__);
+    return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
   }
 
   if (IsSuspended()) {
-    return MediaDataPromise::CreateAndReject(CANCELED, __func__);
+    return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   }
 
   if (IsSeeking()) {
     LOG("called mid-seek. Rejecting.");
-    return MediaDataPromise::CreateAndReject(CANCELED, __func__);
+    return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   }
 
   if (mShutdown) {
     NS_WARNING("RequestAudioData on shutdown MediaFormatReader!");
-    return MediaDataPromise::CreateAndReject(CANCELED, __func__);
+    return MediaDataPromise::CreateAndReject(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
   }
 
   RefPtr<MediaDataPromise> p = mAudio.EnsurePromise(__func__);
   ScheduleUpdate(TrackInfo::kAudioTrack);
 
   return p;
 }
 
@@ -1200,55 +1200,55 @@ MediaFormatReader::Update(TrackType aTra
           mPreviousDecodedKeyframeTime_us = output->mTime;
         }
         nsCString error;
         mVideo.mIsHardwareAccelerated =
           mVideo.mDecoder && mVideo.mDecoder->IsHardwareAccelerated(error);
       }
     } else if (decoder.HasFatalError()) {
       LOG("Rejecting %s promise: DECODE_ERROR", TrackTypeToStr(aTrack));
-      decoder.RejectPromise(DECODE_ERROR, __func__);
+      decoder.RejectPromise(decoder.mError.ref(), __func__);
       return;
     } else if (decoder.mDrainComplete) {
       bool wasDraining = decoder.mDraining;
       decoder.mDrainComplete = false;
       decoder.mDraining = false;
       if (decoder.mDemuxEOS) {
         LOG("Rejecting %s promise: EOS", TrackTypeToStr(aTrack));
-        decoder.RejectPromise(END_OF_STREAM, __func__);
+        decoder.RejectPromise(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
       } else if (decoder.mWaitingForData) {
         if (wasDraining && decoder.mLastSampleTime &&
             !decoder.mNextStreamSourceID) {
           // We have completed draining the decoder following WaitingForData.
           // Set up the internal seek machinery to be able to resume from the
           // last sample decoded.
           LOG("Seeking to last sample time: %lld",
               decoder.mLastSampleTime.ref().mStart.ToMicroseconds());
           InternalSeek(aTrack, InternalSeekTarget(decoder.mLastSampleTime.ref(), true));
         }
         if (!decoder.mReceivedNewData) {
           LOG("Rejecting %s promise: WAITING_FOR_DATA", TrackTypeToStr(aTrack));
-          decoder.RejectPromise(WAITING_FOR_DATA, __func__);
+          decoder.RejectPromise(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__);
         }
       }
       // Now that draining has completed, we check if we have received
       // new data again as the result may now be different from the earlier
       // run.
       if (UpdateReceivedNewData(aTrack) || decoder.mSeekRequest.Exists()) {
         LOGV("Nothing more to do");
         return;
       }
     } else if (decoder.mDemuxEOS && !decoder.mNeedDraining &&
                !decoder.HasPendingDrain() && decoder.mQueuedSamples.IsEmpty()) {
       // It is possible to transition from WAITING_FOR_DATA directly to EOS
       // state during the internal seek; in which case no draining would occur.
       // There is no more samples left to be decoded and we are already in
       // EOS state. We can immediately reject the data promise.
       LOG("Rejecting %s promise: EOS", TrackTypeToStr(aTrack));
-      decoder.RejectPromise(END_OF_STREAM, __func__);
+      decoder.RejectPromise(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
     }
   }
 
   if (decoder.mNeedDraining) {
     DrainDecoder(aTrack);
     return;
   }
 
@@ -1392,25 +1392,25 @@ MediaFormatReader::ResetDecode(TrackSet 
 
   // Reset miscellaneous seeking state.
   mPendingSeekTime.reset();
 
   if (HasVideo() && aTracks.contains(TrackInfo::kVideoTrack)) {
     mVideo.ResetDemuxer();
     Reset(TrackInfo::kVideoTrack);
     if (mVideo.HasPromise()) {
-      mVideo.RejectPromise(CANCELED, __func__);
+      mVideo.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
     }
   }
 
   if (HasAudio() && aTracks.contains(TrackInfo::kAudioTrack)) {
     mAudio.ResetDemuxer();
     Reset(TrackInfo::kAudioTrack);
     if (mAudio.HasPromise()) {
-      mAudio.RejectPromise(CANCELED, __func__);
+      mAudio.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
     }
   }
 
   return MediaDecoderReader::ResetDecode(aTracks);
 }
 
 void
 MediaFormatReader::Output(TrackType aTrack, MediaData* aSample)
@@ -1574,17 +1574,17 @@ MediaFormatReader::OnVideoSkipFailed(Med
       DropDecodedSamples(TrackInfo::kVideoTrack);
       // We can't complete the skip operation, will just service a video frame
       // normally.
       ScheduleUpdate(TrackInfo::kVideoTrack);
       break;
     case DemuxerFailureReason::CANCELED: MOZ_FALLTHROUGH;
     case DemuxerFailureReason::SHUTDOWN:
       if (mVideo.HasPromise()) {
-        mVideo.RejectPromise(CANCELED, __func__);
+        mVideo.RejectPromise(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
       }
       break;
     default:
       NotifyError(TrackType::kVideoTrack);
       break;
   }
 }
 
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -350,17 +350,17 @@ private:
     uint64_t mNumSamplesSkippedTotal;
 
     // These get overridden in the templated concrete class.
     // Indicate if we have a pending promise for decoded frame.
     // Rejecting the promise will stop the reader from decoding ahead.
     virtual bool HasPromise() const = 0;
     virtual RefPtr<MediaDataPromise> EnsurePromise(const char* aMethodName) = 0;
     virtual void ResolvePromise(MediaData* aData, const char* aMethodName) = 0;
-    virtual void RejectPromise(MediaDecoderReader::NotDecodedReason aReason,
+    virtual void RejectPromise(const MediaResult& aError,
                                const char* aMethodName) = 0;
 
     // Clear track demuxer related data.
     void ResetDemuxer()
     {
       mDemuxRequest.DisconnectIfExists();
       mSeekRequest.DisconnectIfExists();
       mTrackDemuxer->Reset();
@@ -458,21 +458,21 @@ private:
 
     void ResolvePromise(MediaData* aData, const char* aMethodName) override
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
       mPromise.Resolve(aData, aMethodName);
       mHasPromise = false;
     }
 
-    void RejectPromise(MediaDecoderReader::NotDecodedReason aReason,
+    void RejectPromise(const MediaResult& aError,
                        const char* aMethodName) override
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
-      mPromise.Reject(aReason, aMethodName);
+      mPromise.Reject(aError, aMethodName);
       mHasPromise = false;
     }
 
   private:
     MozPromiseHolder<MediaDataPromise> mPromise;
     Atomic<bool> mHasPromise;
   };
 
--- a/dom/media/NextFrameSeekTask.cpp
+++ b/dom/media/NextFrameSeekTask.cpp
@@ -178,22 +178,22 @@ NextFrameSeekTask::OnAudioDecoded(MediaD
 
   // We accept any audio data here.
   mSeekedAudioData = aAudioSample;
 
   MaybeFinishSeek();
 }
 
 void
-NextFrameSeekTask::OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
+NextFrameSeekTask::OnAudioNotDecoded(const MediaResult& aError)
 {
   AssertOwnerThread();
   MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
 
-  SAMPLE_LOG("OnAudioNotDecoded (aReason=%u)", aReason);
+  SAMPLE_LOG("OnAudioNotDecoded (aError=%u)", aError.Code());
 
   // We don't really handle audio deocde error here. Let MDSM to trigger further
   // audio decoding tasks if it needs to play audio, and MDSM will then receive
   // the decoding state from MediaDecoderReader.
 
   MaybeFinishSeek();
 }
 
@@ -219,47 +219,47 @@ NextFrameSeekTask::OnVideoDecoded(MediaD
     RequestVideoData();
     return;
   }
 
   MaybeFinishSeek();
 }
 
 void
-NextFrameSeekTask::OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
+NextFrameSeekTask::OnVideoNotDecoded(const MediaResult& aError)
 {
   AssertOwnerThread();
   MOZ_ASSERT(!mSeekTaskPromise.IsEmpty(), "Seek shouldn't be finished");
 
-  SAMPLE_LOG("OnVideoNotDecoded (aReason=%u)", aReason);
+  SAMPLE_LOG("OnVideoNotDecoded (aError=%u)", aError.Code());
 
-  if (aReason == MediaDecoderReader::END_OF_STREAM) {
+  if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
     mIsVideoQueueFinished = true;
   }
 
   // Video seek not finished.
   if (NeedMoreVideo()) {
-    switch (aReason) {
-      case MediaDecoderReader::DECODE_ERROR:
+    switch (aError.Code()) {
+      case NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA:
+        mReader->WaitForData(MediaData::VIDEO_DATA);
+        break;
+      case NS_ERROR_DOM_MEDIA_CANCELED:
+        RequestVideoData();
+        break;
+      case NS_ERROR_DOM_MEDIA_END_OF_STREAM:
+        MOZ_ASSERT(false, "Shouldn't want more data for ended video.");
+        break;
+      default:
         // We might lose the audio sample after canceling the callbacks.
         // However it doesn't really matter because MDSM is gonna shut down
         // when seek fails.
         CancelCallbacks();
         // Reject the promise since we can't finish video seek anyway.
         RejectIfExist(__func__);
         break;
-      case MediaDecoderReader::WAITING_FOR_DATA:
-        mReader->WaitForData(MediaData::VIDEO_DATA);
-        break;
-      case MediaDecoderReader::CANCELED:
-        RequestVideoData();
-        break;
-      case MediaDecoderReader::END_OF_STREAM:
-        MOZ_ASSERT(false, "Shouldn't want more data for ended video.");
-        break;
     }
     return;
   }
 
   MaybeFinishSeek();
 }
 
 void
@@ -269,27 +269,27 @@ NextFrameSeekTask::SetCallbacks()
 
   // Register dummy callbcak for audio decoding since we don't need to handle
   // the decoded audio samples.
   mAudioCallback = mReader->AudioCallback().Connect(
     OwnerThread(), [this] (AudioCallbackData aData) {
     if (aData.is<MediaData*>()) {
       OnAudioDecoded(aData.as<MediaData*>());
     } else {
-      OnAudioNotDecoded(aData.as<MediaDecoderReader::NotDecodedReason>());
+      OnAudioNotDecoded(aData.as<MediaResult>());
     }
   });
 
   mVideoCallback = mReader->VideoCallback().Connect(
     OwnerThread(), [this] (VideoCallbackData aData) {
     typedef Tuple<MediaData*, TimeStamp> Type;
     if (aData.is<Type>()) {
       OnVideoDecoded(Get<0>(aData.as<Type>()));
     } else {
-      OnVideoNotDecoded(aData.as<MediaDecoderReader::NotDecodedReason>());
+      OnVideoNotDecoded(aData.as<MediaResult>());
     }
   });
 
   mAudioWaitCallback = mReader->AudioWaitCallback().Connect(
     OwnerThread(), [this] (WaitCallbackData aData) {
     // We don't make an audio decode request here, instead, let MDSM to
     // trigger further audio decode tasks if MDSM itself needs to play audio.
     MaybeFinishSeek();
--- a/dom/media/NextFrameSeekTask.h
+++ b/dom/media/NextFrameSeekTask.h
@@ -52,21 +52,21 @@ private:
   bool IsAudioSeekComplete() const;
 
   bool IsVideoSeekComplete() const;
 
   void MaybeFinishSeek();
 
   void OnAudioDecoded(MediaData* aAudioSample);
 
-  void OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason);
+  void OnAudioNotDecoded(const MediaResult& aError);
 
   void OnVideoDecoded(MediaData* aVideoSample);
 
-  void OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason);
+  void OnVideoNotDecoded(const MediaResult& aError);
 
   void SetCallbacks();
 
   void CancelCallbacks();
 
   // Update the seek target's time before resolving this seek task, the updated
   // time will be used in the MDSM::SeekCompleted() to update the MDSM's position.
   void UpdateSeekTargetTime();
--- a/dom/media/gtest/TestMediaFormatReader.cpp
+++ b/dom/media/gtest/TestMediaFormatReader.cpp
@@ -102,17 +102,17 @@ public:
 
   void OnVideoRawDataDemuxed(MediaData* aVideoSample)
   {
     EXPECT_TRUE(aVideoSample);
     EXPECT_EQ(MediaData::RAW_DATA, aVideoSample->mType);
     ReaderShutdown();
   }
 
-  void OnNotDemuxed(MediaDecoderReader::NotDecodedReason aReason)
+  void OnNotDemuxed(const MediaResult& aReason)
   {
     EXPECT_TRUE(false);
     ReaderShutdown();
   }
 
   void ReaderShutdown()
   {
     RefPtr<MediaFormatReaderBinding> self = this;
--- a/dom/media/webaudio/MediaBufferDecoder.cpp
+++ b/dom/media/webaudio/MediaBufferDecoder.cpp
@@ -130,17 +130,17 @@ private:
     }
   }
 
   void Decode();
   void OnMetadataRead(MetadataHolder* aMetadata);
   void OnMetadataNotRead(ReadMetadataFailureReason aReason);
   void RequestSample();
   void SampleDecoded(MediaData* aData);
-  void SampleNotDecoded(MediaDecoderReader::NotDecodedReason aReason);
+  void SampleNotDecoded(const MediaResult& aError);
   void FinishDecode();
   void AllocateBuffer();
   void CallbackTheResult();
 
   void Cleanup()
   {
     MOZ_ASSERT(NS_IsMainThread());
     // MediaDecoderReader expects that BufferDecoder is alive.
@@ -332,25 +332,24 @@ MediaDecodeTask::SampleDecoded(MediaData
   if (!mFirstFrameDecoded) {
     mDecoderReader->ReadUpdatedMetadata(&mMediaInfo);
     mFirstFrameDecoded = true;
   }
   RequestSample();
 }
 
 void
-MediaDecodeTask::SampleNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
+MediaDecodeTask::SampleNotDecoded(const MediaResult& aError)
 {
   MOZ_ASSERT(!NS_IsMainThread());
-  if (aReason == MediaDecoderReader::DECODE_ERROR) {
+  if (aError == NS_ERROR_DOM_MEDIA_END_OF_STREAM) {
+    FinishDecode();
+  } else {
     mDecoderReader->Shutdown();
     ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
-  } else {
-    MOZ_ASSERT(aReason == MediaDecoderReader::END_OF_STREAM);
-    FinishDecode();
   }
 }
 
 void
 MediaDecodeTask::FinishDecode()
 {
   mDecoderReader->Shutdown();