Bug 1121332. Part 1 - add media key status to gmp-api. r=cpearce. a=lmandel
authorJW Wang <jwwang@mozilla.com>
Sat, 31 Jan 2015 13:22:12 +1300
changeset 250183 7a0c7799b5ea
parent 250182 7bc573c193ea
child 250184 ffdf11b39ebf
push id4521
push usercpearce@mozilla.com
push date2015-03-04 01:22 +0000
treeherdermozilla-beta@8abdbdecd2d6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce, lmandel
bugs1121332
milestone37.0
Bug 1121332. Part 1 - add media key status to gmp-api. r=cpearce. a=lmandel
dom/media/eme/CDMCallbackProxy.cpp
dom/media/eme/CDMCallbackProxy.h
dom/media/eme/CDMCaps.cpp
dom/media/eme/CDMCaps.h
dom/media/gmp-plugin/fake.info
dom/media/gmp/GMPDecryptorChild.cpp
dom/media/gmp/GMPDecryptorChild.h
dom/media/gmp/GMPDecryptorParent.cpp
dom/media/gmp/GMPDecryptorParent.h
dom/media/gmp/GMPDecryptorProxy.h
dom/media/gmp/GMPMessageUtils.h
dom/media/gmp/PGMPDecryptor.ipdl
dom/media/gmp/gmp-api/gmp-decryption.h
dom/media/gtest/TestGMPCrossOrigin.cpp
media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
media/gmp-clearkey/0.1/clearkey.info
--- a/dom/media/eme/CDMCallbackProxy.cpp
+++ b/dom/media/eme/CDMCallbackProxy.cpp
@@ -262,45 +262,28 @@ CDMCallbackProxy::SessionError(const nsC
                               aSessionId,
                               aException,
                               aSystemCode,
                               aMessage);
   NS_DispatchToMainThread(task);
 }
 
 void
