Bug 1299072: P4. Return extended failure details to reader. r?cpearce draft
authorJean-Yves Avenard <jyavenard@mozilla.com>
Sat, 10 Sep 2016 09:56:53 +1000
changeset 412869 298a7d07a7fb71102ffd71c71e9eeed69776ac27
parent 412868 9e707c5242f893c7d834cda172fa5bcb85dca5a9
child 412870 3be51f71e4ac8e15d05a567ff932bf3f6ff6a043
push id29276
push userbmo:jyavenard@mozilla.com
push dateTue, 13 Sep 2016 03:29:20 +0000
reviewerscpearce
bugs1299072
milestone51.0a1
Bug 1299072: P4. Return extended failure details to reader. r?cpearce We provide even further details for the GMP decoder. Other decoders to follow. MozReview-Commit-ID: 7NxJPec8xWv
dom/media/Benchmark.cpp
dom/media/Benchmark.h
dom/media/MediaFormatReader.cpp
dom/media/MediaFormatReader.h
dom/media/platforms/PlatformDecoderModule.h
dom/media/platforms/agnostic/BlankDecoderModule.cpp
dom/media/platforms/agnostic/OpusDecoder.cpp
dom/media/platforms/agnostic/TheoraDecoder.cpp
dom/media/platforms/agnostic/VPXDecoder.cpp
dom/media/platforms/agnostic/VorbisDecoder.cpp
dom/media/platforms/agnostic/WAVDecoder.cpp
dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp
dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp
dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h
dom/media/platforms/android/MediaCodecDataDecoder.cpp
dom/media/platforms/android/RemoteDataDecoder.cpp
dom/media/platforms/apple/AppleATDecoder.cpp
dom/media/platforms/apple/AppleVTDecoder.cpp
dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
dom/media/platforms/gonk/GonkMediaDataDecoder.cpp
dom/media/platforms/omx/OmxDataDecoder.cpp
dom/media/platforms/omx/OmxDataDecoder.h
dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
dom/media/platforms/wrappers/FuzzingWrapper.cpp
dom/media/platforms/wrappers/FuzzingWrapper.h
dom/media/platforms/wrappers/H264Converter.cpp
--- a/dom/media/Benchmark.cpp
+++ b/dom/media/Benchmark.cpp
@@ -276,17 +276,17 @@ BenchmarkPlayback::Output(MediaData* aDa
       ref->Dispatch(NS_NewRunnableFunction([ref, decodeFps]() {
         ref->ReturnResult(decodeFps);
       }));
     }
   }));
 }
 
 void
-BenchmarkPlayback::Error(MediaDataDecoderError aError)
+BenchmarkPlayback::Error(const MediaResult& aError)
 {
   RefPtr<Benchmark> ref(mMainThreadState);
   Dispatch(NS_NewRunnableFunction([this, ref]() {  MainThreadShutdown(); }));
 }
 
 void
 BenchmarkPlayback::InputExhausted()
 {
--- a/dom/media/Benchmark.h
+++ b/dom/media/Benchmark.h
@@ -27,17 +27,17 @@ class BenchmarkPlayback : public QueueOb
   void DemuxSamples();
   void DemuxNextSample();
   void MainThreadShutdown();
   void InitDecoder(TrackInfo&& aInfo);
 
   // MediaDataDecoderCallback
   // Those methods are called on the MediaDataDecoder's task queue.
   void Output(MediaData* aData) override;
-  void Error(MediaDataDecoderError aError) override;
+  void Error(const MediaResult& aError) override;
   void InputExhausted() override;
   void DrainComplete() override;
   bool OnReaderTaskQueue() override;
 
   Atomic<Benchmark*> mMainThreadState;
 
   RefPtr<TaskQueue> mDecoderTaskQueue;
   RefPtr<MediaDataDecoder> mDecoder;
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -718,17 +718,17 @@ MediaFormatReader::NotifyDrainComplete(T
     LOG("MediaFormatReader called DrainComplete() before flushing, ignoring.");
     return;
   }
   decoder.mDrainComplete = true;
   ScheduleUpdate(aTrack);
 }
 
 void
-MediaFormatReader::NotifyError(TrackType aTrack, MediaDataDecoderError aError)
+MediaFormatReader::NotifyError(TrackType aTrack, const MediaResult& aError)
 {
   MOZ_ASSERT(OnTaskQueue());
   LOGV("%s Decoding error", TrackTypeToStr(aTrack));
   auto& decoder = GetDecoderData(aTrack);
   decoder.mError = decoder.HasFatalError() ? decoder.mError : Some(aError);
   ScheduleUpdate(aTrack);
 }
 
