Bug 1319987: P7. Re-implement handling for WaitingForKey in MFR. r?cpearce draft
authorJean-Yves Avenard <jyavenard@mozilla.com>
Fri, 27 Jan 2017 11:48:00 +0100
changeset 479124 2d4d6cc26d0e744f9b586e066883238ee4cfa50b
parent 479123 93a6f7f700c5a83d086e08904aaaa5144e038ab5
child 479125 32bf0abda6358317b332fe7c727adc17012c22e5
push id44146
push userbmo:jyavenard@mozilla.com
push dateSun, 05 Feb 2017 21:13:07 +0000
reviewerscpearce
bugs1319987
milestone54.0a1
Bug 1319987: P7. Re-implement handling for WaitingForKey in MFR. r?cpearce MozReview-Commit-ID: FCiAO54oiH3
dom/media/MediaDecoderReader.h
dom/media/MediaFormatReader.cpp
dom/media/MediaFormatReader.h
dom/media/platforms/PlatformDecoderModule.h
dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h
dom/media/platforms/wrappers/H264Converter.cpp
dom/media/platforms/wrappers/H264Converter.h
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -238,16 +238,28 @@ public:
     return mTimedMetadataEvent;
   }
 
   MediaEventProducer<void>& MediaNotSeekableProducer()
   {
     return mOnMediaNotSeekable;
   }
 
+  // Notified if the reader can't decode a sample due to a missing decryption
+  // key.
+  MediaEventSource<TrackInfo::TrackType>& OnTrackWaitingForKey()
+  {
+    return mOnTrackWaitingForKey;
+  }
+
+  MediaEventProducer<TrackInfo::TrackType>& OnTrackWaitingForKeyProducer()
+  {
+    return mOnTrackWaitingForKey;
+  }
+
   // Switch the video decoder to BlankDecoderModule. It might takes effective
   // since a few samples later depends on how much demuxed samples are already
   // queued in the original video decoder.
   virtual void SetVideoBlankDecode(bool aIsBlankDecode) {}
 
 protected:
   virtual ~MediaDecoderReader();
 
@@ -301,16 +313,19 @@ protected:
   bool mShutdown;
 
   // Used to send TimedMetadata to the listener.
   TimedMetadataEventProducer mTimedMetadataEvent;
 
   // Notify if this media is not seekable.
   MediaEventProducer<void> mOnMediaNotSeekable;
 
+  // Notify if we are waiting for a decryption key.
+  MediaEventProducer<TrackInfo::TrackType> mOnTrackWaitingForKey;
+
 private:
   virtual nsresult InitInternal() { return NS_OK; }
 
   // Does any spinup that needs to happen on this task queue. This runs on a
   // different thread than Init, and there should not be ordering dependencies
   // between the two (even though in practice, Init will always run first right
   // now thanks to the tail dispatcher).
   void InitializationTask();
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -384,34 +384,38 @@ MediaFormatReader::DecoderFactory::DoCre
     case TrackInfo::kAudioTrack: {
       data.mDecoder = mOwner->mPlatform->CreateDecoder({
         ownerData.mInfo
         ? *ownerData.mInfo->GetAsAudioInfo()
         : *ownerData.mOriginalInfo->GetAsAudioInfo(),
         ownerData.mTaskQueue,
         mOwner->mCrashHelper,
         ownerData.mIsBlankDecode,
-        &result
+        &result,
+        aTrack,
+        &mOwner->OnTrackWaitingForKeyProducer()
       });
       break;
     }
 
     case TrackType::kVideoTrack: {
       // Decoders use the layers backend to decide if they can use hardware decoding,
       // so specify LAYERS_NONE if we want to forcibly disable it.
       data.mDecoder = mOwner->mPlatform->CreateDecoder({
         ownerData.mInfo
         ? *ownerData.mInfo->GetAsVideoInfo()
         : *ownerData.mOriginalInfo->GetAsVideoInfo(),
         ownerData.mTaskQueue,
         mOwner->mKnowsCompositor,
         mOwner->GetImageContainer(),
         mOwner->mCrashHelper,
         ownerData.mIsBlankDecode,
-        &result
+        &result,
+        aTrack,
+        &mOwner->OnTrackWaitingForKeyProducer()
       });
       break;
     }
 
     default:
       break;
   }
 