-CDMCallbackProxy::KeyIdUsable(const nsCString& aSessionId,
-                              const nsTArray<uint8_t>& aKeyId)
+CDMCallbackProxy::KeyStatusChanged(const nsCString& aSessionId,
+                                   const nsTArray<uint8_t>& aKeyId,
+                                   GMPMediaKeyStatus aStatus)
 {
   MOZ_ASSERT(mProxy->IsOnGMPThread());
 
   bool keysChange = false;
   {
     CDMCaps::AutoLock caps(mProxy->Capabilites());
-    keysChange = caps.SetKeyUsable(aKeyId, NS_ConvertUTF8toUTF16(aSessionId));
-  }
-  if (keysChange) {
-    nsRefPtr<nsIRunnable> task;
-    task = NS_NewRunnableMethodWithArg<nsString>(mProxy,
-                                                 &CDMProxy::OnKeysChange,
-                                                 NS_ConvertUTF8toUTF16(aSessionId));
-    NS_DispatchToMainThread(task);
-  }
-}
-
-void
-CDMCallbackProxy::KeyIdNotUsable(const nsCString& aSessionId,
-                                 const nsTArray<uint8_t>& aKeyId)
-{
-  MOZ_ASSERT(mProxy->IsOnGMPThread());
-
-  bool keysChange = false;
-  {
-    CDMCaps::AutoLock caps(mProxy->Capabilites());
-    keysChange = caps.SetKeyUnusable(aKeyId, NS_ConvertUTF8toUTF16(aSessionId));
+    keysChange = caps.SetKeyStatus(aKeyId,
+                                   NS_ConvertUTF8toUTF16(aSessionId),
+                                   aStatus);
   }
   if (keysChange) {
     nsRefPtr<nsIRunnable> task;
     task = NS_NewRunnableMethodWithArg<nsString>(mProxy,
                                                  &CDMProxy::OnKeysChange,
                                                  NS_ConvertUTF8toUTF16(aSessionId));
     NS_DispatchToMainThread(task);
   }
--- a/dom/media/eme/CDMCallbackProxy.h
+++ b/dom/media/eme/CDMCallbackProxy.h
@@ -38,21 +38,19 @@ public:
 
   virtual void SessionClosed(const nsCString& aSessionId) MOZ_OVERRIDE;
 
   virtual void SessionError(const nsCString& aSessionId,
                             nsresult aException,
                             uint32_t aSystemCode,
                             const nsCString& aMessage) MOZ_OVERRIDE;
 
-  virtual void KeyIdUsable(const nsCString& aSessionId,
-                           const nsTArray<uint8_t>& aKeyId) MOZ_OVERRIDE;
-
-  virtual void KeyIdNotUsable(const nsCString& aSessionId,
-                              const nsTArray<uint8_t>& aKeyId) MOZ_OVERRIDE;
+  virtual void KeyStatusChanged(const nsCString& aSessionId,
+                                const nsTArray<uint8_t>& aKeyId,
+                                GMPMediaKeyStatus aStatus) MOZ_OVERRIDE;
 
   virtual void SetCaps(uint64_t aCaps) MOZ_OVERRIDE;
 
   virtual void Decrypted(uint32_t aId,
                          GMPErr aResult,
                          const nsTArray<uint8_t>& aDecryptedData) MOZ_OVERRIDE;
 
   virtual void Terminated() MOZ_OVERRIDE;
--- a/dom/media/eme/CDMCaps.cpp
+++ b/dom/media/eme/CDMCaps.cpp
@@ -1,16 +1,15 @@
 /* -*- 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 "CDMCaps.h"
-#include "gmp-decryption.h"
 #include "EMELog.h"
 #include "nsThreadUtils.h"
 #include "SamplesWaitingForKey.h"
 
 namespace mozilla {
 
 CDMCaps::CDMCaps()
   : mMonitor("CDMCaps")
@@ -75,69 +74,66 @@ CDMCaps::AutoLock::CallOnMainThreadWhenC
     mData.mWaitForCaps.AppendElement(aContinuation);
   }
 }
 
 bool
 CDMCaps::AutoLock::IsKeyUsable(const CencKeyId& aKeyId)
 {
   mData.mMonitor.AssertCurrentThreadOwns();
-  const auto& keys = mData.mUsableKeyIds;
+  const auto& keys = mData.mKeyStatuses;
   for (size_t i = 0; i < keys.Length(); i++) {
-    if (keys[i].mId == aKeyId) {
+    if (keys[i].mId == aKeyId && keys[i].mStatus == kGMPUsable) {
       return true;
     }
   }
   return false;
 }
 
 bool
-CDMCaps::AutoLock::SetKeyUsable(const CencKeyId& aKeyId,
-                                const nsString& aSessionId)
+CDMCaps::AutoLock::SetKeyStatus(const CencKeyId& aKeyId,
+                                const nsString& aSessionId,
+                                GMPMediaKeyStatus aStatus)
 {
   mData.mMonitor.AssertCurrentThreadOwns();
-  UsableKey key(aKeyId, aSessionId);
-  if (mData.mUsableKeyIds.Contains(key)) {
-    return false;
+  KeyStatus key(aKeyId, aSessionId, aStatus);
+  auto index = mData.mKeyStatuses.IndexOf(key);
+
+  if (aStatus == kGMPUnknown) {
+    // Return true if the element is found to notify key changes.
+    return mData.mKeyStatuses.RemoveElement(key);
   }
-  mData.mUsableKeyIds.AppendElement(key);
+
+  if (index != mData.mKeyStatuses.NoIndex) {
+    if (mData.mKeyStatuses[index].mStatus == aStatus) {
+      return false;
+    }
+    mData.mKeyStatuses[index].mStatus = aStatus;
+  } else {
+    mData.mKeyStatuses.AppendElement(key);
+  }
+
+  if (aStatus != kGMPUsable) {
+    return true;
+  }
+
   auto& waiters = mData.mWaitForKeys;
   size_t i = 0;
   while (i < waiters.Length()) {
     auto& w = waiters[i];
     if (w.mKeyId == aKeyId) {
       w.mListener->NotifyUsable(aKeyId);
       waiters.RemoveElementAt(i);
     } else {
       i++;
     }
   }
   return true;
 }
 
-bool
-CDMCaps::AutoLock::SetKeyUnusable(const CencKeyId& aKeyId,
-                                  const nsString& aSessionId)
-{
-  mData.mMonitor.AssertCurrentThreadOwns();
-  UsableKey key(aKeyId, aSessionId);
-  if (!mData.mUsableKeyIds.Contains(key)) {
-    return false;
-  }
-  auto& keys = mData.mUsableKeyIds;
-  for (size_t i = 0; i < keys.Length(); i++) {
-    if (keys[i].mId == aKeyId &&
-        keys[i].mSessionId == aSessionId) {
-      keys.RemoveElementAt(i);
-      break;
-    }
-  }
-  return true;
-}
-
 void
 CDMCaps::AutoLock::NotifyWhenKeyIdUsable(const CencKeyId& aKey,
                                          SamplesWaitingForKey* aListener)
 {
   mData.mMonitor.AssertCurrentThreadOwns();
   MOZ_ASSERT(!IsKeyUsable(aKey));
   MOZ_ASSERT(aListener);
   mData.mWaitForKeys.AppendElement(WaitForKeys(aKey, aListener));
@@ -173,17 +169,17 @@ CDMCaps::AutoLock::CanDecryptVideo()
 {
   return mData.HasCap(GMP_EME_CAP_DECRYPT_VIDEO);
 }
 
 void
 CDMCaps::AutoLock::GetUsableKeysForSession(const nsAString& aSessionId,
                                            nsTArray<CencKeyId>& aOutKeyIds)
 {
-  for (size_t i = 0; i < mData.mUsableKeyIds.Length(); i++) {
-    const auto& key = mData.mUsableKeyIds[i];
-    if (key.mSessionId.Equals(aSessionId)) {
+  for (size_t i = 0; i < mData.mKeyStatuses.Length(); i++) {
+    const auto& key = mData.mKeyStatuses[i];
+    if (key.mSessionId.Equals(aSessionId) && key.mStatus == kGMPUsable) {
       aOutKeyIds.AppendElement(key.mId);
     }
   }
 }
 
-} // namespace mozilla
\ No newline at end of file
+} // namespace mozilla
--- a/dom/media/eme/CDMCaps.h
+++ b/dom/media/eme/CDMCaps.h
@@ -9,16 +9,17 @@
 
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "mozilla/Monitor.h"
 #include "nsIThread.h"
 #include "nsTArray.h"
 #include "mozilla/Attributes.h"
 #include "SamplesWaitingForKey.h"
+#include "gmp-decryption.h"
 
 namespace mozilla {
 
 // 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();
@@ -32,23 +33,19 @@ public:
     ~AutoLock();
 
     // Returns true if the capabilities of the CDM are known, i.e. they have
     // been reported by the CDM to Gecko.
     bool AreCapsKnown();
 
     bool IsKeyUsable(const CencKeyId& aKeyId);
 
-    // Returns true if setting this key usable results in the usable keys
-    // changing for this session, i.e. the key was not previously marked usable.
-    bool SetKeyUsable(const CencKeyId& aKeyId, const nsString& aSessionId);
-
-    // Returns true if setting this key unusable results in the usable keys
-    // changing for this session, i.e. the key was previously marked usable.
-    bool SetKeyUnusable(const CencKeyId& aKeyId, const nsString& aSessionId);
+    // Returns true if key status changed,
+    // i.e. the key status changed from usable to expired.
+    bool SetKeyStatus(const CencKeyId& aKeyId, const nsString& aSessionId, GMPMediaKeyStatus aStatus);
 
     void GetUsableKeysForSession(const nsAString& aSessionId,
                                  nsTArray<CencKeyId>& aOutKeyIds);
 
     // Sets the capabilities of the CDM. aCaps is the logical OR of the
     // GMP_EME_CAP_* flags from gmp-decryption.h.
     void SetCaps(uint64_t aCaps);
 
@@ -80,35 +77,39 @@ private:
       , mListener(aListener)
     {}
     CencKeyId mKeyId;
     nsRefPtr<SamplesWaitingForKey> mListener;
   };
 
   Monitor mMonitor;
 
-  struct UsableKey {
-    UsableKey(const CencKeyId& aId,
-              const nsString& aSessionId)
+  struct KeyStatus {
+    KeyStatus(const CencKeyId& aId,
+              const nsString& aSessionId,
+              GMPMediaKeyStatus aStatus)
       : mId(aId)
       , mSessionId(aSessionId)
+      , mStatus(aStatus)
     {}
-    UsableKey(const UsableKey& aOther)
+    KeyStatus(const KeyStatus& aOther)
       : mId(aOther.mId)
       , mSessionId(aOther.mSessionId)
+      , mStatus(aOther.mStatus)
     {}
-    bool operator==(const UsableKey& aOther) const {
+    bool operator==(const KeyStatus& aOther) const {
       return mId == aOther.mId &&
              mSessionId == aOther.mSessionId;
     };
 
     CencKeyId mId;
     nsString mSessionId;
+    GMPMediaKeyStatus mStatus;
   };
-  nsTArray<UsableKey> mUsableKeyIds;
+  nsTArray<KeyStatus> mKeyStatuses;
 
   nsTArray<WaitForKeys> mWaitForKeys;
 
   nsTArray<nsRefPtr<nsIRunnable>> mWaitForCaps;
   uint64_t mCaps;
 
   // It is not safe to copy this object.
   CDMCaps(const CDMCaps&) = delete;
--- a/dom/media/gmp-plugin/fake.info
+++ b/dom/media/gmp-plugin/fake.info
@@ -1,5 +1,5 @@
 Name: fake
 Description: Fake GMP Plugin
 Version: 1.0
-APIs: encode-video[h264], decode-video[h264], eme-decrypt-v4[fake]
+APIs: encode-video[h264], decode-video[h264], eme-decrypt-v5[fake]
 Libraries: dxva2.dll
--- a/dom/media/gmp/GMPDecryptorChild.cpp
+++ b/dom/media/gmp/GMPDecryptorChild.cpp
@@ -121,37 +121,27 @@ GMPDecryptorChild::SessionError(const ch
 {
   CALL_ON_GMP_THREAD(SendSessionError,
                      nsAutoCString(aSessionId, aSessionIdLength),
                      aException, aSystemCode,
                      nsAutoCString(aMessage, aMessageLength));
 }
 
 void
-GMPDecryptorChild::KeyIdUsable(const char* aSessionId,
-                               uint32_t aSessionIdLength,
-                               const uint8_t* aKeyId,
-                               uint32_t aKeyIdLength)
+GMPDecryptorChild::KeyStatusChanged(const char* aSessionId,
+                                    uint32_t aSessionIdLength,
+                                    const uint8_t* aKeyId,
+                                    uint32_t aKeyIdLength,
+                                    GMPMediaKeyStatus aStatus)
 {
   nsAutoTArray<uint8_t, 16> kid;
   kid.AppendElements(aKeyId, aKeyIdLength);
-  CALL_ON_GMP_THREAD(SendKeyIdUsable,
-                     nsAutoCString(aSessionId, aSessionIdLength), kid);
-}
-
-void
-GMPDecryptorChild::KeyIdNotUsable(const char* aSessionId,
-                                  uint32_t aSessionIdLength,
-                                  const uint8_t* aKeyId,
-                                  uint32_t aKeyIdLength)
-{
-  nsAutoTArray<uint8_t, 16> kid;
-  kid.AppendElements(aKeyId, aKeyIdLength);
-  CALL_ON_GMP_THREAD(SendKeyIdNotUsable,
-                     nsAutoCString(aSessionId, aSessionIdLength), kid);
+  CALL_ON_GMP_THREAD(SendKeyStatusChanged,
+                     nsAutoCString(aSessionId, aSessionIdLength), kid,
+                     aStatus);
 }
 
 void
 GMPDecryptorChild::Decrypted(GMPBuffer* aBuffer, GMPErr aResult)
 {
   if (!ON_GMP_THREAD()) {
     // We should run this whole method on the GMP thread since the buffer needs
     // to be deleted after the SendDecrypted call.
--- a/dom/media/gmp/GMPDecryptorChild.h
+++ b/dom/media/gmp/GMPDecryptorChild.h
@@ -58,25 +58,21 @@ public:
 
   virtual void SessionError(const char* aSessionId,
                             uint32_t aSessionIdLength,
                             GMPDOMException aException,
                             uint32_t aSystemCode,
                             const char* aMessage,
                             uint32_t aMessageLength) MOZ_OVERRIDE;
 
-  virtual void KeyIdUsable(const char* aSessionId,
-                           uint32_t aSessionIdLength,
-                           const uint8_t* aKeyId,
-                           uint32_t aKeyIdLength) MOZ_OVERRIDE;
-
-  virtual void KeyIdNotUsable(const char* aSessionId,
-                              uint32_t aSessionIdLength,
-                              const uint8_t* aKeyId,
-                              uint32_t aKeyIdLength) MOZ_OVERRIDE;
+  virtual void KeyStatusChanged(const char* aSessionId,
+                                uint32_t aSessionIdLength,
+                                const uint8_t* aKeyId,
+                                uint32_t aKeyIdLength,
+                                GMPMediaKeyStatus aStatus) MOZ_OVERRIDE;
 
   virtual void SetCapabilities(uint64_t aCaps) MOZ_OVERRIDE;
 
   virtual void Decrypted(GMPBuffer* aBuffer, GMPErr aResult) MOZ_OVERRIDE;
 
   // GMPDecryptorHost
   virtual void GetSandboxVoucher(const uint8_t** aVoucher,
                                  uint32_t* aVoucherLength) MOZ_OVERRIDE;
--- a/dom/media/gmp/GMPDecryptorParent.cpp
+++ b/dom/media/gmp/GMPDecryptorParent.cpp
@@ -260,36 +260,25 @@ GMPDecryptorParent::RecvSessionError(con
   mCallback->SessionError(aSessionId,
                           GMPExToNsresult(aException),
                           aSystemCode,
                           aMessage);
   return true;
 }
 
 bool
-GMPDecryptorParent::RecvKeyIdUsable(const nsCString& aSessionId,
-                                    const nsTArray<uint8_t>& aKeyId)
+GMPDecryptorParent::RecvKeyStatusChanged(const nsCString& aSessionId,
+                                         InfallibleTArray<uint8_t>&& aKeyId,
+                                         const GMPMediaKeyStatus& aStatus)
 {
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
     return false;
   }
-  mCallback->KeyIdUsable(aSessionId, aKeyId);
-  return true;
-}
-
-bool
-GMPDecryptorParent::RecvKeyIdNotUsable(const nsCString& aSessionId,
-                                       const nsTArray<uint8_t>& aKeyId)
-{
-  if (!mIsOpen) {
-    NS_WARNING("Trying to use a dead GMP decrypter!");
-    return false;
-  }
-  mCallback->KeyIdNotUsable(aSessionId, aKeyId);
+  mCallback->KeyStatusChanged(aSessionId, aKeyId, aStatus);
   return true;
 }
 
 bool
 GMPDecryptorParent::RecvSetCaps(const uint64_t& aCaps)
 {
   if (!mIsOpen) {
     NS_WARNING("Trying to use a dead GMP decrypter!");
--- a/dom/media/gmp/GMPDecryptorParent.h
+++ b/dom/media/gmp/GMPDecryptorParent.h
@@ -86,21 +86,19 @@ private:
 
   virtual bool RecvSessionClosed(const nsCString& aSessionId) MOZ_OVERRIDE;
 
   virtual bool RecvSessionError(const nsCString& aSessionId,
                                 const GMPDOMException& aException,
                                 const uint32_t& aSystemCode,
                                 const nsCString& aMessage) MOZ_OVERRIDE;
 
-  virtual bool RecvKeyIdUsable(const nsCString& aSessionId,
-                                const nsTArray<uint8_t>& aKeyId) MOZ_OVERRIDE;
-
-  virtual bool RecvKeyIdNotUsable(const nsCString& aSessionId,
-                                  const nsTArray<uint8_t>& aKeyId) MOZ_OVERRIDE;
+  virtual bool RecvKeyStatusChanged(const nsCString& aSessionId,
+                                    InfallibleTArray<uint8_t>&& aKeyId,
+                                    const GMPMediaKeyStatus& aStatus) MOZ_OVERRIDE;
 
   virtual bool RecvDecrypted(const uint32_t& aId,
                              const GMPErr& aErr,
                              const nsTArray<uint8_t>& aBuffer) MOZ_OVERRIDE;
 
   virtual bool RecvSetCaps(const uint64_t& aCaps) MOZ_OVERRIDE;
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) MOZ_OVERRIDE;
--- a/dom/media/gmp/GMPDecryptorProxy.h
+++ b/dom/media/gmp/GMPDecryptorProxy.h
@@ -39,21 +39,19 @@ public:
 
   virtual void SessionClosed(const nsCString& aSessionId) = 0;
 
   virtual void SessionError(const nsCString& aSessionId,
                             nsresult aException,
                             uint32_t aSystemCode,
                             const nsCString& aMessage) = 0;
 
-  virtual void KeyIdUsable(const nsCString& aSessionId,
-                           const nsTArray<uint8_t>& aKeyId) = 0;
-
-  virtual void KeyIdNotUsable(const nsCString& aSessionId,
-                              const nsTArray<uint8_t>& aKeyId) = 0;
+  virtual void KeyStatusChanged(const nsCString& aSessionId,
+                                const nsTArray<uint8_t>& aKeyId,
+                                GMPMediaKeyStatus aStatus) = 0;
 
   virtual void SetCaps(uint64_t aCaps) = 0;
 
   virtual void Decrypted(uint32_t aId,
                          GMPErr aResult,
                          const nsTArray<uint8_t>& aDecryptedData) = 0;
 };
 
--- a/dom/media/gmp/GMPMessageUtils.h
+++ b/dom/media/gmp/GMPMessageUtils.h
@@ -56,16 +56,23 @@ struct ParamTraits<GMPDOMException>
 template <>
 struct ParamTraits<GMPSessionMessageType>
 : public ContiguousEnumSerializer<GMPSessionMessageType,
                                   kGMPLicenseRequest,
                                   kGMPMessageInvalid>
 {};
 
 template <>
+struct ParamTraits<GMPMediaKeyStatus>
+: public ContiguousEnumSerializer<GMPMediaKeyStatus,
+                                  kGMPUsable,
+                                  kGMPMediaKeyStatusInvalid>
+{};
+
+template <>
 struct ParamTraits<GMPSessionType>
 : public ContiguousEnumSerializer<GMPSessionType,
                                   kGMPTemporySession,
                                   kGMPSessionInvalid>
 {};
 
 template <>
 struct ParamTraits<GMPAudioCodecType>
--- a/dom/media/gmp/PGMPDecryptor.ipdl
+++ b/dom/media/gmp/PGMPDecryptor.ipdl
@@ -2,16 +2,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 protocol PGMP;
 include GMPTypes;
 
 using GMPSessionMessageType from  "gmp-decryption.h";
+using GMPMediaKeyStatus from  "gmp-decryption.h";
 using GMPSessionType from  "gmp-decryption.h";
 using GMPDOMException from "gmp-decryption.h";
 using GMPErr from "gmp-errors.h";
 
 namespace mozilla {
 namespace gmp {
 
 async protocol PGMPDecryptor
@@ -72,19 +73,18 @@ parent:
 
   SessionClosed(nsCString aSessionId);
 
   SessionError(nsCString aSessionId,
                GMPDOMException aDOMExceptionCode,
                uint32_t aSystemCode,
                nsCString aMessage);
 
-  KeyIdUsable(nsCString aSessionId, uint8_t[] aKey);
+  KeyStatusChanged(nsCString aSessionId, uint8_t[] aKey,
+                   GMPMediaKeyStatus aStatus);
 
   SetCaps(uint64_t aCaps);
 
-  KeyIdNotUsable(nsCString aSessionId,uint8_t[] aKey);
-
   Decrypted(uint32_t aId, GMPErr aResult, uint8_t[] aBuffer);
 };
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/gmp-api/gmp-decryption.h
+++ b/dom/media/gmp/gmp-api/gmp-decryption.h
@@ -71,16 +71,24 @@ enum GMPDOMException {
 enum GMPSessionMessageType {
   kGMPLicenseRequest = 0,
   kGMPLicenseRenewal = 1,
   kGMPLicenseRelease = 2,
   kGMPIndividualizationRequest = 3,
   kGMPMessageInvalid = 4 // Must always be last.
 };
 
+enum GMPMediaKeyStatus {
+  kGMPUsable = 0,
+  kGMPExpired = 1,
+  kGMPOutputNotAllowed = 2,
+  kGMPUnknown = 3,
+  kGMPMediaKeyStatusInvalid = 4 // Must always be last.
+};
+
 // Time in milliseconds, as offset from epoch, 1 Jan 1970.
 typedef int64_t GMPTimestamp;
 
 // Capability definitions. The capabilities of the EME GMP are reported
 // to Gecko by calling the GMPDecryptorCallback::SetCapabilities()
 // callback and specifying the logical OR of the GMP_EME_CAP_* flags below.
 //
 // Note the DECRYPT and the DECRYPT_AND_DECODE are mutually exclusive;
@@ -168,30 +176,24 @@ public:
   // aMessage is optional, but if present must be null terminated.
   virtual void SessionError(const char* aSessionId,
                             uint32_t aSessionIdLength,
                             GMPDOMException aException,
                             uint32_t aSystemCode,
                             const char* aMessage,
                             uint32_t aMessageLength) = 0;
 
-  // Marks a key as usable. Gecko will not call into the CDM to decrypt
+  // Notifies the status of a key. Gecko will not call into the CDM to decrypt
   // or decode content encrypted with a key unless the CDM has marked it
   // usable first. So a CDM *MUST* mark its usable keys as usable!
-  virtual void KeyIdUsable(const char* aSessionId,
-                           uint32_t aSessionIdLength,
-                           const uint8_t* aKeyId,
-                           uint32_t aKeyIdLength) = 0;
-
-  // Marks a key as no longer usable.
-  // Note: Keys are assumed to be not usable when a session is closed or removed.
-  virtual void KeyIdNotUsable(const char* aSessionId,
-                              uint32_t aSessionIdLength,
-                              const uint8_t* aKeyId,
-                              uint32_t aKeyIdLength) = 0;
+  virtual void KeyStatusChanged(const char* aSessionId,
+                                uint32_t aSessionIdLength,
+                                const uint8_t* aKeyId,
+                                uint32_t aKeyIdLength,
+                                GMPMediaKeyStatus aStatus) = 0;
 
   // The CDM must report its capabilites of this CDM. aCaps should be a
   // logical OR of the GMP_EME_CAP_* flags. The CDM *MUST* call this
   // function and report whether it can decrypt and/or decode. Without
   // this, Gecko does not know how to use the CDM and will not send
   // samples to the CDM to decrypt or decrypt-and-decode mode. Note a
   // CDM cannot change modes once playback has begun.
   virtual void SetCapabilities(uint64_t aCaps) = 0;
@@ -215,17 +217,17 @@ public:
 };
 
 enum GMPSessionType {
   kGMPTemporySession = 0,
   kGMPPersistentSession = 1,
   kGMPSessionInvalid = 2 // Must always be last.
 };
 
-#define GMP_API_DECRYPTOR "eme-decrypt-v4"
+#define GMP_API_DECRYPTOR "eme-decrypt-v5"
 
 // API exposed by plugin library to manage decryption sessions.
 // When the Host requests this by calling GMPGetAPIFunc().
 //
 // API name macro: GMP_API_DECRYPTOR
 // Host API: GMPDecryptorHost
 class GMPDecryptor {
 public:
--- a/dom/media/gtest/TestGMPCrossOrigin.cpp
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -1111,20 +1111,19 @@ class GMPStorageTest : public GMPDecrypt
                              const nsCString& aSessionId) MOZ_OVERRIDE { }
   virtual void ExpirationChange(const nsCString& aSessionId,
                                 GMPTimestamp aExpiryTime) MOZ_OVERRIDE {}
   virtual void SessionClosed(const nsCString& aSessionId) MOZ_OVERRIDE {}
   virtual void SessionError(const nsCString& aSessionId,
                             nsresult aException,
                             uint32_t aSystemCode,
                             const nsCString& aMessage) MOZ_OVERRIDE {}
-  virtual void KeyIdUsable(const nsCString& aSessionId,
-                           const nsTArray<uint8_t>& aKeyId) MOZ_OVERRIDE { }
-  virtual void KeyIdNotUsable(const nsCString& aSessionId,
-                              const nsTArray<uint8_t>& aKeyId) MOZ_OVERRIDE {}
+  virtual void KeyStatusChanged(const nsCString& aSessionId,
+                                const nsTArray<uint8_t>& aKeyId,
+                                GMPMediaKeyStatus aStatus) MOZ_OVERRIDE { }
   virtual void SetCaps(uint64_t aCaps) MOZ_OVERRIDE {}
   virtual void Decrypted(uint32_t aId,
                          GMPErr aResult,
                          const nsTArray<uint8_t>& aDecryptedData) MOZ_OVERRIDE { }
   virtual void Terminated() MOZ_OVERRIDE { }
 
 private:
   ~GMPStorageTest() { }
--- a/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
@@ -162,18 +162,19 @@ ClearKeySessionManager::PersistentSessio
     Key key(base + CLEARKEY_KEY_LEN, base + 2 * CLEARKEY_KEY_LEN);
     MOZ_ASSERT(key.size() == CLEARKEY_KEY_LEN);
 
     session->AddKeyId(keyId);
 
     mDecryptionManager->ExpectKeyId(keyId);
     mDecryptionManager->InitKey(keyId, key);
     mKeyIds.insert(key);
-    mCallback->KeyIdUsable(&aSessionId[0], aSessionId.size(),
-                           &keyId[0], keyId.size());
+    mCallback->KeyStatusChanged(&aSessionId[0], aSessionId.size(),
+                                &keyId[0], keyId.size(),
+                                kGMPUsable);
   }
 
   mCallback->ResolveLoadSessionPromise(aPromiseId, true);
 }
 
 void
 ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
                                       const char* aSessionId,
@@ -198,18 +199,19 @@ ClearKeySessionManager::UpdateSession(ui
     CK_LOGW("ClearKey CDM failed to parse JSON Web Key.");
     mCallback->RejectPromise(aPromiseId, kGMPInvalidAccessError, nullptr, 0);
     return;
   }
 
   for (auto it = keyPairs.begin(); it != keyPairs.end(); it++) {
     mDecryptionManager->InitKey(it->mKeyId, it->mKey);
     mKeyIds.insert(it->mKeyId);
-    mCallback->KeyIdUsable(aSessionId, aSessionIdLength,
-                           &it->mKeyId[0], it->mKeyId.size());
+    mCallback->KeyStatusChanged(aSessionId, aSessionIdLength,
+                                &it->mKeyId[0], it->mKeyId.size(),
+                                kGMPUsable);
   }
 
   if (session->Type() != kGMPPersistentSession) {
     mCallback->ResolvePromise(aPromiseId);
     return;
   }
 
   // Store the keys on disk. We store a record whose name is the sessionId,
@@ -274,18 +276,19 @@ ClearKeySessionManager::ClearInMemorySes
   MOZ_ASSERT(aSession);
 
   const vector<KeyId>& keyIds = aSession->GetKeyIds();
   for (auto it = keyIds.begin(); it != keyIds.end(); it++) {
     MOZ_ASSERT(mDecryptionManager->HasKeyForKeyId(*it));
     mDecryptionManager->ReleaseKeyId(*it);
 
     const string& sessionId = aSession->Id();
-    mCallback->KeyIdNotUsable(&sessionId[0], sessionId.size(),
-                              &(*it)[0], it->size());
+    mCallback->KeyStatusChanged(&sessionId[0], sessionId.size(),
+                                &(*it)[0], it->size(),
+                                kGMPUnknown);
   }
 
   mSessions.erase(aSession->Id());
   delete aSession;
 }
 
 void
 ClearKeySessionManager::RemoveSession(uint32_t aPromiseId,
--- a/media/gmp-clearkey/0.1/clearkey.info
+++ b/media/gmp-clearkey/0.1/clearkey.info
@@ -1,4 +1,4 @@
 Name: clearkey
 Description: ClearKey decrypt-only GMP plugin
 Version: 0.1
-APIs: eme-decrypt-v4[org.w3.clearkey]
+APIs: eme-decrypt-v5[org.w3.clearkey]