Bug 1109861 - Add delegate to manage waiting for the CDM to mark key usable. r=kinetik
authorChris Pearce <cpearce@mozilla.com>
Thu, 11 Dec 2014 15:59:37 +1300
changeset 219238 8dbf60547db00bec3f9a6913590df69ad57e2503
parent 219237 5ddb36faf9ee9eaef191d1140ae1e9f96daae6c2
child 219239 36679c5f568edfd9b3318b503b637b3144beb79d
push id10368
push userkwierso@gmail.com
push dateFri, 12 Dec 2014 01:38:39 +0000
treeherderfx-team@5288b15d22de [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskinetik
bugs1109861
milestone37.0a1
Bug 1109861 - Add delegate to manage waiting for the CDM to mark key usable. r=kinetik
dom/media/eme/CDMCaps.cpp
dom/media/eme/CDMCaps.h
dom/media/eme/CDMProxy.cpp
dom/media/eme/CDMProxy.h
dom/media/fmp4/eme/EMEAudioDecoder.cpp
dom/media/fmp4/eme/EMEAudioDecoder.h
dom/media/fmp4/eme/EMEDecoderModule.cpp
dom/media/fmp4/eme/EMEH264Decoder.cpp
dom/media/fmp4/eme/EMEH264Decoder.h
dom/media/fmp4/eme/SamplesWaitingForKey.cpp
dom/media/fmp4/eme/SamplesWaitingForKey.h
dom/media/fmp4/eme/moz.build
dom/media/gmp/gmp-api/gmp-errors.h
--- a/dom/media/eme/CDMCaps.cpp
+++ b/dom/media/eme/CDMCaps.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 "CDMCaps.h"
 #include "gmp-decryption.h"
 #include "EMELog.h"
 #include "nsThreadUtils.h"
+#include "SamplesWaitingForKey.h"
 
 namespace mozilla {
 
 CDMCaps::CDMCaps()
   : mMonitor("CDMCaps")
   , mCaps(0)
 {
 }
@@ -98,22 +99,17 @@ CDMCaps::AutoLock::SetKeyUsable(const Ce
     return false;
   }
   mData.mUsableKeyIds.AppendElement(key);
   auto& waiters = mData.mWaitForKeys;
   size_t i = 0;
   while (i < waiters.Length()) {
     auto& w = waiters[i];
     if (w.mKeyId == aKeyId) {
-      if (waiters[i].mTarget) {
-        EME_LOG("SetKeyUsable() notified waiter.");
-        w.mTarget->Dispatch(w.mContinuation, NS_DISPATCH_NORMAL);
-      } else {
-        w.mContinuation->Run();
-      }
+      w.mListener->NotifyUsable(aKeyId);
       waiters.RemoveElementAt(i);
     } else {
       i++;
     }
   }
   return true;
 }
 
@@ -133,24 +129,23 @@ CDMCaps::AutoLock::SetKeyUnusable(const 
       keys.RemoveElementAt(i);
       break;
     }
   }
   return true;
 }
 
 void
-CDMCaps::AutoLock::CallWhenKeyUsable(const CencKeyId& aKey,
-                                     nsIRunnable* aContinuation,
-                                     nsIThread* aTarget)
+CDMCaps::AutoLock::NotifyWhenKeyIdUsable(const CencKeyId& aKey,
+                                         SamplesWaitingForKey* aListener)
 {
   mData.mMonitor.AssertCurrentThreadOwns();
   MOZ_ASSERT(!IsKeyUsable(aKey));
-  MOZ_ASSERT(aContinuation);
-  mData.mWaitForKeys.AppendElement(WaitForKeys(aKey, aContinuation, aTarget));
+  MOZ_ASSERT(aListener);
+  mData.mWaitForKeys.AppendElement(WaitForKeys(aKey, aListener));
 }
 
 bool
 CDMCaps::AutoLock::AreCapsKnown()
 {
   mData.mMonitor.AssertCurrentThreadOwns();
   return mData.mCaps != 0;
 }
--- a/dom/media/eme/CDMCaps.h
+++ b/dom/media/eme/CDMCaps.h
@@ -8,21 +8,20 @@
 #define CDMCaps_h_
 
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "mozilla/Monitor.h"
 #include "nsIThread.h"
 #include "nsTArray.h"
 #include "mozilla/Attributes.h"