@@ -1248,17 +1248,17 @@ MediaFormatReader::Update(TrackType aTra
   }
 
   if (decoder.mNeedDraining) {
     DrainDecoder(aTrack);
     return;
   }
 
   if (decoder.mError &&
-      decoder.mError.ref() == MediaDataDecoderError::DECODE_ERROR) {
+      decoder.mError.ref().Code() == NS_ERROR_DOM_MEDIA_DECODE_ERR) {
     decoder.mDecodePending = false;
     decoder.mError.reset();
     if (++decoder.mNumOfConsecutiveError > decoder.mMaxConsecutiveError) {
       NotifyError(aTrack);
       return;
     }
     LOG("%s decoded error count %d", TrackTypeToStr(aTrack),
                                      decoder.mNumOfConsecutiveError);
@@ -1413,17 +1413,17 @@ MediaFormatReader::ResetDecode(TrackSet 
   return MediaDecoderReader::ResetDecode(aTracks);
 }
 
 void
 MediaFormatReader::Output(TrackType aTrack, MediaData* aSample)
 {
   if (!aSample) {
     NS_WARNING("MediaFormatReader::Output() passed a null sample");
-    Error(aTrack);
+    Error(aTrack, MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__));
     return;
   }
 
   LOGV("Decoded %s sample time=%lld timecode=%lld kf=%d dur=%lld",
        TrackTypeToStr(aTrack), aSample->mTime, aSample->mTimecode,
        aSample->mKeyframe, aSample->mDuration);
 
   RefPtr<nsIRunnable> task =
@@ -1446,20 +1446,20 @@ MediaFormatReader::InputExhausted(TrackT
 {
   RefPtr<nsIRunnable> task =
     NewRunnableMethod<TrackType>(
       this, &MediaFormatReader::NotifyInputExhausted, aTrack);
   OwnerThread()->Dispatch(task.forget());
 }
 
 void
-MediaFormatReader::Error(TrackType aTrack, MediaDataDecoderError aError)
+MediaFormatReader::Error(TrackType aTrack, const MediaResult& aError)
 {
   RefPtr<nsIRunnable> task =
-    NewRunnableMethod<TrackType, MediaDataDecoderError>(
+    NewRunnableMethod<TrackType, MediaResult>(
       this, &MediaFormatReader::NotifyError, aTrack, aError);
   OwnerThread()->Dispatch(task.forget());
 }
 
 void
 MediaFormatReader::Reset(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -161,30 +161,31 @@ private:
   // the first sample past the target will be dropped.
   void InternalSeek(TrackType aTrack, const InternalSeekTarget& aTarget);
 
   // Drain the current decoder.
   void DrainDecoder(TrackType aTrack);
   void NotifyNewOutput(TrackType aTrack, MediaData* aSample);
   void NotifyInputExhausted(TrackType aTrack);
   void NotifyDrainComplete(TrackType aTrack);
-  void NotifyError(TrackType aTrack, MediaDataDecoderError aError = MediaDataDecoderError::FATAL_ERROR);
+  void NotifyError(TrackType aTrack,
+                   const MediaResult& aError = MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR));
   void NotifyWaitingForData(TrackType aTrack);
   void NotifyEndOfStream(TrackType aTrack);
 
   void ExtractCryptoInitData(nsTArray<uint8_t>& aInitData);
 
   // Initializes mLayersBackendType if possible.
   void InitLayersBackendType();
 
   // DecoderCallback proxies the MediaDataDecoderCallback calls to these
   // functions.
   void Output(TrackType aType, MediaData* aSample);
   void InputExhausted(TrackType aTrack);
-  void Error(TrackType aTrack, MediaDataDecoderError aError = MediaDataDecoderError::FATAL_ERROR);
+  void Error(TrackType aTrack, const MediaResult& aError);
   void Reset(TrackType aTrack);
   void DrainComplete(TrackType aTrack);
   void DropDecodedSamples(TrackType aTrack);
   void WaitingForKey(TrackType aTrack);
 
   bool ShouldSkip(bool aSkipToNextKeyframe, media::TimeUnit aTimeThreshold);
 
   void SetVideoDecodeThreshold();
@@ -201,17 +202,17 @@ private:
     {
     }
     void Output(MediaData* aSample) override {
       mReader->Output(mType, aSample);
     }
     void InputExhausted() override {
       mReader->InputExhausted(mType);
     }
-    void Error(MediaDataDecoderError aError) override {
+    void Error(const MediaResult& aError) override {
       mReader->Error(mType, aError);
     }
     void DrainComplete() override {
       mReader->DrainComplete(mType);
     }
     void ReleaseMediaResources() override {
       mReader->ReleaseResources();
     }
@@ -322,20 +323,20 @@ private:
     bool HasPendingDrain() const
     {
       return mDraining || mDrainComplete;
     }
 
     uint32_t mNumOfConsecutiveError;
     uint32_t mMaxConsecutiveError;
 
-    Maybe<MediaDataDecoderError> mError;
+    Maybe<MediaResult> mError;
     bool HasFatalError() const
     {
-      return mError.isSome() && mError.ref() == MediaDataDecoderError::FATAL_ERROR;
+      return mError.isSome() && mError.ref() != NS_ERROR_DOM_MEDIA_DECODE_ERR;
     }
 
     // If set, all decoded samples prior mTimeThreshold will be dropped.
     // Used for internal seeking when a change of stream is detected or when
     // encountering data discontinuity.
     Maybe<InternalSeekTarget> mTimeThreshold;
     // Time of last sample returned.
     Maybe<media::TimeInterval> mLastSampleTime;
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -9,16 +9,17 @@
 
 #include "MediaDecoderReader.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/layers/LayersTypes.h"
 #include "nsTArray.h"
 #include "mozilla/RefPtr.h"
 #include "GMPService.h"
 #include <queue>
+#include "MediaResult.h"
 
 namespace mozilla {
 class TrackInfo;
 class AudioInfo;
 class VideoInfo;
 class MediaRawData;
 class DecoderDoctorDiagnostics;
 
@@ -149,34 +150,29 @@ protected:
   // On Windows the task queue's threads in have MSCOM initialized with
   // COINIT_MULTITHREADED.
   // It is safe to store a reference to aConfig.
   // This is called on the decode task queue.
   virtual already_AddRefed<MediaDataDecoder>
   CreateAudioDecoder(const CreateDecoderParams& aParams) = 0;
 };
 
-enum class MediaDataDecoderError : uint8_t{
-  FATAL_ERROR,
-  DECODE_ERROR
-};
-
 // A callback used by MediaDataDecoder to return output/errors to the
 // MediaFormatReader.
 // Implementation is threadsafe, and can be called on any thread.
 class MediaDataDecoderCallback {
 public:
   virtual ~MediaDataDecoderCallback() {}
 
   // Called by MediaDataDecoder when a sample has been decoded.
   virtual void Output(MediaData* aData) = 0;
 
   // Denotes an error in the decoding process. The reader will stop calling
   // the decoder.
-  virtual void Error(MediaDataDecoderError aError) = 0;
+  virtual void Error(const MediaResult& aError) = 0;
 
   // Denotes that the last input sample has been inserted into the decoder,
   // and no more output can be produced unless more input is sent.
   // A frame decoding session is completed once InputExhausted has been called.
   // MediaDataDecoder::Input will not be called again until InputExhausted has
   // been called.
   virtual void InputExhausted() = 0;
 
--- a/dom/media/platforms/agnostic/BlankDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/BlankDecoderModule.cpp
@@ -74,17 +74,17 @@ public:
   {
     return "blank media data decoder";
   }
 
 private:
   void OutputFrame(MediaData* aData)
   {
     if (!aData) {
-      mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+      mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
       return;
     }
 
     // Frames come out in DTS order but we need to output them in PTS order.
     mReorderQueue.Push(aData);
 
     while (mReorderQueue.Length() > mMaxRefFrames) {
       mCallback->Output(mReorderQueue.Pop().get());
--- a/dom/media/platforms/agnostic/OpusDecoder.cpp
+++ b/dom/media/platforms/agnostic/OpusDecoder.cpp
@@ -149,20 +149,22 @@ OpusDataDecoder::ProcessDecode(MediaRawD
 {
   if (mIsFlushing) {
     return;
   }
 
   DecodeError err = DoDecode(aSample);
   switch (err) {
     case DecodeError::FATAL_ERROR:
-      mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+      mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                        __func__));
       return;
     case DecodeError::DECODE_ERROR:
-      mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+      mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+                                        __func__));
       break;
     case DecodeError::DECODE_SUCCESS:
       mCallback->InputExhausted();
       break;
   }
 }
 
 OpusDataDecoder::DecodeError
--- a/dom/media/platforms/agnostic/TheoraDecoder.cpp
+++ b/dom/media/platforms/agnostic/TheoraDecoder.cpp
@@ -194,17 +194,18 @@ TheoraDecoder::DoDecode(MediaRawData* aS
 void
 TheoraDecoder::ProcessDecode(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   if (mIsFlushing) {
     return;
   }
   if (DoDecode(aSample) == -1) {
-    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                      __func__));
   } else {
     mCallback->InputExhausted();
   }
 }
 
 void
 TheoraDecoder::Input(MediaRawData* aSample)
 {
--- a/dom/media/platforms/agnostic/VPXDecoder.cpp
+++ b/dom/media/platforms/agnostic/VPXDecoder.cpp
@@ -186,17 +186,18 @@ VPXDecoder::DoDecode(MediaRawData* aSamp
 void
 VPXDecoder::ProcessDecode(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   if (mIsFlushing) {
     return;
   }
   if (DoDecode(aSample) == -1) {
-    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+                                      __func__));
   } else {
     mCallback->InputExhausted();
   }
 }
 
 void
 VPXDecoder::Input(MediaRawData* aSample)
 {
--- a/dom/media/platforms/agnostic/VorbisDecoder.cpp
+++ b/dom/media/platforms/agnostic/VorbisDecoder.cpp
@@ -133,17 +133,18 @@ VorbisDataDecoder::Input(MediaRawData* a
 void
 VorbisDataDecoder::ProcessDecode(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   if (mIsFlushing) {
     return;
   }
   if (DoDecode(aSample) == -1) {
-    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+                                      __func__));
   } else {
     mCallback->InputExhausted();
   }
 }
 
 int
 VorbisDataDecoder::DoDecode(MediaRawData* aSample)
 {
--- a/dom/media/platforms/agnostic/WAVDecoder.cpp
+++ b/dom/media/platforms/agnostic/WAVDecoder.cpp
@@ -61,17 +61,18 @@ WaveDataDecoder::Init()
 {
   return InitPromise::CreateAndResolve(TrackInfo::kAudioTrack, __func__);
 }
 
 void
 WaveDataDecoder::Input(MediaRawData* aSample)
 {
   if (!DoDecode(aSample)) {
-    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+                                      __func__));
   } else {
     mCallback->InputExhausted();
   }
 }
 
 bool
 WaveDataDecoder::DoDecode(MediaRawData* aSample)
 {
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -88,17 +88,18 @@ public:
 
     if (aDecrypted.mStatus == NoKeyErr) {
       // Key became unusable after we sent the sample to CDM to decrypt.
       // Call Input() again, so that the sample is enqueued for decryption
       // if the key becomes usable again.
       Input(aDecrypted.mSample);
     } else if (aDecrypted.mStatus != Ok) {
       if (mCallback) {
-        mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+        mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                          __func__));
       }
     } else {
       MOZ_ASSERT(!mIsShutdown);
       // The Adobe GMP AAC decoder gets confused if we pass it non-encrypted
       // samples with valid crypto data. So clear the crypto data, since the
       // sample should be decrypted now anyway. If we don't do this and we're
       // using the Adobe GMP for unencrypted decoding of data that is decrypted
       // by gmp-clearkey, decoding will fail.
--- a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "GMPAudioDecoder.h"
 #include "nsServiceManagerUtils.h"
 #include "MediaInfo.h"
 #include "GMPDecoderModule.h"
+#include "nsPrintfCString.h"
 
 namespace mozilla {
 
 #if defined(DEBUG)
 bool IsOnGMPThread()
 {
   nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   MOZ_ASSERT(mps);
@@ -26,56 +27,59 @@ bool IsOnGMPThread()
 
 void
 AudioCallbackAdapter::Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate)
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   if (aRate == 0 || aChannels == 0) {
     NS_WARNING("Invalid rate or num channels returned on GMP audio samples");
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
     return;
   }
 
   size_t numFrames = aPCM.Length() / aChannels;
   MOZ_ASSERT((aPCM.Length() % aChannels) == 0);
   AlignedAudioBuffer audioData(aPCM.Length());
   if (!audioData) {
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
     return;
   }
 
   for (size_t i = 0; i < aPCM.Length(); ++i) {
     audioData[i] = AudioSampleToFloat(aPCM[i]);
   }
 
   if (mMustRecaptureAudioPosition) {
     mAudioFrameSum = 0;
     auto timestamp = UsecsToFrames(aTimeStamp, aRate);
     if (!timestamp.isValid()) {
       NS_WARNING("Invalid timestamp");
-      mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+      mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+                                        __func__));
       return;
     }
     mAudioFrameOffset = timestamp.value();
     mMustRecaptureAudioPosition = false;
   }
 
   auto timestamp = FramesToUsecs(mAudioFrameOffset + mAudioFrameSum, aRate);
   if (!timestamp.isValid()) {
     NS_WARNING("Invalid timestamp on audio samples");
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+      mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+                                        __func__));
     return;
   }
   mAudioFrameSum += numFrames;
 
   auto duration = FramesToUsecs(numFrames, aRate);
   if (!duration.isValid()) {
     NS_WARNING("Invalid duration on audio samples");
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+      mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_OVERFLOW_ERR,
+                                        __func__));
     return;
   }
 
   RefPtr<AudioData> audio(new AudioData(mLastStreamOffset,
                                         timestamp.value(),
                                         duration.value(),
                                         numFrames,
                                         Move(audioData),
@@ -111,24 +115,26 @@ AudioCallbackAdapter::ResetComplete()
   mMustRecaptureAudioPosition = true;
   mCallback->FlushComplete();
 }
 
 void
 AudioCallbackAdapter::Error(GMPErr aErr)
 {
   MOZ_ASSERT(IsOnGMPThread());
-  mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+  mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                    nsPrintfCString("%s: %d", __func__, aErr)));
 }
 
 void
 AudioCallbackAdapter::Terminated()
 {
   NS_WARNING("AAC GMP decoder terminated.");
-  mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+  mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                    __func__));
 }
 
 GMPAudioDecoderParams::GMPAudioDecoderParams(const CreateDecoderParams& aParams)
   : mConfig(aParams.AudioConfig())
   , mTaskQueue(aParams.mTaskQueue)
   , mCallback(nullptr)
   , mAdapter(nullptr)
   , mCrashHelper(aParams.mCrashHelper)