@@ -842,16 +846,18 @@ MediaFormatReader::MediaFormatReader(Abs
   MOZ_ASSERT(aDemuxer);
   MOZ_COUNT_CTOR(MediaFormatReader);
 
   if (aDecoder && aDecoder->CompositorUpdatedEvent()) {
     mCompositorUpdatedListener =
       aDecoder->CompositorUpdatedEvent()->Connect(
         mTaskQueue, this, &MediaFormatReader::NotifyCompositorUpdated);
   }
+  mOnTrackWaitingForKeyListener = OnTrackWaitingForKey().Connect(
+    mTaskQueue, this, &MediaFormatReader::NotifyWaitingForKey);
 }
 
 MediaFormatReader::~MediaFormatReader()
 {
   MOZ_COUNT_DTOR(MediaFormatReader);
 }
 
 RefPtr<ShutdownPromise>
@@ -890,16 +896,17 @@ MediaFormatReader::Shutdown()
     mVideo.ResetState();
     promises.AppendElement(ShutdownDecoderWithPromise(TrackInfo::kVideoTrack));
   }
 
   promises.AppendElement(mDemuxer->Shutdown());
   mDemuxer = nullptr;
 
   mCompositorUpdatedListener.DisconnectIfExists();
+  mOnTrackWaitingForKeyListener.Disconnect();
 
   RefPtr<ShutdownPromise> p = mShutdownPromise.Ensure(__func__);
   ShutdownPromise::All(OwnerThread(), promises)
     ->Then(OwnerThread(), __func__, this,
            &MediaFormatReader::TearDownDecoders,
            &MediaFormatReader::TearDownDecoders);
 
   mShutdown = true;
@@ -1519,16 +1526,31 @@ MediaFormatReader::NotifyWaitingForData(
   decoder.mWaitingForData = true;
   if (decoder.mTimeThreshold) {
     decoder.mTimeThreshold.ref().mWaiting = true;
   }
   ScheduleUpdate(aTrack);
 }
 
 void
+MediaFormatReader::NotifyWaitingForKey(TrackType aTrack)
+{
+  MOZ_ASSERT(OnTaskQueue());
+  auto& decoder = GetDecoderData(aTrack);
+  if (mDecoder) {
+    mDecoder->NotifyWaitingForKey();
+  }
+  if (!decoder.mDecodeRequest.Exists()) {
+    LOGV("WaitingForKey received while no pending decode. Ignoring");
+  }
+  decoder.mWaitingForKey = true;
+  ScheduleUpdate(aTrack);
+}
+
+void
 MediaFormatReader::NotifyEndOfStream(TrackType aTrack)
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
   decoder.mDemuxEOS = true;
   ScheduleUpdate(aTrack);
 }
 
@@ -2008,16 +2030,20 @@ MediaFormatReader::Update(TrackType aTra
     } 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(NS_ERROR_DOM_MEDIA_END_OF_STREAM, __func__);
+    } else if (decoder.mWaitingForKey) {
+      LOG("Rejecting %s promise: WAITING_FOR_DATA due to waiting for key",
+          TrackTypeToStr(aTrack));
+      decoder.RejectPromise(NS_ERROR_DOM_MEDIA_WAITING_FOR_DATA, __func__);
     }
   }
 
   if (decoder.mNeedDraining) {
     DrainDecoder(aTrack);
     return;
   }
 