+#include "SamplesWaitingForKey.h"
 
 namespace mozilla {
 
-typedef nsTArray<uint8_t> CencKeyId;
-
 // CDM capabilities; what keys a CDMProxy can use, and whether it can decrypt, or
 // decrypt-and-decode on a per stream basis. Must be locked to access state.
 class CDMCaps {
 public:
   CDMCaps();
   ~CDMCaps();
 
   // Locks the CDMCaps. It must be locked to access its shared state.
@@ -56,44 +55,37 @@ public:
     bool CanDecryptAndDecodeAudio();
     bool CanDecryptAndDecodeVideo();
 
     bool CanDecryptAudio();
     bool CanDecryptVideo();
 
     void CallOnMainThreadWhenCapsAvailable(nsIRunnable* aContinuation);
 
-    // Calls aContinuation on aTarget thread when key become usable.
-    // Pass aTarget=nullptr and runnable will be called on the GMP thread
-    // when key becomes usable.
-    void CallWhenKeyUsable(const CencKeyId& aKey,
-                           nsIRunnable* aContinuation,
-                           nsIThread* aTarget = nullptr);
-
+    // Notifies the SamplesWaitingForKey when key become usable.
+    void NotifyWhenKeyIdUsable(const CencKeyId& aKey,
+                               SamplesWaitingForKey* aSamplesWaiting);
   private:
     // Not taking a strong ref, since this should be allocated on the stack.
     CDMCaps& mData;
   };
 
 private:
   void Lock();
   void Unlock();
   bool HasCap(uint64_t);
 
   struct WaitForKeys {
     WaitForKeys(const CencKeyId& aKeyId,
-                nsIRunnable* aContinuation,
-                nsIThread* aTarget)
+                SamplesWaitingForKey* aListener)
       : mKeyId(aKeyId)
-      , mContinuation(aContinuation)
-      , mTarget(aTarget)
+      , mListener(aListener)
     {}
     CencKeyId mKeyId;
-    nsRefPtr<nsIRunnable> mContinuation;
-    nsCOMPtr<nsIThread> mTarget;
+    nsRefPtr<SamplesWaitingForKey> mListener;
   };
 
   Monitor mMonitor;
 
   struct UsableKey {
     UsableKey(const CencKeyId& aId,
               const nsString& aSessionId)
       : mId(aId)
--- a/dom/media/eme/CDMProxy.cpp
+++ b/dom/media/eme/CDMProxy.cpp
@@ -331,17 +331,17 @@ CDMProxy::Shutdown()
 void
 CDMProxy::gmp_Shutdown()
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   // Abort any pending decrypt jobs, to awaken any clients waiting on a job.
   for (size_t i = 0; i < mDecryptionJobs.Length(); i++) {
     DecryptJob* job = mDecryptionJobs[i];
-    job->mClient->Decrypted(NS_ERROR_ABORT, nullptr);
+    job->mClient->Decrypted(GMPAbortedErr, nullptr);
   }
   mDecryptionJobs.Clear();
 
   if (mCDM) {
     mCDM->Close();
     mCDM = nullptr;
   }
 }