@@ -241,26 +247,28 @@ GMPAudioDecoder::Init()
 
 void
 GMPAudioDecoder::Input(MediaRawData* aSample)
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   RefPtr<MediaRawData> sample(aSample);
   if (!mGMP) {
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
     return;
   }
 
   mAdapter->SetLastStreamOffset(sample->mOffset);
 
   gmp::GMPAudioSamplesImpl samples(sample, mConfig.mChannels, mConfig.mRate);
   nsresult rv = mGMP->Decode(samples);
   if (NS_FAILED(rv)) {
-    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+    mCallback->Error(
+      MediaResult(rv, nsPrintfCString("%s: decode error (%d)",
+                                           __func__, rv)));
   }
 }
 
 void
 GMPAudioDecoder::Flush()
 {
   MOZ_ASSERT(IsOnGMPThread());
 
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
@@ -49,17 +49,17 @@ VideoCallbackAdapter::Decoded(GMPVideoi4
                                  decodedFrame->Duration(),
                                  b,
                                  false,
                                  -1,
                                  pictureRegion);
   if (v) {
     mCallback->Output(v);
   } else {
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
   }
 }
 
 void
 VideoCallbackAdapter::ReceivedDecodedReferenceFrame(const uint64_t aPictureId)
 {
   MOZ_ASSERT(IsOnGMPThread());
 }