@@ -2049,23 +2075,33 @@ MediaFormatReader::Update(TrackType aTra
        "shutdown:%d pending:%u waiting:%d promise:%d sid:%u",
        TrackTypeToStr(aTrack), needInput, needOutput, decoder.mNumSamplesInput,
        decoder.mNumSamplesOutput, uint32_t(size_t(decoder.mSizeOfQueue)),
        decoder.mDecodeRequest.Exists(), decoder.mFlushRequest.Exists(),
        decoder.mShutdownRequest.Exists(), uint32_t(decoder.mOutput.Length()),
        decoder.mWaitingForData, decoder.HasPromise(),
        decoder.mLastStreamSourceID);
 
-  if ((decoder.mWaitingForData &&
-       (!decoder.mTimeThreshold || decoder.mTimeThreshold.ref().mWaiting))) {
+  if ((decoder.mWaitingForData
+       && (!decoder.mTimeThreshold || decoder.mTimeThreshold.ref().mWaiting))
+      || (decoder.mWaitingForKey && decoder.mDecodeRequest.Exists())) {
     // Nothing more we can do at present.
     LOGV("Still waiting for data or key.");
     return;
   }
 
+  if (decoder.mWaitingForKey) {
+    decoder.mWaitingForKey = false;
+    if (decoder.HasWaitingPromise() && !decoder.IsWaiting()) {
+      LOGV("No longer waiting for key. Resolving waiting promise");
+      decoder.mWaitingPromise.Resolve(decoder.mType, __func__);
+      return;
+    }
+  }
+
   if (!needInput) {
     LOGV("No need for additional input (pending:%u)",
          uint32_t(decoder.mOutput.Length()));
     return;
   }
 
   // Demux samples if we don't have some.
   RequestDemuxSamples(aTrack);
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -136,16 +136,17 @@ private:
 
   // Drain the current decoder.
   void DrainDecoder(TrackType aTrack);
   void NotifyNewOutput(TrackType aTrack,
                        const MediaDataDecoder::DecodedData& aResults);
   void NotifyDrainComplete(TrackType aTrack);
   void NotifyError(TrackType aTrack, const MediaResult& aError);
   void NotifyWaitingForData(TrackType aTrack);
+  void NotifyWaitingForKey(TrackType aTrack);
   void NotifyEndOfStream(TrackType aTrack);
 
   void ExtractCryptoInitData(nsTArray<uint8_t>& aInitData);
 
   // Initializes mLayersBackendType if possible.
   void InitLayersBackendType();
 
   void Reset(TrackType aTrack);
@@ -165,16 +166,17 @@ private:
                 uint32_t aNumOfMaxError)
       : mOwner(aOwner)
       , mType(aType)
       , mMutex("DecoderData")
       , mDescription("shutdown")
       , mUpdateScheduled(false)
       , mDemuxEOS(false)
       , mWaitingForData(false)
+      , mWaitingForKey(false)
       , mReceivedNewData(false)
       , mNeedDraining(false)
       , mDraining(false)
       , mDrainComplete(false)
       , mFlushed(true)
       , mNumOfConsecutiveError(0)
       , mMaxConsecutiveError(aNumOfMaxError)
       , mNumSamplesInput(0)
@@ -221,16 +223,17 @@ private:
       mDescription = "shutdown";
       mDecoder = nullptr;
     }
 
     // Only accessed from reader's task queue.
     bool mUpdateScheduled;
     bool mDemuxEOS;
     bool mWaitingForData;
+    bool mWaitingForKey;
     bool mReceivedNewData;
 
     // Pending seek.
     MozPromiseRequestHolder<MediaTrackDemuxer::SeekPromise> mSeekRequest;
 
     // Queued demux samples waiting to be decoded.
     nsTArray<RefPtr<MediaRawData>> mQueuedSamples;
     MozPromiseRequestHolder<MediaTrackDemuxer::SamplesPromise> mDemuxRequest;
