Bug 1266027 part 1 - make the MediaDecoderReaderWrapper as a proxy of requesting media data; r=jwwang draft
authorKaku Kuo <tkuo@mozilla.com>
Wed, 27 Apr 2016 14:50:23 +0800
changeset 357711 1686f637362f53c85d6a869c090ed9164cdb9279
parent 357432 6adc822f5e27a55551faeb6c47a9bd8b0859a23b
child 357712 cde91534c481f6f36274fcd6ba9f4637a5c1a1be
push id16820
push usertkuo@mozilla.com
push dateFri, 29 Apr 2016 05:08:17 +0000
reviewersjwwang
bugs1266027
milestone49.0a1
Bug 1266027 part 1 - make the MediaDecoderReaderWrapper as a proxy of requesting media data; r=jwwang MozReview-Commit-ID: CgTBPmtbNfh
dom/media/MediaCallbackID.cpp
dom/media/MediaCallbackID.h
dom/media/MediaDecoderReaderWrapper.cpp
dom/media/MediaDecoderReaderWrapper.h
dom/media/moz.build
xpcom/threads/MozPromise.h
new file mode 100644
--- /dev/null
+++ b/dom/media/MediaCallbackID.cpp
@@ -0,0 +1,50 @@
+#include "MediaCallbackID.h"
+
+namespace mozilla {
+
+char const* CallbackID::INVALID_TAG = "INVALID_TAG";
+int32_t const CallbackID::INVALID_ID = -1;
+
+CallbackID::CallbackID()
+  : mTag(INVALID_TAG), mID(INVALID_ID)
+{
+}
+
+CallbackID::CallbackID(char const* aTag, int32_t aID /* = 0*/)
+  : mTag(aTag), mID(aID)
+{
+}
+
+CallbackID&
+CallbackID::operator++()
+{
+  ++mID;
+  return *this;
+}
+
+CallbackID
+CallbackID::operator++(int)
+{
+  CallbackID ret = *this;
+  ++(*this); // call prefix++
+  return ret;
+}
+
+bool
+CallbackID::operator==(const CallbackID& rhs) const
+{
+  return (strcmp(mTag, rhs.mTag) == 0) && (mID == rhs.mID);
+}
+
+bool
+CallbackID::operator!=(const CallbackID& rhs) const
+{
+  return !(*this == rhs);
+}
+
+CallbackID::operator int() const
+{
+  return mID;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/MediaCallbackID.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#ifndef MediaCallbackID_h_
+#define MediaCallbackID_h_
+
+namespace mozilla {
+
+struct CallbackID
+{
+  static char const* INVALID_TAG;
+  static int32_t const INVALID_ID;
+
+  CallbackID();
+
+  explicit CallbackID(char const* aTag, int32_t aID = 0);
+
+  CallbackID& operator++();   // prefix++
+
+  CallbackID operator++(int); // postfix++
+
+  bool operator==(const CallbackID& rhs) const;
+
+  bool operator!=(const CallbackID& rhs) const;
+
+  operator int() const;
+
+private:
+  char const* mTag;
+  int32_t mID;
+};
+
+} // namespace mozilla
+
+#endif // MediaCallbackID_h_
--- a/dom/media/MediaDecoderReaderWrapper.cpp
+++ b/dom/media/MediaDecoderReaderWrapper.cpp
@@ -139,16 +139,18 @@ private:
 };
 
 MediaDecoderReaderWrapper::MediaDecoderReaderWrapper(bool aIsRealTime,
                                                      AbstractThread* aOwnerThread,
                                                      MediaDecoderReader* aReader)
   : mForceZeroStartTime(aIsRealTime || aReader->ForceZeroStartTime())
   , mOwnerThread(aOwnerThread)
   , mReader(aReader)
+  , mAudioCallbackID("AudioCallbackID")
+  , mVideoCallbackID("VideoCallbackID")
 {}
 
 MediaDecoderReaderWrapper::~MediaDecoderReaderWrapper()
 {}
 
 media::TimeUnit
 MediaDecoderReaderWrapper::StartTime() const
 {
@@ -173,44 +175,76 @@ MediaDecoderReaderWrapper::ReadMetadata(
 RefPtr<HaveStartTimePromise>
 MediaDecoderReaderWrapper::AwaitStartTime()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mShutdown);
   return mStartTimeRendezvous->AwaitStartTime();
 }
 
-RefPtr<MediaDecoderReaderWrapper::MediaDataPromise>
+void
+MediaDecoderReaderWrapper::CancelAudioCallback(CallbackID aID)
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  MOZ_ASSERT(aID == mAudioCallbackID);
+  ++mAudioCallbackID;
+  mRequestAudioDataCB = nullptr;
+}
+
+void
+MediaDecoderReaderWrapper::CancelVideoCallback(CallbackID aID)
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  MOZ_ASSERT(aID == mVideoCallbackID);
+  ++mVideoCallbackID;
+  mRequestVideoDataCB = nullptr;
+}
+
+void
 MediaDecoderReaderWrapper::RequestAudioData()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mShutdown);