@@ -90,25 +90,26 @@ VideoCallbackAdapter::ResetComplete()
   MOZ_ASSERT(IsOnGMPThread());
   mCallback->FlushComplete();
 }
 
 void
 VideoCallbackAdapter::Error(GMPErr aErr)
 {
   MOZ_ASSERT(IsOnGMPThread());
-  mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+  mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                               nsPrintfCString("%s: %d", __func__, aErr)));
 }
 
 void
 VideoCallbackAdapter::Terminated()
 {
   // Note that this *may* be called from the proxy thread also.
   NS_WARNING("GMP decoder terminated.");
-  mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+  mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
 }
 
 GMPVideoDecoderParams::GMPVideoDecoderParams(const CreateDecoderParams& aParams)
   : mConfig(aParams.VideoConfig())
   , mTaskQueue(aParams.mTaskQueue)
   , mCallback(nullptr)
   , mAdapter(nullptr)
   , mImageContainer(aParams.mImageContainer)
@@ -178,24 +179,24 @@ GMPVideoDecoder::GetNodeId()
 }
 
 GMPUniquePtr<GMPVideoEncodedFrame>
 GMPVideoDecoder::CreateFrame(MediaRawData* aSample)
 {
   GMPVideoFrame* ftmp = nullptr;
   GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
   if (GMP_FAILED(err)) {
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
     return nullptr;
   }
 
   GMPUniquePtr<GMPVideoEncodedFrame> frame(static_cast<GMPVideoEncodedFrame*>(ftmp));
   err = frame->CreateEmptyFrame(aSample->Size());
   if (GMP_FAILED(err)) {
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
     return nullptr;
   }
 
   memcpy(frame->Buffer(), aSample->Data(), frame->Size());
 
   // Convert 4-byte NAL unit lengths to host-endian 4-byte buffer lengths to
   // suit the GMP API.
   if (mConvertNALUnitLengths) {
@@ -315,31 +316,32 @@ GMPVideoDecoder::Init()
 
 void
 GMPVideoDecoder::Input(MediaRawData* aSample)
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   RefPtr<MediaRawData> sample(aSample);
   if (!mGMP) {
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                      __func__));
     return;
   }
 
   mAdapter->SetLastStreamOffset(sample->mOffset);
 
   GMPUniquePtr<GMPVideoEncodedFrame> frame = CreateFrame(sample);
   if (!frame) {
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
     return;
   }
   nsTArray<uint8_t> info; // No codec specific per-frame info to pass.
   nsresult rv = mGMP->Decode(Move(frame), false, info, 0);
   if (NS_FAILED(rv)) {
-    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__));
   }
 }
 
 void
 GMPVideoDecoder::Flush()
 {
   MOZ_ASSERT(IsOnGMPThread());
 
--- a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp
+++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.cpp
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "MediaDataDecoderProxy.h"
 #include "MediaData.h"
 
 namespace mozilla {
 
 void
-MediaDataDecoderCallbackProxy::Error(MediaDataDecoderError aError)
+MediaDataDecoderCallbackProxy::Error(const MediaResult& aError)
 {
   mProxyCallback->Error(aError);
 }
 
 void
 MediaDataDecoderCallbackProxy::FlushComplete()
 {
   mProxyDecoder->FlushComplete();
--- a/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h
+++ b/dom/media/platforms/agnostic/gmp/MediaDataDecoderProxy.h
@@ -69,17 +69,17 @@ public:
    , mProxyCallback(aCallback)
   {
   }
 
   void Output(MediaData* aData) override {
     mProxyCallback->Output(aData);
   }
 
-  void Error(MediaDataDecoderError aError) override;
+  void Error(const MediaResult& aError) override;
 
   void InputExhausted() override {
     mProxyCallback->InputExhausted();
   }
 
   void DrainComplete() override {
     mProxyCallback->DrainComplete();
   }
--- a/dom/media/platforms/android/MediaCodecDataDecoder.cpp
+++ b/dom/media/platforms/android/MediaCodecDataDecoder.cpp
@@ -263,17 +263,18 @@ MediaCodecDataDecoder::Init()
                MediaDataDecoder::DecoderFailureReason::INIT_ERROR, __func__);
 }
 
 nsresult
 MediaCodecDataDecoder::InitDecoder(Surface::Param aSurface)
 {
   mDecoder = CreateDecoder(mMimeType);
   if (!mDecoder) {
-    INVOKE_CALLBACK(Error, MediaDataDecoderError::FATAL_ERROR);
+    INVOKE_CALLBACK(Error,
+                    MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
     return NS_ERROR_FAILURE;
   }
 
   nsresult rv;
   NS_ENSURE_SUCCESS(rv = mDecoder->Configure(mFormat, aSurface, nullptr, 0), rv);
   NS_ENSURE_SUCCESS(rv = mDecoder->Start(), rv);
 
   NS_ENSURE_SUCCESS(rv = ResetInputBuffers(), rv);
@@ -290,17 +291,17 @@ static const int64_t kDecoderTimeout = 1
 
 #define BREAK_ON_DECODER_ERROR() \
   if (NS_FAILED(res)) { \
     NS_WARNING("Exiting decoder loop due to exception"); \
     if (mState == ModuleState::kDrainDecoder) { \
       INVOKE_CALLBACK(DrainComplete); \
       SetState(ModuleState::kDecoding); \
     } \
-    INVOKE_CALLBACK(Error, MediaDataDecoderError::FATAL_ERROR); \
+    INVOKE_CALLBACK(Error, MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__)); \
     break; \
   }
 
 nsresult
 MediaCodecDataDecoder::GetInputBuffer(
     JNIEnv* aEnv, int aIndex, jni::Object::LocalRef* aBuffer)
 {
   MOZ_ASSERT(aEnv);
@@ -530,17 +531,19 @@ MediaCodecDataDecoder::DecoderLoop()
     } else if (outputStatus == MediaCodec::INFO_OUTPUT_BUFFERS_CHANGED) {
       res = ResetOutputBuffers();
       BREAK_ON_DECODER_ERROR();
     } else if (outputStatus == MediaCodec::INFO_OUTPUT_FORMAT_CHANGED) {
       res = mDecoder->GetOutputFormat(ReturnTo(&outputFormat));
       BREAK_ON_DECODER_ERROR();
     } else if (outputStatus < 0) {
       NS_WARNING("Unknown error from decoder!");
-      INVOKE_CALLBACK(Error, MediaDataDecoderError::DECODE_ERROR);
+      INVOKE_CALLBACK(Error,
+                      MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR,
+                                       __func__));
       // Don't break here just in case it's recoverable. If it's not, other
       // stuff will fail later and we'll bail out.
     } else {
       // We have a valid buffer index >= 0 here.
       int32_t flags;
       nsresult res = bufferInfo->Flags(&flags);
       BREAK_ON_DECODER_ERROR();
 
--- a/dom/media/platforms/android/RemoteDataDecoder.cpp
+++ b/dom/media/platforms/android/RemoteDataDecoder.cpp
@@ -75,18 +75,18 @@ public:
       HandleOutputFormatChanged(MediaFormat::Ref::From(aFormat));
     }
   }
 
   void OnError(bool aIsFatal)
   {
     if (mDecoderCallback) {
       mDecoderCallback->Error(aIsFatal ?
-        MediaDataDecoderError::FATAL_ERROR :
-        MediaDataDecoderError::DECODE_ERROR);
+        MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__) :
+        MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__));
     }
   }
 
   void DisposeNative()
   {
     // TODO
   }
 