@@ -517,17 +517,17 @@ CDMProxy::Decrypt(mp4_demuxer::MP4Sample
 void
 CDMProxy::gmp_Decrypt(nsAutoPtr<DecryptJob> aJob)
 {
   MOZ_ASSERT(IsOnGMPThread());
   MOZ_ASSERT(aJob->mClient);
   MOZ_ASSERT(aJob->mSample);
 
   if (!mCDM) {
-    aJob->mClient->Decrypted(NS_ERROR_FAILURE, nullptr);
+    aJob->mClient->Decrypted(GMPAbortedErr, nullptr);
     return;
   }
 
   aJob->mId = ++mDecryptionJobCount;
   nsTArray<uint8_t> data;
   data.AppendElements(aJob->mSample->data, aJob->mSample->size);
   mCDM->Decrypt(aJob->mId, aJob->mSample->crypto, data);
   mDecryptionJobs.AppendElement(aJob.forget());
@@ -544,29 +544,36 @@ CDMProxy::gmp_Decrypted(uint32_t aId,
     if (job->mId == aId) {
       if (aDecryptedData.Length() != job->mSample->size) {
         NS_WARNING("CDM returned incorrect number of decrypted bytes");
       }
       if (GMP_SUCCEEDED(aResult)) {
         PodCopy(job->mSample->data,
                 aDecryptedData.Elements(),
                 std::min<size_t>(aDecryptedData.Length(), job->mSample->size));
-        job->mClient->Decrypted(NS_OK, job->mSample.forget());
+        job->mClient->Decrypted(GMPNoErr, job->mSample.forget());
+      } else if (aResult == GMPNoKeyErr) {
+        NS_WARNING("CDM returned GMPNoKeyErr");
+        // We still have the encrypted sample, so we can re-enqueue it to be
+        // decrypted again once the key is usable again.
+        job->mClient->Decrypted(GMPNoKeyErr, job->mSample.forget());
       } else {
-        job->mClient->Decrypted(NS_ERROR_FAILURE, nullptr);
+        nsAutoCString str("CDM returned decode failure GMPErr=");
+        str.AppendInt(aResult);
+        NS_WARNING(str.get());
+        job->mClient->Decrypted(aResult, nullptr);
       }
       mDecryptionJobs.RemoveElementAt(i);
       return;
-    } else {
-      NS_WARNING("GMPDecryptorChild returned incorrect job ID");
     }
   }
+  NS_WARNING("GMPDecryptorChild returned incorrect job ID");
 }
 
 void
 CDMProxy::gmp_Terminated()
 {
   MOZ_ASSERT(IsOnGMPThread());
-  EME_LOG("CDM terminated");
+  NS_WARNING("CDM terminated");
   gmp_Shutdown();
 }
 
 } // namespace mozilla
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -22,17 +22,17 @@ class CDMCallbackProxy;
 
 namespace dom {
 class MediaKeySession;
 }
 
 class DecryptionClient {
 public:
   virtual ~DecryptionClient() {}
-  virtual void Decrypted(nsresult aResult,
+  virtual void Decrypted(GMPErr aResult,
                          mp4_demuxer::MP4Sample* aSample) = 0;
 };
 
 // Proxies calls GMP/CDM, and proxies calls back.
 // Note: Promises are passed in via a PromiseId, so that the ID can be
 // passed via IPC to the CDM, which can then signal when to reject or
 // resolve the promise using its PromiseId.
 class CDMProxy {
--- a/dom/media/fmp4/eme/EMEAudioDecoder.cpp
+++ b/dom/media/fmp4/eme/EMEAudioDecoder.cpp
@@ -29,29 +29,34 @@ EMEAudioDecoder::EMEAudioDecoder(CDMProx
   , mAudioFrameSum(0)
   , mAudioFrameOffset(0)
   , mStreamOffset(0)
   , mProxy(aProxy)
   , mGMP(nullptr)
   , mConfig(aConfig)
   , mTaskQueue(aTaskQueue)
   , mCallback(aCallback)
+  , mSamplesWaitingForKey(new SamplesWaitingForKey(this, mTaskQueue, mProxy))
   , mMonitor("EMEAudioDecoder")
   , mFlushComplete(false)
+#ifdef DEBUG
+  , mIsShutdown(false)
+#endif
 {
 }
 
 EMEAudioDecoder::~EMEAudioDecoder()
 {
 }
 
 nsresult
 EMEAudioDecoder::Init()
 {
   // Note: this runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
 
   MOZ_ASSERT((mConfig.bits_per_sample / 8) == 2); // Demuxer guarantees this.
 
   mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   MOZ_ASSERT(mMPS);
 
   nsresult rv = mMPS->GetThread(getter_AddRefs(mGMPThread));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -63,28 +68,34 @@ EMEAudioDecoder::Init()
 
   return NS_OK;
 }
 
 nsresult
 EMEAudioDecoder::Input(MP4Sample* aSample)
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
+
+  if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
+    return NS_OK;
+  }
 
   nsRefPtr<nsIRunnable> task(new DeliverSample(this, aSample));
   nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 EMEAudioDecoder::Flush()
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
 
   {
     MonitorAutoLock mon(mMonitor);
     mFlushComplete = false;
   }
 
   nsRefPtr<nsIRunnable> task;
   task = NS_NewRunnableMethod(this, &EMEAudioDecoder::GmpFlush);
@@ -100,33 +111,42 @@ EMEAudioDecoder::Flush()
 
   return NS_OK;
 }
 
 nsresult
 EMEAudioDecoder::Drain()
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
 
   nsRefPtr<nsIRunnable> task;
   task = NS_NewRunnableMethod(this, &EMEAudioDecoder::GmpDrain);
   nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 nsresult
 EMEAudioDecoder::Shutdown()
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
+#ifdef DEBUG
+  mIsShutdown = true;
+#endif
 
   nsRefPtr<nsIRunnable> task;
   task = NS_NewRunnableMethod(this, &EMEAudioDecoder::GmpShutdown);
   nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_SYNC);
   NS_ENSURE_SUCCESS(rv, rv);
+
+  mSamplesWaitingForKey->BreakCycles();
+  mSamplesWaitingForKey = nullptr;
+
   return NS_OK;
 }
 
 void
 EMEAudioDecoder::Decoded(const nsTArray<int16_t>& aPCM,
                          uint64_t aTimeStamp,
                          uint32_t aChannels,
                          uint32_t aRate)
@@ -216,19 +236,25 @@ EMEAudioDecoder::ResetComplete()
     mon.NotifyAll();
   }
 }
 
 void
 EMEAudioDecoder::Error(GMPErr aErr)
 {
   MOZ_ASSERT(IsOnGMPThread());
-  EME_LOG("EMEAudioDecoder::Error");
-  mCallback->Error();
-  GmpShutdown();
+  EME_LOG("EMEAudioDecoder::Error %d", aErr);
+  if (aErr == GMPNoKeyErr) {
+    // The GMP failed to decrypt a frame due to not having a key. This can
+    // happen if a key expires or a session is closed during playback.
+    NS_WARNING("GMP failed to decrypt due to lack of key");
+  } else {
+    mCallback->Error();
+    GmpShutdown();
+  }
 }
 
 void
 EMEAudioDecoder::Terminated()
 {
   MOZ_ASSERT(IsOnGMPThread());
   GmpShutdown();
 }