+  MOZ_ASSERT(mRequestAudioDataCB, "Request audio data without callback!");
 
   auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
                        &MediaDecoderReader::RequestAudioData);
 
   if (!mStartTimeRendezvous->HaveStartTime()) {
     p = p->Then(mOwnerThread, __func__, mStartTimeRendezvous.get(),
                 &StartTimeRendezvous::ProcessFirstSample<MediaData::AUDIO_DATA>,
                 &StartTimeRendezvous::FirstSampleRejected<MediaData::AUDIO_DATA>)
          ->CompletionPromise();
   }
 
-  return p->Then(mOwnerThread, __func__, this,
-                 &MediaDecoderReaderWrapper::OnSampleDecoded,
-                 &MediaDecoderReaderWrapper::OnNotDecoded)
-          ->CompletionPromise();
+  RefPtr<MediaDecoderReaderWrapper> self = this;
+  mAudioDataRequest.Begin(p->Then(mOwnerThread, __func__,
+    [self] (MediaData* aAudioSample) {
+      MOZ_ASSERT(self->mRequestAudioDataCB);
+      self->mAudioDataRequest.Complete();
+      self->OnSampleDecoded(self->mRequestAudioDataCB.get(), aAudioSample, TimeStamp());
+    },
+    [self] (MediaDecoderReader::NotDecodedReason aReason) {
+      MOZ_ASSERT(self->mRequestAudioDataCB);
+      self->mAudioDataRequest.Complete();
+      self->OnNotDecoded(self->mRequestAudioDataCB.get(), aReason);
+    }));
 }
 
-RefPtr<MediaDecoderReaderWrapper::MediaDataPromise>
+void
 MediaDecoderReaderWrapper::RequestVideoData(bool aSkipToNextKeyframe,
                                             media::TimeUnit aTimeThreshold)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mShutdown);
+  MOZ_ASSERT(mRequestVideoDataCB, "Request video data without callback!");
+
+  // Time the video decode and send this value back to callbacks who accept
+  // a TimeStamp as its second parameter.
+  TimeStamp videoDecodeStartTime = TimeStamp::Now();
 
   if (aTimeThreshold.ToMicroseconds() > 0 &&
       mStartTimeRendezvous->HaveStartTime()) {
     aTimeThreshold += StartTime();
   }
 
   auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
                        &MediaDecoderReader::RequestVideoData,
@@ -218,20 +252,42 @@ MediaDecoderReaderWrapper::RequestVideoD
 
   if (!mStartTimeRendezvous->HaveStartTime()) {
     p = p->Then(mOwnerThread, __func__, mStartTimeRendezvous.get(),
                 &StartTimeRendezvous::ProcessFirstSample<MediaData::VIDEO_DATA>,
                 &StartTimeRendezvous::FirstSampleRejected<MediaData::VIDEO_DATA>)
          ->CompletionPromise();
   }
 