@@ -377,17 +377,18 @@ private:
       }
     }
 
     void HandleOutputFormatChanged(MediaFormat::Param aFormat) override
     {
       aFormat->GetInteger(NS_LITERAL_STRING("channel-count"), &mOutputChannels);
       AudioConfig::ChannelLayout layout(mOutputChannels);
       if (!layout.IsValid()) {
-        mDecoderCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+        mDecoderCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                                 __func__));
         return;
       }
       aFormat->GetInteger(NS_LITERAL_STRING("sample-rate"), &mOutputSampleRate);
       LOG("Audio output format changed: channels:%d sample rate:%d", mOutputChannels, mOutputSampleRate);
     }
 
   private:
     RemoteAudioDecoder* mDecoder;
@@ -471,17 +472,17 @@ RemoteDataDecoder::Input(MediaRawData* a
   env->SetByteArrayRegion(data, 0, length, reinterpret_cast<const jbyte*>(aSample->Data()));
 
   jni::ByteArray::LocalRef bytes(env);
   bytes = jni::Object::LocalRef::Adopt(env, data);
 
   BufferInfo::LocalRef bufferInfo;
   nsresult rv = BufferInfo::New(&bufferInfo);
   if (NS_FAILED(rv)) {
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
     return;
   }
   bufferInfo->Set(0, aSample->Size(), aSample->mTime, 0);
 
   mJavaDecoder->Input(bytes, bufferInfo);
 }
 
 } // mozilla
--- a/dom/media/platforms/apple/AppleATDecoder.cpp
+++ b/dom/media/platforms/apple/AppleATDecoder.cpp
@@ -189,28 +189,30 @@ AppleATDecoder::SubmitSample(MediaRawDat
   if (mIsFlushing) {
     return;
   }
 
   nsresult rv = NS_OK;
   if (!mConverter) {
     rv = SetupDecoder(aSample);
     if (rv != NS_OK && rv != NS_ERROR_NOT_INITIALIZED) {
-      mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+      mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                        __func__));
       return;
     }
   }
 
   mQueuedSamples.AppendElement(aSample);
 
   if (rv == NS_OK) {
     for (size_t i = 0; i < mQueuedSamples.Length(); i++) {
-      if (NS_FAILED(DecodeSample(mQueuedSamples[i]))) {
+      rv = DecodeSample(mQueuedSamples[i]);
+      if (NS_FAILED(rv)) {
         mQueuedSamples.Clear();
-        mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+        mCallback->Error(MediaResult(rv, __func__));
         return;
       }
     }
     mQueuedSamples.Clear();
   }
   mCallback->InputExhausted();
 }
 