@@ -270,28 +296,16 @@ EMEAudioDecoder::GmpInput(MP4Sample* aSa
 {
   MOZ_ASSERT(IsOnGMPThread());
   nsAutoPtr<MP4Sample> sample(aSample);
   if (!mGMP) {
     mCallback->Error();
     return NS_ERROR_FAILURE;
   }
 
-  if (sample->crypto.valid) {
-    CDMCaps::AutoLock caps(mProxy->Capabilites());
-    MOZ_ASSERT(caps.CanDecryptAndDecodeAudio());
-    const auto& keyid = sample->crypto.key;
-    if (!caps.IsKeyUsable(keyid)) {
-      // DeliverSample assumes responsibility for deleting aSample.
-      nsRefPtr<nsIRunnable> task(new DeliverSample(this, sample.forget()));
-      caps.CallWhenKeyUsable(keyid, task, mGMPThread);
-      return NS_OK;
-    }
-  }
-
   gmp::GMPAudioSamplesImpl samples(sample, mAudioChannels, mAudioRate);
   mGMP->Decode(samples);
 
   mStreamOffset = sample->byte_offset;
 
   return NS_OK;
 }
 
--- a/dom/media/fmp4/eme/EMEAudioDecoder.h
+++ b/dom/media/fmp4/eme/EMEAudioDecoder.h
@@ -8,16 +8,17 @@
 #define EMEAACDecoder_h_
 
 #include "PlatformDecoderModule.h"
 #include "mp4_demuxer/DecoderData.h"
 #include "mozIGeckoMediaPluginService.h"
 #include "nsServiceManagerUtils.h"
 #include "GMPAudioHost.h"
 #include "GMPAudioDecoderProxy.h"
+#include "SamplesWaitingForKey.h"
 
 namespace mozilla {
 
 class EMEAudioDecoder : public MediaDataDecoder
                       , public GMPAudioDecoderProxyCallback
 {
   typedef mp4_demuxer::MP4Sample MP4Sample;
   typedef mp4_demuxer::AudioDecoderConfig AudioDecoderConfig;
@@ -103,15 +104,21 @@ private:
   nsCOMPtr<nsIThread> mGMPThread;
   nsRefPtr<CDMProxy> mProxy;
   GMPAudioDecoderProxy* mGMP;
 
   const mp4_demuxer::AudioDecoderConfig& mConfig;
   nsRefPtr<MediaTaskQueue> mTaskQueue;
   MediaDataDecoderCallback* mCallback;
 
+  nsRefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
+
   Monitor mMonitor;
   bool mFlushComplete;
+
+#ifdef DEBUG
+  bool mIsShutdown;
+#endif
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/fmp4/eme/EMEDecoderModule.cpp
+++ b/dom/media/fmp4/eme/EMEDecoderModule.cpp
@@ -16,165 +16,158 @@
 #include "mozilla/CDMProxy.h"
 #include "mozilla/EMELog.h"
 #include "MediaTaskQueue.h"
 #include "SharedThreadPool.h"
 #include "mozilla/EMELog.h"
 #include "EMEH264Decoder.h"
 #include "EMEAudioDecoder.h"
 #include "mozilla/unused.h"
+#include "SamplesWaitingForKey.h"
 #include <string>
 
 namespace mozilla {
 
 class EMEDecryptor : public MediaDataDecoder {
   typedef mp4_demuxer::MP4Sample MP4Sample;
 
 public:
 
   EMEDecryptor(MediaDataDecoder* aDecoder,
                MediaDataDecoderCallback* aCallback,
                CDMProxy* aProxy)
     : mDecoder(aDecoder)
     , mCallback(aCallback)
     , mTaskQueue(CreateMediaDecodeTaskQueue())
     , mProxy(aProxy)
+    , mSamplesWaitingForKey(new SamplesWaitingForKey(this, mTaskQueue, mProxy))
+#ifdef DEBUG
+    , mIsShutdown(false)
+#endif
   {
   }
 
   virtual nsresult Init() MOZ_OVERRIDE {
+    MOZ_ASSERT(!mIsShutdown);
     nsresult rv = mTaskQueue->SyncDispatch(
       NS_NewRunnableMethod(mDecoder, &MediaDataDecoder::Init));
     unused << NS_WARN_IF(NS_FAILED(rv));
     return rv;
   }
 
-  class RedeliverEncryptedInput : public nsRunnable {
-  public:
-    RedeliverEncryptedInput(EMEDecryptor* aDecryptor,
-                            MediaTaskQueue* aTaskQueue,
-                            MP4Sample* aSample)
-      : mDecryptor(aDecryptor)
-      , mTaskQueue(aTaskQueue)
-      , mSample(aSample)
-    {}
-
-    NS_IMETHOD Run() {
-      RefPtr<nsIRunnable> task;
-      task = NS_NewRunnableMethodWithArg<MP4Sample*>(mDecryptor,
-                                                     &EMEDecryptor::Input,
-                                                     mSample.forget());
-      mTaskQueue->Dispatch(task.forget());
-      mTaskQueue = nullptr;
-      mDecryptor = nullptr;
-      return NS_OK;
-    }
-
-  private:
-    nsRefPtr<EMEDecryptor> mDecryptor;
-    nsRefPtr<MediaTaskQueue> mTaskQueue;
-    nsAutoPtr<MP4Sample> mSample;
-  };
-
   class DeliverDecrypted : public DecryptionClient {
   public:
     DeliverDecrypted(EMEDecryptor* aDecryptor, MediaTaskQueue* aTaskQueue)
       : mDecryptor(aDecryptor)
       , mTaskQueue(aTaskQueue)
     {}
-    virtual void Decrypted(nsresult aResult,
+    virtual void Decrypted(GMPErr aResult,
                            mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE {
-      if (NS_FAILED(aResult)) {
+      if (aResult == GMPNoKeyErr) {
+        RefPtr<nsIRunnable> task;
+        task = NS_NewRunnableMethodWithArg<MP4Sample*>(mDecryptor,
+                                                       &EMEDecryptor::Input,
+                                                       aSample);
+        mTaskQueue->Dispatch(task.forget());
+      } else if (GMP_FAILED(aResult)) {
         mDecryptor->mCallback->Error();
         MOZ_ASSERT(!aSample);
       } else {
         RefPtr<nsIRunnable> task;
         task = NS_NewRunnableMethodWithArg<MP4Sample*>(mDecryptor,
                                                        &EMEDecryptor::Decrypted,
                                                        aSample);
         mTaskQueue->Dispatch(task.forget());
-        mTaskQueue = nullptr;
-        mDecryptor = nullptr;
       }
+      mTaskQueue = nullptr;
+      mDecryptor = nullptr;
     }
   private:
     nsRefPtr<EMEDecryptor> mDecryptor;
     nsRefPtr<MediaTaskQueue> mTaskQueue;
   };
 
   virtual nsresult Input(MP4Sample* aSample) MOZ_OVERRIDE {
+    MOZ_ASSERT(!mIsShutdown);
     // We run the PDM on its own task queue. We can't run it on the decode
     // task queue, because that calls into Input() in a loop and waits until
     // output is delivered. We need to defer some Input() calls while we wait
     // for keys to become usable, and once they do we need to dispatch an event
     // to run the PDM on the same task queue, but since the decode task queue
     // is waiting in MP4Reader::Decode() for output our task would never run.
     // So we dispatch tasks to make all calls into the wrapped decoder.
-    {
-      CDMCaps::AutoLock caps(mProxy->Capabilites());
-      if (!caps.IsKeyUsable(aSample->crypto.key)) {
-        EME_LOG("Encountered a non-usable key, waiting");
-        nsRefPtr<nsIRunnable> task(new RedeliverEncryptedInput(this,
-                                                               mTaskQueue,
-                                                               aSample));
-        caps.CallWhenKeyUsable(aSample->crypto.key, task);
-        return NS_OK;
-      }
+    if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
+      return NS_OK;
     }
+
     mProxy->Decrypt(aSample, new DeliverDecrypted(this, mTaskQueue));
     return NS_OK;
   }
 
   void Decrypted(mp4_demuxer::MP4Sample* aSample) {
+    MOZ_ASSERT(!mIsShutdown);
     nsresult rv = mTaskQueue->Dispatch(
       NS_NewRunnableMethodWithArg<mp4_demuxer::MP4Sample*>(
         mDecoder,
         &MediaDataDecoder::Input,
         aSample));
     unused << NS_WARN_IF(NS_FAILED(rv));
   }
 
   virtual nsresult Flush() MOZ_OVERRIDE {
+    MOZ_ASSERT(!mIsShutdown);
     nsresult rv = mTaskQueue->SyncDispatch(
       NS_NewRunnableMethod(
         mDecoder,
         &MediaDataDecoder::Flush));
     unused << NS_WARN_IF(NS_FAILED(rv));
+    mSamplesWaitingForKey->Flush();
     return rv;
   }
 
   virtual nsresult Drain() MOZ_OVERRIDE {
+    MOZ_ASSERT(!mIsShutdown);
     nsresult rv = mTaskQueue->Dispatch(
       NS_NewRunnableMethod(
         mDecoder,
         &MediaDataDecoder::Drain));
     unused << NS_WARN_IF(NS_FAILED(rv));
     return rv;
   }
 
   virtual nsresult Shutdown() MOZ_OVERRIDE {
+    MOZ_ASSERT(!mIsShutdown);
+#ifdef DEBUG
+    mIsShutdown = true;
+#endif
     nsresult rv = mTaskQueue->SyncDispatch(
       NS_NewRunnableMethod(
         mDecoder,
         &MediaDataDecoder::Shutdown));
     unused << NS_WARN_IF(NS_FAILED(rv));
+    mSamplesWaitingForKey->BreakCycles();
+    mSamplesWaitingForKey = nullptr;
     mDecoder = nullptr;
     mTaskQueue->BeginShutdown();
     mTaskQueue->AwaitShutdownAndIdle();
     mTaskQueue = nullptr;
     mProxy = nullptr;
     return rv;
   }
 
 private:
 
   nsRefPtr<MediaDataDecoder> mDecoder;
   MediaDataDecoderCallback* mCallback;
   nsRefPtr<MediaTaskQueue> mTaskQueue;
   nsRefPtr<CDMProxy> mProxy;
+  nsRefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
+#ifdef DEBUG
+  bool mIsShutdown;
+#endif
 };
 
 EMEDecoderModule::EMEDecoderModule(CDMProxy* aProxy,
                                    PlatformDecoderModule* aPDM,
                                    bool aCDMDecodesAudio,
                                    bool aCDMDecodesVideo)
   : mProxy(aProxy)
   , mPDM(aPDM)
--- a/dom/media/fmp4/eme/EMEH264Decoder.cpp
+++ b/dom/media/fmp4/eme/EMEH264Decoder.cpp
@@ -29,28 +29,33 @@ EMEH264Decoder::EMEH264Decoder(CDMProxy*
   : mProxy(aProxy)
   , mGMP(nullptr)
   , mHost(nullptr)
   , mConfig(aConfig)
   , mImageContainer(aImageContainer)
   , mTaskQueue(aTaskQueue)
   , mCallback(aCallback)
   , mLastStreamOffset(0)
+  , mSamplesWaitingForKey(new SamplesWaitingForKey(this, mTaskQueue, mProxy))
   , mMonitor("EMEH264Decoder")
   , mFlushComplete(false)
+#ifdef DEBUG
+  , mIsShutdown(false)
+#endif
 {
 }
 
 EMEH264Decoder::~EMEH264Decoder() {
 }
 
 nsresult
 EMEH264Decoder::Init()
 {
   // Note: this runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
 
   mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   MOZ_ASSERT(mMPS);
 
   nsresult rv = mMPS->GetThread(getter_AddRefs(mGMPThread));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsRefPtr<InitTask> task(new InitTask(this));
@@ -60,28 +65,34 @@ EMEH264Decoder::Init()
 
   return NS_OK;
 }
 
 nsresult
 EMEH264Decoder::Input(MP4Sample* aSample)
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
+
+  if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
+    return NS_OK;
+  }
 
   nsRefPtr<nsIRunnable> task(new DeliverSample(this, aSample));
   nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 EMEH264Decoder::Flush()
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
 
   {
     MonitorAutoLock mon(mMonitor);
     mFlushComplete = false;
   }
 
   nsRefPtr<nsIRunnable> task;
   task = NS_NewRunnableMethod(this, &EMEH264Decoder::GmpFlush);
@@ -97,33 +108,42 @@ EMEH264Decoder::Flush()
 
   return NS_OK;
 }
 
 nsresult
 EMEH264Decoder::Drain()
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
 
   nsRefPtr<nsIRunnable> task;
   task = NS_NewRunnableMethod(this, &EMEH264Decoder::GmpDrain);
   nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 nsresult
 EMEH264Decoder::Shutdown()
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
+#ifdef DEBUG
+  mIsShutdown = true;
+#endif
 
   nsRefPtr<nsIRunnable> task;
   task = NS_NewRunnableMethod(this, &EMEH264Decoder::GmpShutdown);
   nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_SYNC);
   NS_ENSURE_SUCCESS(rv, rv);
+
+  mSamplesWaitingForKey->BreakCycles();
+  mSamplesWaitingForKey = nullptr;
+
   return NS_OK;
 }
 
 void
 EMEH264Decoder::Decoded(GMPVideoi420Frame* aDecodedFrame)
 {
   MOZ_ASSERT(IsOnGMPThread());
 
@@ -206,19 +226,25 @@ EMEH264Decoder::ResetComplete()
     mon.NotifyAll();
   }
 }
 
 void
 EMEH264Decoder::Error(GMPErr aErr)
 {
   MOZ_ASSERT(IsOnGMPThread());
-  EME_LOG("EMEH264Decoder::Error");
-  mCallback->Error();
-  GmpShutdown();
+  EME_LOG("EMEH264Decoder::Error %d", aErr);
+  if (aErr == GMPNoKeyErr) {
+    // The GMP failed to decrypt a frame due to not having a key. This can
+    // happen if a key expires or a session is closed during playback.
+    NS_WARNING("GMP failed to decrypt due to lack of key");
+  } else {
+    mCallback->Error();
+    GmpShutdown();
+  }
 }
 
 void
 EMEH264Decoder::Terminated()
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   NS_WARNING("H.264 GMP decoder terminated.");
@@ -272,28 +298,16 @@ EMEH264Decoder::GmpInput(MP4Sample* aSam
   MOZ_ASSERT(IsOnGMPThread());
 
   nsAutoPtr<MP4Sample> sample(aSample);
   if (!mGMP) {
     mCallback->Error();
     return NS_ERROR_FAILURE;
   }
 
-  if (sample->crypto.valid) {
-    CDMCaps::AutoLock caps(mProxy->Capabilites());
-    MOZ_ASSERT(caps.CanDecryptAndDecodeVideo());
-    const auto& keyid = sample->crypto.key;
-    if (!caps.IsKeyUsable(keyid)) {
-      nsRefPtr<nsIRunnable> task(new DeliverSample(this, sample.forget()));
-      caps.CallWhenKeyUsable(keyid, task, mGMPThread);
-      return NS_OK;
-    }
-  }
-
-
   mLastStreamOffset = sample->byte_offset;
 
   GMPVideoFrame* ftmp = nullptr;
   GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
   if (GMP_FAILED(err)) {
     mCallback->Error();
     return NS_ERROR_FAILURE;
   }
--- a/dom/media/fmp4/eme/EMEH264Decoder.h
+++ b/dom/media/fmp4/eme/EMEH264Decoder.h
@@ -7,16 +7,17 @@
 #ifndef EMEH264Decoder_h_
 #define EMEH264Decoder_h_
 
 #include "PlatformDecoderModule.h"
 #include "mp4_demuxer/DecoderData.h"
 #include "ImageContainer.h"
 #include "GMPVideoDecoderProxy.h"
 #include "mozIGeckoMediaPluginService.h"
+#include "SamplesWaitingForKey.h"
 
 namespace mozilla {
 
 class CDMProxy;
 class MediaTaskQueue;
 
 class EMEH264Decoder : public MediaDataDecoder
                      , public GMPVideoDecoderCallbackProxy
@@ -99,15 +100,22 @@ private:
   GMPVideoHost* mHost;
 
   VideoInfo mVideoInfo;
   const mp4_demuxer::VideoDecoderConfig& mConfig;
   nsRefPtr<layers::ImageContainer> mImageContainer;
   nsRefPtr<MediaTaskQueue> mTaskQueue;
   MediaDataDecoderCallback* mCallback;
   int64_t mLastStreamOffset;
+
+  nsRefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
+
   Monitor mMonitor;
   bool mFlushComplete;
+
+#ifdef DEBUG
+  bool mIsShutdown;
+#endif
 };
 
 }
 
 #endif // EMEH264Decoder_h_
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/eme/SamplesWaitingForKey.cpp
@@ -0,0 +1,82 @@
+/* -*- 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/. */
+
+#include "SamplesWaitingForKey.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/CDMCaps.h"
+
+namespace mozilla {
+
+SamplesWaitingForKey::SamplesWaitingForKey(MediaDataDecoder* aDecoder,
+                                           MediaTaskQueue* aTaskQueue,
+                                           CDMProxy* aProxy)
+  : mMutex("SamplesWaitingForKey")
+  , mDecoder(aDecoder)
+  , mTaskQueue(aTaskQueue)
+  , mProxy(aProxy)
+{
+}
+
+SamplesWaitingForKey::~SamplesWaitingForKey()
+{
+}
+
+bool
+SamplesWaitingForKey::WaitIfKeyNotUsable(MP4Sample* aSample)
+{
+  if (!aSample || !aSample->crypto.valid || !mProxy) {
+    return false;
+  }
+  CDMCaps::AutoLock caps(mProxy->Capabilites());
+  const auto& keyid = aSample->crypto.key;
+  if (!caps.IsKeyUsable(keyid)) {
+    {
+      MutexAutoLock lock(mMutex);
+      mSamples.AppendElement(aSample);
+    }
+    caps.NotifyWhenKeyIdUsable(aSample->crypto.key, this);
+    return true;
+  }
+  return false;
+}
+
+void
+SamplesWaitingForKey::NotifyUsable(const CencKeyId& aKeyId)
+{
+  MutexAutoLock lock(mMutex);
+  size_t i = 0;
+  while (i < mSamples.Length()) {
+    if (aKeyId == mSamples[i]->crypto.key) {
+      RefPtr<nsIRunnable> task;
+      task = NS_NewRunnableMethodWithArg<MP4Sample*>(mDecoder,
+                                                     &MediaDataDecoder::Input,
+                                                     mSamples[i].forget());
+      mSamples.RemoveElementAt(i);
+      mTaskQueue->Dispatch(task.forget());
+    } else {
+      i++;
+    }
+  }
+}
+
+void
+SamplesWaitingForKey::Flush()
+{
+  MutexAutoLock lock(mMutex);
+  mSamples.Clear();
+}
+
+void
+SamplesWaitingForKey::BreakCycles()
+{
+  MutexAutoLock lock(mMutex);
+  mDecoder = nullptr;
+  mTaskQueue = nullptr;
+  mProxy = nullptr;
+  mSamples.Clear();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/eme/SamplesWaitingForKey.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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 SamplesWaitingForKey_h_
+#define SamplesWaitingForKey_h_
+
+#include "mp4_demuxer/DecoderData.h"
+#include "MediaTaskQueue.h"
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+typedef nsTArray<uint8_t> CencKeyId;
+
+class CDMProxy;
+
+// Encapsulates the task of waiting for the CDMProxy to have the necessary
+// keys to decypt a given sample.
+class SamplesWaitingForKey {
+  typedef mp4_demuxer::MP4Sample MP4Sample;
+public:
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SamplesWaitingForKey)
+
+  explicit SamplesWaitingForKey(MediaDataDecoder* aDecoder,
+                                MediaTaskQueue* aTaskQueue,
+                                CDMProxy* aProxy);
+
+  // Returns true if we need to wait for a key to become usable.
+  // Will callback MediaDataDecoder::Input(aSample) on mDecoder once the
+  // sample is ready to be decrypted. The order of input samples is
+  // preserved.
+  bool WaitIfKeyNotUsable(MP4Sample* aSample);
+
+  void NotifyUsable(const CencKeyId& aKeyId);
+
+  void Flush();
+
+  void BreakCycles();
+
+protected:
+  ~SamplesWaitingForKey();
+
+private:
+  Mutex mMutex;
+  nsRefPtr<MediaDataDecoder> mDecoder;
+  nsRefPtr<MediaTaskQueue> mTaskQueue;
+  nsRefPtr<CDMProxy> mProxy;
+  nsTArray<nsAutoPtr<MP4Sample>> mSamples;
+};
+
+} // namespace mozilla
+
+#endif //  SamplesWaitingForKey_h_
--- a/dom/media/fmp4/eme/moz.build
+++ b/dom/media/fmp4/eme/moz.build
@@ -1,26 +1,28 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# 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/.
-
-EXPORTS += [
-    'EMEAudioDecoder.h',
-    'EMEDecoderModule.h',
-    'EMEH264Decoder.h',
-]
-
-UNIFIED_SOURCES += [
-    'EMEAudioDecoder.cpp',
-    'EMEDecoderModule.cpp',
-    'EMEH264Decoder.cpp',
-]
-
-include('/ipc/chromium/chromium-config.mozbuild')
-
-FINAL_LIBRARY = 'xul'
-
-FAIL_ON_WARNINGS = True
-
-if CONFIG['OS_ARCH'] == 'WINNT':
-    DEFINES['NOMINMAX'] = True
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+EXPORTS += [
+    'EMEAudioDecoder.h',
+    'EMEDecoderModule.h',
+    'EMEH264Decoder.h',
+    'SamplesWaitingForKey.h',
+]
+
+UNIFIED_SOURCES += [
+    'EMEAudioDecoder.cpp',
+    'EMEDecoderModule.cpp',
+    'EMEH264Decoder.cpp',
+    'SamplesWaitingForKey.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+FAIL_ON_WARNINGS = True
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+    DEFINES['NOMINMAX'] = True
--- a/dom/media/gmp/gmp-api/gmp-errors.h
+++ b/dom/media/gmp/gmp-api/gmp-errors.h
@@ -42,15 +42,16 @@ typedef enum {
   GMPRecordInUse = 5,
   GMPQuotaExceededErr = 6,
   GMPDecodeErr = 7,
   GMPEncodeErr = 8,
   GMPNoKeyErr = 9,
   GMPCryptoErr = 10,
   GMPEndOfEnumeration = 11,
   GMPInvalidArgErr = 12,
+  GMPAbortedErr = 13,
   GMPLastErr // Placeholder, must be last. This enum's values must remain consecutive!
 } GMPErr;
 
 #define GMP_SUCCEEDED(x) ((x) == GMPNoErr)
 #define GMP_FAILED(x) ((x) != GMPNoErr)
 
 #endif // GMP_ERRORS_h_