-  return p->Then(mOwnerThread, __func__, this,
-                 &MediaDecoderReaderWrapper::OnSampleDecoded,
-                 &MediaDecoderReaderWrapper::OnNotDecoded)
-          ->CompletionPromise();
+  RefPtr<MediaDecoderReaderWrapper> self = this;
+  mVideoDataRequest.Begin(p->Then(mOwnerThread, __func__,
+    [self, videoDecodeStartTime] (MediaData* aVideoSample) {
+      MOZ_ASSERT(self->mRequestVideoDataCB);
+      self->mVideoDataRequest.Complete();
+      self->OnSampleDecoded(self->mRequestVideoDataCB.get(), aVideoSample, videoDecodeStartTime);
+    },
+    [self] (MediaDecoderReader::NotDecodedReason aReason) {
+      MOZ_ASSERT(self->mRequestVideoDataCB);
+      self->mVideoDataRequest.Complete();
+      self->OnNotDecoded(self->mRequestVideoDataCB.get(), aReason);
+    }));
+}
+
+bool
+MediaDecoderReaderWrapper::IsRequestingAudioData() const
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  return mAudioDataRequest.Exists();
+}
+
+bool
+MediaDecoderReaderWrapper::IsRequestingVidoeData() const
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  return mVideoDataRequest.Exists();
 }
 
 RefPtr<MediaDecoderReader::SeekPromise>
 MediaDecoderReaderWrapper::Seek(SeekTarget aTarget, media::TimeUnit aEndTime)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   aTarget.SetTime(aTarget.GetTime() + StartTime());
   return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
@@ -272,25 +328,34 @@ MediaDecoderReaderWrapper::SetIdle()
     NS_NewRunnableMethod(mReader, &MediaDecoderReader::SetIdle);
   mReader->OwnerThread()->Dispatch(r.forget());
 }
 
 void
 MediaDecoderReaderWrapper::ResetDecode()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+
+  mAudioDataRequest.DisconnectIfExists();
+  mVideoDataRequest.DisconnectIfExists();
+
   nsCOMPtr<nsIRunnable> r =
     NS_NewRunnableMethod(mReader, &MediaDecoderReader::ResetDecode);
   mReader->OwnerThread()->Dispatch(r.forget());
 }
 
 RefPtr<ShutdownPromise>
 MediaDecoderReaderWrapper::Shutdown()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  MOZ_ASSERT(!mRequestAudioDataCB);
+  MOZ_ASSERT(!mRequestVideoDataCB);
+  MOZ_ASSERT(!mAudioDataRequest.Exists());
+  MOZ_ASSERT(!mVideoDataRequest.Exists());
+
   mShutdown = true;
   if (mStartTimeRendezvous) {
     mStartTimeRendezvous->Destroy();
     mStartTimeRendezvous = nullptr;
   }
   return InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
                      &MediaDecoderReader::Shutdown);
 }
@@ -318,17 +383,30 @@ MediaDecoderReaderWrapper::OnMetadataRea
       },
       [] () {
         NS_WARNING("Setting start time on reader failed");
       });
   }
 }
 
 void
-MediaDecoderReaderWrapper::OnSampleDecoded(MediaData* aSample)
+MediaDecoderReaderWrapper::OnSampleDecoded(CallbackBase* aCallback,
+                                           MediaData* aSample,
+                                           TimeStamp aDecodeStartTime)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
-  if (!mShutdown) {
-    aSample->AdjustForStartTime(StartTime().ToMicroseconds());
-  }
+  MOZ_ASSERT(!mShutdown);
+
+  aSample->AdjustForStartTime(StartTime().ToMicroseconds());
+  aCallback->OnResolved(aSample, aDecodeStartTime);
+}
+
+void
+MediaDecoderReaderWrapper::OnNotDecoded(CallbackBase* aCallback,
+                                        MediaDecoderReader::NotDecodedReason aReason)
+{
+  MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+  MOZ_ASSERT(!mShutdown);
+
+  aCallback->OnRejected(aReason);
 }
 
 } // namespace mozilla
--- a/dom/media/MediaDecoderReaderWrapper.h
+++ b/dom/media/MediaDecoderReaderWrapper.h
@@ -7,16 +7,17 @@
 #ifndef MediaDecoderReaderWrapper_h_
 #define MediaDecoderReaderWrapper_h_
 
 #include "mozilla/AbstractThread.h"
 #include "mozilla/RefPtr.h"
 #include "nsISupportsImpl.h"
 
 #include "MediaDecoderReader.h"