@@ -257,17 +259,17 @@ AppleATDecoder::DecodeSample(MediaRawDat
                                                   _PassthroughInputDataCallback,
                                                   &userData,
                                                   &numFrames /* in/out */,
                                                   &decBuffer,
                                                   packets.get());
 
     if (rv && rv != kNoMoreDataErr) {
       LOG("Error decoding audio stream: %d\n", rv);
-      return NS_ERROR_FAILURE;
+      return NS_ERROR_DOM_MEDIA_DECODE_ERR;
     }
 
     if (numFrames) {
       outputData.AppendElements(decoded.get(), numFrames * channels);
     }
 
     if (rv == kNoMoreDataErr) {
       break;
@@ -278,34 +280,34 @@ AppleATDecoder::DecodeSample(MediaRawDat
     return NS_OK;
   }
 
   size_t numFrames = outputData.Length() / channels;
   int rate = mOutputFormat.mSampleRate;
   media::TimeUnit duration = FramesToTimeUnit(numFrames, rate);
   if (!duration.IsValid()) {
     NS_WARNING("Invalid count of accumulated audio samples");
-    return NS_ERROR_FAILURE;
+    return NS_ERROR_DOM_MEDIA_OVERFLOW_ERR;
   }
 
 #ifdef LOG_SAMPLE_DECODE
   LOG("pushed audio at time %lfs; duration %lfs\n",
       (double)aSample->mTime / USECS_PER_S,
       duration.ToSeconds());
 #endif
 
   AudioSampleBuffer data(outputData.Elements(), outputData.Length());
   if (!data.Data()) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   if (mChannelLayout && !mAudioConverter) {
     AudioConfig in(*mChannelLayout.get(), rate);
     AudioConfig out(channels, rate);
     if (!in.IsValid() || !out.IsValid()) {
-      return NS_ERROR_FAILURE;
+      return NS_ERROR_DOM_MEDIA_DECODE_ERR;
     }
     mAudioConverter = MakeUnique<AudioConverter>(in, out);
   }
   if (mAudioConverter) {
     MOZ_ASSERT(mAudioConverter->CanWorkInPlace());
     data = mAudioConverter->Process(Move(data));
   }
 
--- a/dom/media/platforms/apple/AppleVTDecoder.cpp
+++ b/dom/media/platforms/apple/AppleVTDecoder.cpp
@@ -305,18 +305,18 @@ AppleVTDecoder::OutputFrame(CVPixelBuffe
     MOZ_ASSERT(planes == 2, "Likely not NV12 format and it must be.");
 
     VideoData::YCbCrBuffer buffer;
 
     // Lock the returned image data.
     CVReturn rv = CVPixelBufferLockBaseAddress(aImage, kCVPixelBufferLock_ReadOnly);
     if (rv != kCVReturnSuccess) {
       NS_ERROR("error locking pixel data");
-      mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
-      return NS_ERROR_FAILURE;
+      mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
+      return NS_ERROR_OUT_OF_MEMORY;
     }
     // Y plane.
     buffer.mPlanes[0].mData =
       static_cast<uint8_t*>(CVPixelBufferGetBaseAddressOfPlane(aImage, 0));
     buffer.mPlanes[0].mStride = CVPixelBufferGetBytesPerRowOfPlane(aImage, 0);
     buffer.mPlanes[0].mWidth = width;
     buffer.mPlanes[0].mHeight = height;
     buffer.mPlanes[0].mOffset = 0;
@@ -371,18 +371,18 @@ AppleVTDecoder::OutputFrame(CVPixelBuffe
                                  visible);
 #else
     MOZ_ASSERT_UNREACHABLE("No MacIOSurface on iOS");
 #endif
   }
 
   if (!data) {
     NS_ERROR("Couldn't create VideoData for frame");
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
-    return NS_ERROR_FAILURE;
+    mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
+    return NS_ERROR_OUT_OF_MEMORY;
   }
 
   // Frames come out in DTS order but we need to output them
   // in composition order.
   MonitorAutoLock mon(mMonitor);
   mReorderQueue.Push(data);
   if (mReorderQueue.Length() > mMaxRefFrames) {
     mCallback->Output(mReorderQueue.Pop().get());
@@ -441,37 +441,39 @@ AppleVTDecoder::DoDecode(MediaRawData* a
                                           kCFAllocatorNull, // Block allocator.
                                           NULL, // Block source.
                                           0,    // Data offset.
                                           aSample->Size(),
                                           false,
                                           block.receive());
   if (rv != noErr) {
     NS_ERROR("Couldn't create CMBlockBuffer");
-    return NS_ERROR_FAILURE;
+    mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
+    return NS_ERROR_OUT_OF_MEMORY;
   }
   CMSampleTimingInfo timestamp = TimingInfoFromSample(aSample);
   rv = CMSampleBufferCreate(kCFAllocatorDefault, block, true, 0, 0, mFormat, 1, 1, &timestamp, 0, NULL, sample.receive());
   if (rv != noErr) {
     NS_ERROR("Couldn't create CMSampleBuffer");
-    return NS_ERROR_FAILURE;
+    mCallback->Error(MediaResult(NS_ERROR_OUT_OF_MEMORY, __func__));
+    return NS_ERROR_OUT_OF_MEMORY;
   }
 
   VTDecodeFrameFlags decodeFlags =
     kVTDecodeFrame_EnableAsynchronousDecompression;
   rv = VTDecompressionSessionDecodeFrame(mSession,
                                          sample,
                                          decodeFlags,
                                          CreateAppleFrameRef(aSample),
                                          &infoFlags);
   if (rv != noErr && !(infoFlags & kVTDecodeInfo_FrameDropped)) {
     LOG("AppleVTDecoder: Error %d VTDecompressionSessionDecodeFrame", rv);
     NS_WARNING("Couldn't pass frame to decoder");
-    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
-    return NS_ERROR_FAILURE;
+    mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__));
+    return NS_ERROR_DOM_MEDIA_DECODE_ERR;
   }
 
   return NS_OK;
 }
 
 nsresult
 AppleVTDecoder::InitializeSession()
 {
--- a/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegDataDecoder.cpp
@@ -106,20 +106,20 @@ void
 FFmpegDataDecoder<LIBAV_VER>::ProcessDecode(MediaRawData* aSample)
 {
   MOZ_ASSERT(mTaskQueue->IsCurrentThreadIn());
   if (mIsFlushing) {
     return;
   }
   switch (DoDecode(aSample)) {
     case DecodeResult::DECODE_ERROR:
-      mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+      mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__));
       break;
     case DecodeResult::FATAL_ERROR:
-      mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+      mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
       break;
     case DecodeResult::DECODE_NO_FRAME:
     case DecodeResult::DECODE_FRAME:
       mCallback->InputExhausted();
       break;
     default:
       break;
   }