@@ -240,17 +243,17 @@ private:
     bool HasWaitingPromise() const
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
       return !mWaitingPromise.IsEmpty();
     }
     bool IsWaiting() const
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
-      return mWaitingForData;
+      return mWaitingForData || mWaitingForKey;
     }
 
     // MediaDataDecoder handler's variables.
     MozPromiseRequestHolder<MediaDataDecoder::DecodePromise> mDecodeRequest;
     bool mNeedDraining;
     MozPromiseRequestHolder<MediaDataDecoder::DecodePromise> mDrainRequest;
     bool mDraining;
     bool mDrainComplete;
@@ -368,16 +371,17 @@ private:
     // Reset the state of the DecoderData, clearing all queued frames
     // (pending demuxed and decoded).
     // The track demuxer is *not* reset.
     void ResetState()
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
       mDemuxEOS = false;
       mWaitingForData = false;
+      mWaitingForKey = false;
       mQueuedSamples.Clear();
       mNeedDraining = false;
       mDecodeRequest.DisconnectIfExists();
       mDrainRequest.DisconnectIfExists();
       mDraining = false;
       mDrainComplete = false;
       mTimeThreshold.reset();
       mLastSampleTime.reset();
@@ -555,16 +559,17 @@ private:
   RefPtr<GMPCrashHelper> mCrashHelper;
 
   void SetBlankDecode(TrackType aTrack, bool aIsBlankDecode);
 
   class DecoderFactory;
   UniquePtr<DecoderFactory> mDecoderFactory;
 
   MediaEventListener mCompositorUpdatedListener;
+  MediaEventListener mOnTrackWaitingForKeyListener;
 
   void OnFirstDemuxCompleted(TrackInfo::TrackType aType,
                              RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples);
 
   void OnFirstDemuxFailed(TrackInfo::TrackType aType, const MediaResult& aError);
 
   void MaybeResolveMetadataPromise();
 
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -76,16 +76,18 @@ struct MOZ_STACK_CLASS CreateDecoderPara
   const TrackInfo& mConfig;
   TaskQueue* mTaskQueue = nullptr;
   DecoderDoctorDiagnostics* mDiagnostics = nullptr;
   layers::ImageContainer* mImageContainer = nullptr;
   MediaResult* mError = nullptr;
   RefPtr<layers::KnowsCompositor> mKnowsCompositor;
   RefPtr<GMPCrashHelper> mCrashHelper;
   bool mUseBlankDecoder = false;
+  TrackInfo::TrackType mType = TrackInfo::kUndefinedTrack;
+  MediaEventProducer<TrackInfo::TrackType>* mOnWaitingForKeyEvent = nullptr;
 
 private:
   void Set(TaskQueue* aTaskQueue) { mTaskQueue = aTaskQueue; }
   void Set(DecoderDoctorDiagnostics* aDiagnostics)
   {
     mDiagnostics = aDiagnostics;
   }
   void Set(layers::ImageContainer* aImageContainer)
@@ -94,16 +96,24 @@ private:
   }
   void Set(MediaResult* aError) { mError = aError; }
   void Set(GMPCrashHelper* aCrashHelper) { mCrashHelper = aCrashHelper; }
   void Set(bool aUseBlankDecoder) { mUseBlankDecoder = aUseBlankDecoder; }
   void Set(layers::KnowsCompositor* aKnowsCompositor)
   {
     mKnowsCompositor = aKnowsCompositor;
   }