+#include "MediaCallbackID.h"
 
 namespace mozilla {
 
 class StartTimeRendezvous;
 
 typedef MozPromise<bool, bool, /* isExclusive = */ false> HaveStartTimePromise;
 
 /**
@@ -28,27 +29,230 @@ typedef MozPromise<bool, bool, /* isExcl
 class MediaDecoderReaderWrapper {
   typedef MediaDecoderReader::MetadataPromise MetadataPromise;
   typedef MediaDecoderReader::MediaDataPromise MediaDataPromise;
   typedef MediaDecoderReader::SeekPromise SeekPromise;
   typedef MediaDecoderReader::WaitForDataPromise WaitForDataPromise;
   typedef MediaDecoderReader::BufferedUpdatePromise BufferedUpdatePromise;
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReaderWrapper);
 
+  /*
+   * Type 1: void(MediaData*)
+   *         void(RefPtr<MediaData>)
+   */
+  template <typename T>
+  class ArgType1CheckHelper {
+    template<typename C, typename... Ts>
+    static TrueType
+    test(void(C::*aMethod)(Ts...),
+         decltype((DeclVal<C>().*aMethod)(DeclVal<MediaData*>()), 0));
+
+    template <typename F>
+    static TrueType
+    test(F&&, decltype(DeclVal<F>()(DeclVal<MediaData*>()), 0));
+
+    static FalseType test(...);
+  public:
+    typedef decltype(test(DeclVal<T>(), 0)) Type;
+  };
+
+  template <typename T>
+  struct ArgType1Check : public ArgType1CheckHelper<T>::Type {};
+
+  /*
+   * Type 2: void(MediaData*, TimeStamp)
+   *         void(RefPtr<MediaData>, TimeStamp)
+   *         void(MediaData*, TimeStamp&)
+   *         void(RefPtr<MediaData>, const TimeStamp&&)
+   */
+  template <typename T>
+  class ArgType2CheckHelper {
+
+    template<typename C, typename... Ts>
+    static TrueType
+    test(void(C::*aMethod)(Ts...),
+         decltype((DeclVal<C>().*aMethod)(DeclVal<MediaData*>(), DeclVal<TimeStamp>()), 0));
+
+    template <typename F>
+    static TrueType
+    test(F&&, decltype(DeclVal<F>()(DeclVal<MediaData*>(), DeclVal<TimeStamp>()), 0));
+
+    static FalseType test(...);
+  public:
+    typedef decltype(test(DeclVal<T>(), 0)) Type;
+  };
+
+  template <typename T>
+  struct ArgType2Check : public ArgType2CheckHelper<T>::Type {};
+
+  struct CallbackBase
+  {
+    virtual ~CallbackBase() {}
+    virtual void OnResolved(MediaData*, TimeStamp) = 0;
+    virtual void OnRejected(MediaDecoderReader::NotDecodedReason) = 0;
+  };
+
+  template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
+  struct MethodCallback : public CallbackBase
+  {
+    MethodCallback(ThisType* aThis, ResolveMethodType aResolveMethod, RejectMethodType aRejectMethod)
+      : mThis(aThis), mResolveMethod(aResolveMethod), mRejectMethod(aRejectMethod)
+    {
+    }
+
+    template<typename F>
+    typename EnableIf<ArgType1Check<F>::value, void>::Type
+    CallHelper(MediaData* aSample, TimeStamp)
+    {
+      (mThis->*mResolveMethod)(aSample);
+    }
+
+    template<typename F>
+    typename EnableIf<ArgType2Check<F>::value, void>::Type
+    CallHelper(MediaData* aSample, TimeStamp aDecodeStartTime)
+    {
+      (mThis->*mResolveMethod)(aSample, aDecodeStartTime);
+    }
+
+    void OnResolved(MediaData* aSample, TimeStamp aDecodeStartTime) override
+    {
+      CallHelper<ResolveMethodType>(aSample, aDecodeStartTime);
+    }
+
+    void OnRejected(MediaDecoderReader::NotDecodedReason aReason) override
+    {
+      (mThis->*mRejectMethod)(aReason);
+    }
+
+    RefPtr<ThisType> mThis;
+    ResolveMethodType mResolveMethod;
+    RejectMethodType mRejectMethod;
+  };
+
+  template<typename ResolveFunctionType, typename RejectFunctionType>
+  struct FunctionCallback : public CallbackBase
+  {
+    FunctionCallback(ResolveFunctionType&& aResolveFuntion, RejectFunctionType&& aRejectFunction)
+      : mResolveFuntion(Move(aResolveFuntion)), mRejectFunction(Move(aRejectFunction))
+    {
+    }
+
+    template<typename F>
+    typename EnableIf<ArgType1Check<F>::value, void>::Type
+    CallHelper(MediaData* aSample, TimeStamp)
+    {
+      mResolveFuntion(aSample);
+    }
+
+    template<typename F>
+    typename EnableIf<ArgType2Check<F>::value, void>::Type
+    CallHelper(MediaData* aSample, TimeStamp aDecodeStartTime)
+    {
+      mResolveFuntion(aSample, aDecodeStartTime);
+    }
+
+    void OnResolved(MediaData* aSample, TimeStamp aDecodeStartTime) override
+    {
+      CallHelper<ResolveFunctionType>(aSample, aDecodeStartTime);
+    }
+
+    void OnRejected(MediaDecoderReader::NotDecodedReason aReason) override
+    {
+      mRejectFunction(aReason);
+    }
+
+    ResolveFunctionType mResolveFuntion;
+    RejectFunctionType mRejectFunction;
+  };
+
 public:
   MediaDecoderReaderWrapper(bool aIsRealTime,
                             AbstractThread* aOwnerThread,
                             MediaDecoderReader* aReader);
 
   media::TimeUnit StartTime() const;
   RefPtr<MetadataPromise> ReadMetadata();
   RefPtr<HaveStartTimePromise> AwaitStartTime();
-  RefPtr<MediaDataPromise> RequestAudioData();
-  RefPtr<MediaDataPromise> RequestVideoData(bool aSkipToNextKeyframe,
-                                            media::TimeUnit aTimeThreshold);
+
+  template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
+  CallbackID
+  SetAudioCallback(ThisType* aThisVal,
+                   ResolveMethodType aResolveMethod,
+                   RejectMethodType aRejectMethod)
+  {
+    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+    MOZ_ASSERT(!mRequestAudioDataCB,
+               "Please cancel the original callback before setting a new one.");
+
+    mRequestAudioDataCB.reset(
+      new MethodCallback<ThisType, ResolveMethodType, RejectMethodType>(
+            aThisVal, aResolveMethod, aRejectMethod));
+
+    return mAudioCallbackID;
+  }
+
+  template<typename ResolveFunction, typename RejectFunction>
+  CallbackID
+  SetAudioCallback(ResolveFunction&& aResolveFunction,
+                   RejectFunction&& aRejectFunction)
+  {
+    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+    MOZ_ASSERT(!mRequestAudioDataCB,
+               "Please cancel the original callback before setting a new one.");
+
+    mRequestAudioDataCB.reset(
+      new FunctionCallback<ResolveFunction, RejectFunction>(
+            Move(aResolveFunction), Move(aRejectFunction)));
+
+    return mAudioCallbackID;
+  }
+
+  template<typename ThisType, typename ResolveMethodType, typename RejectMethodType>
+  CallbackID
+  SetVideoCallback(ThisType* aThisVal,
+                   ResolveMethodType aResolveMethod,
+                   RejectMethodType aRejectMethod)
+  {
+    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+    MOZ_ASSERT(!mRequestVideoDataCB,
+               "Please cancel the original callback before setting a new one.");
+
+    mRequestVideoDataCB.reset(
+      new MethodCallback<ThisType, ResolveMethodType, RejectMethodType>(
+            aThisVal, aResolveMethod, aRejectMethod));
+
+    return mVideoCallbackID;
+  }
+
+  template<typename ResolveFunction, typename RejectFunction>
+  CallbackID
+  SetVideoCallback(ResolveFunction&& aResolveFunction,
+                   RejectFunction&& aRejectFunction)
+  {
+    MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
+    MOZ_ASSERT(!mRequestVideoDataCB,
+               "Please cancel the original callback before setting a new one.");
+
+    mRequestVideoDataCB.reset(
+      new FunctionCallback<ResolveFunction, RejectFunction>(
+            Move(aResolveFunction), Move(aRejectFunction)));
+
+    return mVideoCallbackID;
+  }
+
+  void CancelAudioCallback(CallbackID aID);
+  void CancelVideoCallback(CallbackID aID);
+
+  // NOTE: please set callbacks before requesting audio/video data!
+  void RequestAudioData();
+  void RequestVideoData(bool aSkipToNextKeyframe, media::TimeUnit aTimeThreshold);
+
+  bool IsRequestingAudioData() const;
+  bool IsRequestingVidoeData() const;
+
   RefPtr<SeekPromise> Seek(SeekTarget aTarget, media::TimeUnit aEndTime);
   RefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType);
   RefPtr<BufferedUpdatePromise> UpdateBufferedWithPromise();
   RefPtr<ShutdownPromise> Shutdown();
 
   void ReleaseMediaResources();
   void SetIdle();
   void ResetDecode();