--- a/dom/media/platforms/gonk/GonkMediaDataDecoder.cpp
+++ b/dom/media/platforms/gonk/GonkMediaDataDecoder.cpp
@@ -170,31 +170,33 @@ GonkDecoderManager::ProcessInput(bool aE
         mToDo->setInt32("input-eos", 1);
       }
       mDecoder->requestActivityNotification(mToDo);
     } else if (aEndOfStream) {
       mToDo->setInt32("input-eos", 1);
     }
   } else {
     GMDD_LOG("input processed: error#%d", rv);
-    mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                            __func__));
   }
 }
 
 void
 GonkDecoderManager::ProcessFlush()
 {
   MOZ_ASSERT(OnTaskLooper());
 
   mLastTime = INT64_MIN;
   MonitorAutoLock lock(mFlushMonitor);
   mWaitOutput.Clear();
   if (mDecoder->flush() != OK) {
     GMDD_LOG("flush error");
-    mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                            __func__));
   }
   mIsFlushing = false;
   lock.NotifyAll();
 }
 
 // Use output timestamp to determine which output buffer is already returned
 // and remove corresponding info, except for EOS, from the waiting list.
 // This method handles the cases that audio decoder sends multiple output
@@ -220,17 +222,18 @@ void
 GonkDecoderManager::ProcessToDo(bool aEndOfStream)
 {
   MOZ_ASSERT(OnTaskLooper());
 
   MOZ_ASSERT(mToDo.get() != nullptr);
   mToDo.clear();
 
   if (NumQueuedSamples() > 0 && ProcessQueuedSamples() < 0) {
-    mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                            __func__));
     return;
   }
 
   while (mWaitOutput.Length() > 0) {
     RefPtr<MediaData> output;
     WaitOutputInfo wait = mWaitOutput.ElementAt(0);
     nsresult rv = Output(wait.mOffset, output);
     if (rv == NS_OK) {
@@ -247,17 +250,18 @@ GonkDecoderManager::ProcessToDo(bool aEn
       MOZ_ASSERT(mWaitOutput.Length() == 1);
       mWaitOutput.RemoveElementAt(0);
       mDecodeCallback->DrainComplete();
       ResetEOS();
       return;
     } else if (rv == NS_ERROR_NOT_AVAILABLE) {
       break;
     } else {
-      mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+      mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                              __func__));
       return;
     }
   }
 
   if (!aEndOfStream && NumQueuedSamples() <= MIN_QUEUED_SAMPLES) {
     mDecodeCallback->InputExhausted();
     // No need to shedule todo task this time because InputExhausted() will
     // cause Input() to be invoked and do it for us.
@@ -275,17 +279,18 @@ GonkDecoderManager::ProcessToDo(bool aEn
 
 void
 GonkDecoderManager::ResetEOS()
 {
   // After eos, android::MediaCodec needs to be flushed to receive next input
   mWaitOutput.Clear();
   if (mDecoder->flush() != OK) {
     GMDD_LOG("flush error");
-    mDecodeCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    mDecodeCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                            __func__));
   }
 }
 
 void
 GonkDecoderManager::onMessageReceived(const sp<AMessage> &aMessage)
 {
   switch (aMessage->what()) {
     case kNotifyProcessInput:
--- a/dom/media/platforms/omx/OmxDataDecoder.cpp
+++ b/dom/media/platforms/omx/OmxDataDecoder.cpp
@@ -425,19 +425,19 @@ OmxDataDecoder::EmptyBufferDone(BufferDa
 
 void
 OmxDataDecoder::EmptyBufferFailure(OmxBufferFailureHolder aFailureHolder)
 {
   NotifyError(aFailureHolder.mError, __func__);
 }
 
 void
-OmxDataDecoder::NotifyError(OMX_ERRORTYPE aOmxError, const char* aLine, MediaDataDecoderError aError)
+OmxDataDecoder::NotifyError(OMX_ERRORTYPE aOmxError, const char* aLine, const MediaResult& aError)
 {
-  LOG("NotifyError %d (%d) at %s", aOmxError, aError, aLine);
+  LOG("NotifyError %d (%d) at %s", aOmxError, aError.Code(), aLine);
   mCallback->Error(aError);
 }
 
 void
 OmxDataDecoder::FillAndEmptyBuffers()
 {
   MOZ_ASSERT(mOmxTaskQueue->IsCurrentThreadIn());
   MOZ_ASSERT(mOmxState == OMX_StateExecuting);
@@ -685,17 +685,18 @@ OmxDataDecoder::Event(OMX_EVENTTYPE aEve
       }
       LOG("Got OMX_EventPortSettingsChanged event");
       break;
     }
     default:
     {
       // Got error during decoding, send msg to MFR skipping to next key frame.
       if (aEvent == OMX_EventError && mOmxState == OMX_StateExecuting) {
-        NotifyError((OMX_ERRORTYPE)aData1, __func__, MediaDataDecoderError::DECODE_ERROR);
+        NotifyError((OMX_ERRORTYPE)aData1, __func__,
+                    MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__));
         return true;
       }
       LOG("WARNING: got none handle event: %d, aData1: %d, aData2: %d",
           aEvent, aData1, aData2);
       return false;
     }
   }
 
--- a/dom/media/platforms/omx/OmxDataDecoder.h
+++ b/dom/media/platforms/omx/OmxDataDecoder.h
@@ -98,17 +98,17 @@ protected:
   void FillBufferFailure(OmxBufferFailureHolder aFailureHolder);
 
   void EmptyBufferDone(BufferData* aData);
 
   void EmptyBufferFailure(OmxBufferFailureHolder aFailureHolder);
 
   void NotifyError(OMX_ERRORTYPE aOmxError,
                    const char* aLine,
-                   MediaDataDecoderError aError = MediaDataDecoderError::FATAL_ERROR);
+                   const MediaResult& aError = MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR));
 
   // Configure audio/video codec.
   // Some codec may just ignore this and rely on codec specific data in
   // FillCodecConfigDataToOmx().
   void ConfigCodec();
 
   // Sending codec specific data to OMX component. OMX component could send a
   // OMX_EventPortSettingsChanged back to client. And then client needs to