+  void Set(TrackInfo::TrackType aType)
+  {
+    mType = aType;
+  }
+  void Set(MediaEventProducer<TrackInfo::TrackType>* aOnWaitingForKey)
+  {
+    mOnWaitingForKeyEvent = aOnWaitingForKey;
+  }
   template <typename T1, typename T2, typename... Ts>
   void Set(T1&& a1, T2&& a2, Ts&&... args)
   {
     Set(mozilla::Forward<T1>(a1));
     Set(mozilla::Forward<T2>(a2), mozilla::Forward<Ts>(args)...);
   }
 };
 
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -22,23 +22,24 @@
 namespace mozilla {
 
 typedef MozPromiseRequestHolder<CDMProxy::DecryptPromise> DecryptPromiseRequestHolder;
 extern already_AddRefed<PlatformDecoderModule> CreateBlankDecoderModule();
 
 class EMEDecryptor : public MediaDataDecoder
 {
 public:
-  EMEDecryptor(MediaDataDecoder* aDecoder,
-               CDMProxy* aProxy,
-               TaskQueue* aDecodeTaskQueue)
+  EMEDecryptor(MediaDataDecoder* aDecoder, CDMProxy* aProxy,
+               TaskQueue* aDecodeTaskQueue, TrackInfo::TrackType aType,
+               MediaEventProducer<TrackInfo::TrackType>* aOnWaitingForKey)
     : mDecoder(aDecoder)
     , mTaskQueue(aDecodeTaskQueue)
     , mProxy(aProxy)
-    , mSamplesWaitingForKey(new SamplesWaitingForKey(mProxy))
+    , mSamplesWaitingForKey(
+        new SamplesWaitingForKey(mProxy, aType, aOnWaitingForKey))
     , mIsShutdown(false)
   {
   }
 
   RefPtr<InitPromise> Init() override
   {
     MOZ_ASSERT(!mIsShutdown);
     return mDecoder->Init();
@@ -199,22 +200,25 @@ private:
   MozPromiseRequestHolder<DecodePromise> mDecodeRequest;
 
   bool mIsShutdown;
 };
 
 class EMEMediaDataDecoderProxy : public MediaDataDecoderProxy
 {
 public:
-  EMEMediaDataDecoderProxy(already_AddRefed<AbstractThread> aProxyThread,
-                           CDMProxy* aProxy)
-   : MediaDataDecoderProxy(Move(aProxyThread))
-   , mTaskQueue(AbstractThread::GetCurrent()->AsTaskQueue())
-   , mSamplesWaitingForKey(new SamplesWaitingForKey(aProxy))
-   , mProxy(aProxy)
+  EMEMediaDataDecoderProxy(
+    already_AddRefed<AbstractThread> aProxyThread, CDMProxy* aProxy,
+    TrackInfo::TrackType aType,
+    MediaEventProducer<TrackInfo::TrackType>* aOnWaitingForKey)
+    : MediaDataDecoderProxy(Move(aProxyThread))
+    , mTaskQueue(AbstractThread::GetCurrent()->AsTaskQueue())
+    , mSamplesWaitingForKey(
+        new SamplesWaitingForKey(aProxy, aType, aOnWaitingForKey))
+    , mProxy(aProxy)
   {
   }
 
   RefPtr<DecodePromise> Decode(MediaRawData* aSample) override;
   RefPtr<FlushPromise> Flush() override;
   RefPtr<ShutdownPromise> Shutdown() override;
 
 private:
@@ -284,58 +288,60 @@ EMEDecoderModule::EMEDecoderModule(CDMPr
 {
 }
 
 EMEDecoderModule::~EMEDecoderModule()
 {
 }
 
 static already_AddRefed<MediaDataDecoderProxy>
-CreateDecoderWrapper(CDMProxy* aProxy)
+CreateDecoderWrapper(CDMProxy* aProxy, const CreateDecoderParams& aParams)
 {
   RefPtr<gmp::GeckoMediaPluginService> s(gmp::GeckoMediaPluginService::GetGeckoMediaPluginService());
   if (!s) {
     return nullptr;
   }
   RefPtr<AbstractThread> thread(s->GetAbstractGMPThread());
   if (!thread) {
     return nullptr;
   }
-  RefPtr<MediaDataDecoderProxy> decoder(
-    new EMEMediaDataDecoderProxy(thread.forget(), aProxy));
+  RefPtr<MediaDataDecoderProxy> decoder(new EMEMediaDataDecoderProxy(
+    thread.forget(), aProxy, aParams.mType, aParams.mOnWaitingForKeyEvent));
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 EMEDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
   MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
 
   if (MediaPrefs::EMEBlankVideo()) {
     EME_LOG("EMEDecoderModule::CreateVideoDecoder() creating a blank decoder.");
     RefPtr<PlatformDecoderModule> m(CreateBlankDecoderModule());
     return m->CreateVideoDecoder(aParams);
   }
 
   if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) {
     // GMP decodes. Assume that means it can decrypt too.
-    RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(mProxy);
+    RefPtr<MediaDataDecoderProxy> wrapper =
+      CreateDecoderWrapper(mProxy, aParams);
     auto params = GMPVideoDecoderParams(aParams);
     wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy, params));
     return wrapper.forget();
   }
 
   MOZ_ASSERT(mPDM);
   RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
   if (!decoder) {
     return nullptr;
   }
 
   RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(
-    decoder, mProxy, AbstractThread::GetCurrent()->AsTaskQueue()));
+    decoder, mProxy, AbstractThread::GetCurrent()->AsTaskQueue(),
+    aParams.mType, aParams.mOnWaitingForKeyEvent));
   return emeDecoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
 EMEDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
 {
   MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
 
@@ -350,17 +356,18 @@ EMEDecoderModule::CreateAudioDecoder(con
   }
 
   RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
   if (!decoder) {
     return nullptr;
   }
 
   RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(
-    decoder, mProxy, AbstractThread::GetCurrent()->AsTaskQueue()));
+    decoder, mProxy, AbstractThread::GetCurrent()->AsTaskQueue(),
+    aParams.mType, aParams.mOnWaitingForKeyEvent));
   return emeDecoder.forget();
 }
 
 PlatformDecoderModule::ConversionRequired
 EMEDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
 {
   if (aConfig.IsVideo() && MP4Decoder::IsH264(aConfig.mMimeType)) {
     return ConversionRequired::kNeedAVCC;
--- a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
+++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.cpp
@@ -3,23 +3,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/CDMProxy.h"
 #include "mozilla/CDMCaps.h"
 #include "mozilla/TaskQueue.h"
 #include "MediaData.h"
+#include "MediaEventSource.h"
 #include "SamplesWaitingForKey.h"
 
 namespace mozilla {
 
-SamplesWaitingForKey::SamplesWaitingForKey(CDMProxy* aProxy)
+SamplesWaitingForKey::SamplesWaitingForKey(
+  CDMProxy* aProxy, TrackInfo::TrackType aType,
+  MediaEventProducer<TrackInfo::TrackType>* aOnWaitingForKey)
   : mMutex("SamplesWaitingForKey")
   , mProxy(aProxy)
+  , mType(aType)
+  , mOnWaitingForKeyEvent(aOnWaitingForKey)
 {
 }
 
 SamplesWaitingForKey::~SamplesWaitingForKey()
 {
   Flush();
 }
 
@@ -36,16 +41,19 @@ SamplesWaitingForKey::WaitIfKeyNotUsable
   }
   SampleEntry entry;
   entry.mSample = aSample;
   RefPtr<WaitForKeyPromise> p = entry.mPromise.Ensure(__func__);
   {
     MutexAutoLock lock(mMutex);
     mSamples.AppendElement(Move(entry));
   }
+  if (mOnWaitingForKeyEvent) {
+    mOnWaitingForKeyEvent->Notify(mType);
+  }
   caps.NotifyWhenKeyIdUsable(aSample->mCrypto.mKeyId, this);
   return p;
 }
 
 void
 SamplesWaitingForKey::NotifyUsable(const CencKeyId& aKeyId)
 {
   MutexAutoLock lock(mMutex);
--- a/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h
+++ b/dom/media/platforms/agnostic/eme/SamplesWaitingForKey.h
@@ -5,35 +5,38 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef SamplesWaitingForKey_h_
 #define SamplesWaitingForKey_h_
 
 #include "mozilla/MozPromise.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/RefPtr.h"
+#include "MediaInfo.h"
 
 namespace mozilla {
 
 typedef nsTArray<uint8_t> CencKeyId;
 
 class CDMProxy;
+template <typename... Es> class MediaEventProducer;
 class MediaRawData;
 
 // Encapsulates the task of waiting for the CDMProxy to have the necessary
 // keys to decrypt a given sample.
 class SamplesWaitingForKey
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SamplesWaitingForKey)
 
   typedef MozPromise<RefPtr<MediaRawData>, bool, /* IsExclusive = */ true>
     WaitForKeyPromise;
 
-  explicit SamplesWaitingForKey(CDMProxy* aProxy);
+  SamplesWaitingForKey(CDMProxy* aProxy, TrackInfo::TrackType aType,
+                       MediaEventProducer<TrackInfo::TrackType>* aOnWaitingForKey);
 
   // Returns a promise that will be resolved if or when a key for decoding the
   // sample becomes usable.
   RefPtr<WaitForKeyPromise> WaitIfKeyNotUsable(MediaRawData* aSample);
 
   void NotifyUsable(const CencKeyId& aKeyId);
 
   void Flush();
@@ -45,13 +48,15 @@ private:
   Mutex mMutex;
   RefPtr<CDMProxy> mProxy;
   struct SampleEntry
   {
     RefPtr<MediaRawData> mSample;
     MozPromiseHolder<WaitForKeyPromise> mPromise;
   };
   nsTArray<SampleEntry> mSamples;
+  const TrackInfo::TrackType mType;
+  MediaEventProducer<TrackInfo::TrackType>* const mOnWaitingForKeyEvent;
 };
 
 } // namespace mozilla
 
 #endif //  SamplesWaitingForKey_h_