@@ -91,22 +295,35 @@ public:
   void SetCDMProxy(CDMProxy* aProxy) { mReader->SetCDMProxy(aProxy); }
 #endif
 
 private:
   ~MediaDecoderReaderWrapper();
 
   void OnMetadataRead(MetadataHolder* aMetadata);
   void OnMetadataNotRead() {}
-  void OnSampleDecoded(MediaData* aSample);
-  void OnNotDecoded() {}
+  void OnSampleDecoded(CallbackBase* aCallback, MediaData* aSample,
+                       TimeStamp aVideoDecodeStartTime);
+  void OnNotDecoded(CallbackBase* aCallback,
+                    MediaDecoderReader::NotDecodedReason aReason);
 
   const bool mForceZeroStartTime;
   const RefPtr<AbstractThread> mOwnerThread;
   const RefPtr<MediaDecoderReader> mReader;
 
   bool mShutdown = false;
   RefPtr<StartTimeRendezvous> mStartTimeRendezvous;
+
+  UniquePtr<CallbackBase> mRequestAudioDataCB;
+  UniquePtr<CallbackBase> mRequestVideoDataCB;
+  MozPromiseRequestHolder<MediaDataPromise> mAudioDataRequest;
+  MozPromiseRequestHolder<MediaDataPromise> mVideoDataRequest;
+
+  /*
+   * These callback ids are used to prevent mis-canceling callback.
+   */
+  CallbackID mAudioCallbackID;
+  CallbackID mVideoCallbackID;
 };
 
 } // namespace mozilla
 
 #endif // MediaDecoderReaderWrapper_h_
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -103,16 +103,17 @@ EXPORTS += [
     'DOMMediaStream.h',
     'EncodedBufferCache.h',
     'FileBlockCache.h',
     'FlushableTaskQueue.h',
     'FrameStatistics.h',
     'Intervals.h',
     'Latency.h',
     'MediaCache.h',
+    'MediaCallbackID.h',
     'MediaData.h',
     'MediaDataDemuxer.h',
     'MediaDecoder.h',
     'MediaDecoderOwner.h',
     'MediaDecoderReader.h',
     'MediaDecoderStateMachine.h',
     'MediaEventSource.h',
     'MediaFormatReader.h',
@@ -213,16 +214,17 @@ UNIFIED_SOURCES += [
     'DOMMediaStream.cpp',
     'EncodedBufferCache.cpp',
     'FileBlockCache.cpp',
     'FlushableTaskQueue.cpp',
     'GetUserMediaRequest.cpp',
     'GraphDriver.cpp',
     'Latency.cpp',
     'MediaCache.cpp',
+    'MediaCallbackID.cpp',
     'MediaData.cpp',
     'MediaDecoder.cpp',
     'MediaDecoderReader.cpp',
     'MediaDecoderReaderWrapper.cpp',
     'MediaDecoderStateMachine.cpp',
     'MediaDeviceInfo.cpp',
     'MediaDevices.cpp',
     'MediaFormatReader.cpp',
--- a/xpcom/threads/MozPromise.h
+++ b/xpcom/threads/MozPromise.h
@@ -887,17 +887,17 @@ public:
   }
 
   void DisconnectIfExists() {
     if (Exists()) {
       Disconnect();
     }
   }
 
-  bool Exists() { return !!mRequest; }
+  bool Exists() const { return !!mRequest; }
 
 private:
   RefPtr<typename PromiseType::Request> mRequest;
 };
 
 // Asynchronous Potentially-Cross-Thread Method Calls.
 //
 // This machinery allows callers to schedule a promise-returning method to be