--- a/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
+++ b/dom/media/platforms/wmf/WMFMediaDataDecoder.cpp
@@ -118,17 +118,17 @@ WMFMediaDataDecoder::ProcessDecode(Media
   if (mIsFlushing) {
     // Skip sample, to be released by runnable.
     return;
   }
 
   HRESULT hr = mMFTManager->Input(aSample);
   if (FAILED(hr)) {
     NS_WARNING("MFTManager rejected sample");
-    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__));
     if (!mRecordedError) {
       SendTelemetry(hr);
       mRecordedError = true;
     }
     return;
   }
 
   mLastStreamOffset = aSample->mOffset;
@@ -145,17 +145,17 @@ WMFMediaDataDecoder::ProcessOutput()
          output) {
     mHasSuccessfulOutput = true;
     mCallback->Output(output);
   }
   if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
     mCallback->InputExhausted();
   } else if (FAILED(hr)) {
     NS_WARNING("WMFMediaDataDecoder failed to output data");
-    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_DECODE_ERR, __func__));
     if (!mRecordedError) {
       SendTelemetry(hr);
       mRecordedError = true;
     }
   }
 }
 
 void
--- a/dom/media/platforms/wrappers/FuzzingWrapper.cpp
+++ b/dom/media/platforms/wrappers/FuzzingWrapper.cpp
@@ -166,29 +166,27 @@ DecoderCallbackFuzzingWrapper::Output(Me
   }
 
   // Passing the data straight through, no need to dispatch to another queue,
   // callback should deal with that.
   mCallback->Output(aData);
 }
 
 void
-DecoderCallbackFuzzingWrapper::Error(MediaDataDecoderError aError)
+DecoderCallbackFuzzingWrapper::Error(const MediaResult& aError)
 {
   if (!mTaskQueue->IsCurrentThreadIn()) {
-    mTaskQueue->Dispatch(
-      NewRunnableMethod<MediaDataDecoderError>(this,
-                                               &DecoderCallbackFuzzingWrapper::Error,
-                                               aError));
+    mTaskQueue->Dispatch(NewRunnableMethod<MediaResult>(
+      this, &DecoderCallbackFuzzingWrapper::Error, aError));
     return;
   }
   CFW_LOGV("");
   MOZ_ASSERT(mCallback);
   ClearDelayedOutput();
-  mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+  mCallback->Error(aError);
 }
 
 void
 DecoderCallbackFuzzingWrapper::InputExhausted()
 {
   if (!mTaskQueue->IsCurrentThreadIn()) {
     mTaskQueue->Dispatch(NewRunnableMethod(this, &DecoderCallbackFuzzingWrapper::InputExhausted));
     return;
--- a/dom/media/platforms/wrappers/FuzzingWrapper.h
+++ b/dom/media/platforms/wrappers/FuzzingWrapper.h
@@ -55,17 +55,17 @@ public:
   // in lots of frames being decoded and queued for delayed output!
   void SetDontDelayInputExhausted(bool aDontDelayInputExhausted);
 
 private:
   virtual ~DecoderCallbackFuzzingWrapper();
 
   // MediaDataDecoderCallback implementation.
   void Output(MediaData* aData) override;
-  void Error(MediaDataDecoderError aError) override;
+  void Error(const MediaResult& aError) override;
   void InputExhausted() override;
   void DrainComplete() override;
   void ReleaseMediaResources() override;
   bool OnReaderTaskQueue() override;
 
   MediaDataDecoderCallback* mCallback;
 
   // Settings for minimum frame output interval & InputExhausted,
--- a/dom/media/platforms/wrappers/H264Converter.cpp
+++ b/dom/media/platforms/wrappers/H264Converter.cpp
@@ -50,17 +50,17 @@ H264Converter::Init()
 }
 
 void
 H264Converter::Input(MediaRawData* aSample)
 {
   if (!mp4_demuxer::AnnexB::ConvertSampleToAVCC(aSample)) {
     // We need AVCC content to be able to later parse the SPS.
     // This is a no-op if the data is already AVCC.
-    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
     return;
   }
 
   if (mInitPromiseRequest.Exists()) {
     if (mNeedKeyframe) {
       if (!aSample->mKeyframe) {
         // Frames dropped, we need a new one.
         mCallback->InputExhausted();
@@ -83,28 +83,28 @@ H264Converter::Input(MediaRawData* aSamp
       // Ignore for the time being, the MediaRawData will be dropped.
       mCallback->InputExhausted();
       return;
     }
   } else {
     rv = CheckForSPSChange(aSample);
   }
   if (NS_FAILED(rv)) {
-    mCallback->Error(MediaDataDecoderError::DECODE_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
     return;
   }
 
   if (mNeedKeyframe && !aSample->mKeyframe) {
     mCallback->InputExhausted();
     return;
   }
 
   if (!mNeedAVCC &&
       !mp4_demuxer::AnnexB::ConvertSampleToAnnexB(aSample)) {
-    mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+    mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__));
     return;
   }
 
   mNeedKeyframe = false;
 
   aSample->mExtraData = mCurrentConfig.mExtraData;
 
   mDecoder->Input(aSample);
@@ -247,17 +247,18 @@ H264Converter::OnDecoderInitDone(const T
   }
   mMediaRawSamples.Clear();
 }
 
 void
 H264Converter::OnDecoderInitFailed(MediaDataDecoder::DecoderFailureReason aReason)
 {
   mInitPromiseRequest.Complete();
-  mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
+  mCallback->Error(MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                                    __func__));
 }
 
 nsresult
 H264Converter::CheckForSPSChange(MediaRawData* aSample)
 {
   RefPtr<MediaByteBuffer> extra_data =
     mp4_demuxer::AnnexB::ExtractExtraData(aSample);
   if (!mp4_demuxer::AnnexB::HasSPS(extra_data) ||