--- a/dom/media/platforms/wrappers/H264Converter.cpp
+++ b/dom/media/platforms/wrappers/H264Converter.cpp
@@ -23,16 +23,18 @@ H264Converter::H264Converter(PlatformDec
   , mKnowsCompositor(aParams.mKnowsCompositor)
   , mImageContainer(aParams.mImageContainer)
   , mTaskQueue(aParams.mTaskQueue)
   , mDecoder(nullptr)
   , mGMPCrashHelper(aParams.mCrashHelper)
   , mNeedAVCC(aPDM->DecoderNeedsConversion(aParams.mConfig)
       == PlatformDecoderModule::ConversionRequired::kNeedAVCC)
   , mLastError(NS_OK)
+  , mType(aParams.mType)
+  , mOnWaitingForKeyEvent(aParams.mOnWaitingForKeyEvent)
 {
   CreateDecoder(aParams.mDiagnostics);
 }
 
 H264Converter::~H264Converter()
 {
 }
 
@@ -189,17 +191,19 @@ H264Converter::CreateDecoder(DecoderDoct
   }
 
   mDecoder = mPDM->CreateVideoDecoder({
     mCurrentConfig,
     mTaskQueue,
     aDiagnostics,
     mImageContainer,
     mKnowsCompositor,
-    mGMPCrashHelper
+    mGMPCrashHelper,
+    mType,
+    mOnWaitingForKeyEvent
   });
 
   if (!mDecoder) {
     mLastError = NS_ERROR_FAILURE;
     return NS_ERROR_FAILURE;
   }
 
   mNeedKeyframe = true;
--- a/dom/media/platforms/wrappers/H264Converter.h
+++ b/dom/media/platforms/wrappers/H264Converter.h
@@ -71,13 +71,15 @@ private:
   RefPtr<MediaDataDecoder> mDecoder;
   MozPromiseRequestHolder<InitPromise> mInitPromiseRequest;
   MozPromiseRequestHolder<DecodePromise> mDecodePromiseRequest;
   MozPromiseHolder<DecodePromise> mDecodePromise;
   RefPtr<GMPCrashHelper> mGMPCrashHelper;
   bool mNeedAVCC;
   nsresult mLastError;
   bool mNeedKeyframe = true;
+  const TrackInfo::TrackType mType;
+  MediaEventProducer<TrackInfo::TrackType>* const mOnWaitingForKeyEvent;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_H264Converter_h