--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -29,16 +29,43 @@ struct DecryptResult {
DecryptResult(DecryptStatus aStatus, MediaRawData* aSample)
: mStatus(aStatus)
, mSample(aSample)
{}
DecryptStatus mStatus;
RefPtr<MediaRawData> mSample;
};
+class CDMKeyInfo {
+public:
+ explicit CDMKeyInfo(const nsTArray<uint8_t>& aKeyId)
+ : mKeyId(aKeyId)
+ , mStatus()
+ {}
+
+ CDMKeyInfo(const nsTArray<uint8_t>& aKeyId,
+ const dom::Optional<dom::MediaKeyStatus>& aStatus)
+ : mKeyId(aKeyId)
+ , mStatus(aStatus.Value())
+ {}
+
+ // The copy-ctor and copy-assignment operator for Optional<T> are declared as
+ // delete, so override CDMKeyInfo copy-ctor for nsTArray operations.
+ CDMKeyInfo(const CDMKeyInfo& aKeyInfo)
+ {
+ mKeyId = aKeyInfo.mKeyId;
+ if (aKeyInfo.mStatus.WasPassed()) {
+ mStatus.Construct(aKeyInfo.mStatus.Value());
+ }
+ }
+
+ nsTArray<uint8_t> mKeyId;
+ dom::Optional<dom::MediaKeyStatus> mStatus;
+};
+
typedef int64_t UnixTime;
// Proxies calls 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 {
protected:
--- a/dom/media/eme/DecryptorProxyCallback.h
+++ b/dom/media/eme/DecryptorProxyCallback.h
@@ -46,11 +46,14 @@ public:
mozilla::dom::MediaKeyStatus aStatus) = 0;
virtual void ForgetKeyStatus(const nsCString& aSessionId,
const nsTArray<uint8_t>& aKeyId) = 0;
virtual void Decrypted(uint32_t aId,
mozilla::DecryptStatus aResult,
const nsTArray<uint8_t>& aDecryptedData) = 0;
+
+ virtual void BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<mozilla::CDMKeyInfo>& aKeyInfos) = 0;
};
-#endif
\ No newline at end of file
+#endif
--- a/dom/media/gmp/GMPCDMCallbackProxy.cpp
+++ b/dom/media/gmp/GMPCDMCallbackProxy.cpp
@@ -287,16 +287,24 @@ GMPCDMCallbackProxy::KeyStatusChanged(co
MOZ_ASSERT(mProxy->IsOnOwnerThread());
KeyStatusChangedInternal(aSessionId,
aKeyId,
dom::Optional<dom::MediaKeyStatus>(aStatus));
}
void
+GMPCDMCallbackProxy::BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos)
+{
+ MOZ_ASSERT(mProxy->IsOnOwnerThread());
+ BatchedKeyStatusChangedInternal(aSessionId, aKeyInfos);
+}
+
+void
GMPCDMCallbackProxy::ForgetKeyStatus(const nsCString& aSessionId,
const nsTArray<uint8_t>& aKeyId)
{
MOZ_ASSERT(mProxy->IsOnOwnerThread());
KeyStatusChangedInternal(aSessionId,
aKeyId,
dom::Optional<dom::MediaKeyStatus>());
@@ -319,16 +327,39 @@ GMPCDMCallbackProxy::KeyStatusChangedInt
task = NewRunnableMethod<nsString>(mProxy,
&CDMProxy::OnKeyStatusesChange,
NS_ConvertUTF8toUTF16(aSessionId));
NS_DispatchToMainThread(task);
}
}
void
+GMPCDMCallbackProxy::BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos)
+{
+ bool keyStatusesChange = false;
+ {
+ CDMCaps::AutoLock caps(mProxy->Capabilites());
+ for (size_t i = 0; i < aKeyInfos.Length(); i++) {
+ keyStatusesChange |=
+ caps.SetKeyStatus(aKeyInfos[i].mKeyId,
+ NS_ConvertUTF8toUTF16(aSessionId),
+ aKeyInfos[i].mStatus);
+ }
+ }
+ if (keyStatusesChange) {
+ nsCOMPtr<nsIRunnable> task;
+ task = NewRunnableMethod<nsString>(mProxy,
+ &CDMProxy::OnKeyStatusesChange,
+ NS_ConvertUTF8toUTF16(aSessionId));
+ NS_DispatchToMainThread(task);
+ }
+}
+
+void
GMPCDMCallbackProxy::Decrypted(uint32_t aId,
DecryptStatus aResult,
const nsTArray<uint8_t>& aDecryptedData)
{
MOZ_ASSERT(mProxy->IsOnOwnerThread());
mProxy->OnDecrypted(aId, aResult, aDecryptedData);
}
--- a/dom/media/gmp/GMPCDMCallbackProxy.h
+++ b/dom/media/gmp/GMPCDMCallbackProxy.h
@@ -49,26 +49,31 @@ public:
void ForgetKeyStatus(const nsCString& aSessionId,
const nsTArray<uint8_t>& aKeyId) override;
void Decrypted(uint32_t aId,
DecryptStatus aResult,
const nsTArray<uint8_t>& aDecryptedData) override;
+ void BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos) override;
+
void Terminated() override;
~GMPCDMCallbackProxy() {}
private:
friend class GMPCDMProxy;
explicit GMPCDMCallbackProxy(CDMProxy* aProxy);
void KeyStatusChangedInternal(const nsCString& aSessionId,
const nsTArray<uint8_t>& aKeyId,
const dom::Optional<dom::MediaKeyStatus>& aStatus);
+ void BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos);
// Warning: Weak ref.
CDMProxy* mProxy;
};
} // namespace mozilla
#endif // GMPCDMCallbackProxy_h_
--- a/dom/media/gmp/GMPDecryptorChild.cpp
+++ b/dom/media/gmp/GMPDecryptorChild.cpp
@@ -169,16 +169,33 @@ GMPDecryptorChild::KeyStatusChanged(cons
CALL_ON_GMP_THREAD(SendKeyStatusChanged,
nsCString(aSessionId, aSessionIdLength),
kid,
aStatus);
}
}
void
+GMPDecryptorChild::BatchedKeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const GMPMediaKeyInfo* aKeyInfos,
+ uint32_t aKeyInfosLength)
+{
+ nsTArray<GMPKeyInformation> keyInfos;
+ for (uint32_t i = 0; i < aKeyInfosLength; i++) {
+ nsTArray<uint8_t> keyId;
+ keyId.AppendElements(aKeyInfos[i].keyid, aKeyInfos[i].keyid_size);
+ keyInfos.AppendElement(GMPKeyInformation(keyId, aKeyInfos[i].status));
+ }
+ CALL_ON_GMP_THREAD(SendBatchedKeyStatusChanged,
+ nsCString(aSessionId, aSessionIdLength),
+ keyInfos);
+}
+
+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.
mPlugin->GMPMessageLoop()->PostTask(NewRunnableMethod
<GMPBuffer*, GMPErr>(this,
&GMPDecryptorChild::Decrypted,
--- a/dom/media/gmp/GMPDecryptorChild.h
+++ b/dom/media/gmp/GMPDecryptorChild.h
@@ -68,16 +68,21 @@ public:
const uint8_t* aKeyId,
uint32_t aKeyIdLength,
GMPMediaKeyStatus aStatus) override;
void SetCapabilities(uint64_t aCaps) override;
void Decrypted(GMPBuffer* aBuffer, GMPErr aResult) override;
+ void BatchedKeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const GMPMediaKeyInfo* aKeyInfos,
+ uint32_t aKeyInfosLength) override;
+
// GMPDecryptorHost
void GetSandboxVoucher(const uint8_t** aVoucher,
uint32_t* aVoucherLength) override;
void GetPluginVoucher(const uint8_t** aVoucher,
uint32_t* aVoucherLength) override;
private:
~GMPDecryptorChild();
--- a/dom/media/gmp/GMPDecryptorParent.cpp
+++ b/dom/media/gmp/GMPDecryptorParent.cpp
@@ -379,16 +379,40 @@ GMPDecryptorParent::RecvForgetKeyStatus(
this, aSessionId.get(), ToBase64(aKeyId).get()));
if (mIsOpen) {
mCallback->ForgetKeyStatus(aSessionId, aKeyId);
}
return true;
}
+bool
+GMPDecryptorParent::RecvBatchedKeyStatusChanged(const nsCString& aSessionId,
+ InfallibleTArray<GMPKeyInformation>&& aKeyInfos)
+{
+ LOGD(("GMPDecryptorParent[%p]::RecvBatchedKeyStatusChanged(sessionId='%s', KeyInfos len='%d')",
+ this, aSessionId.get(), aKeyInfos.Length()));
+
+ if (mIsOpen) {
+ nsTArray<CDMKeyInfo> cdmKeyInfos(aKeyInfos.Length());
+ for (uint32_t i = 0; i < aKeyInfos.Length(); i++) {
+ // If the status is kGMPUnknown, we're going to forget(remove) that key info.
+ if (aKeyInfos[i].status() != kGMPUnknown) {
+ auto status = ToMediaKeyStatus(aKeyInfos[i].status());
+ cdmKeyInfos.AppendElement(CDMKeyInfo(aKeyInfos[i].keyId(),
+ dom::Optional<dom::MediaKeyStatus>(status)));
+ } else {
+ cdmKeyInfos.AppendElement(CDMKeyInfo(aKeyInfos[i].keyId()));
+ }
+ }
+ mCallback->BatchedKeyStatusChanged(aSessionId, cdmKeyInfos);
+ }
+ return true;
+}
+
DecryptStatus
ToDecryptStatus(GMPErr aError)
{
switch (aError) {
case GMPNoErr: return Ok;
case GMPNoKeyErr: return NoKeyErr;
case GMPAbortedErr: return AbortedErr;
default: return GenericErr;
--- a/dom/media/gmp/GMPDecryptorParent.h
+++ b/dom/media/gmp/GMPDecryptorParent.h
@@ -104,16 +104,19 @@ private:
bool RecvForgetKeyStatus(const nsCString& aSessionId,
InfallibleTArray<uint8_t>&& aKeyId) override;
bool RecvDecrypted(const uint32_t& aId,
const GMPErr& aErr,
InfallibleTArray<uint8_t>&& aBuffer) override;
+ bool RecvBatchedKeyStatusChanged(const nsCString& aSessionId,
+ InfallibleTArray<GMPKeyInformation>&& aKeyInfos) override;
+
bool RecvShutdown() override;
void ActorDestroy(ActorDestroyReason aWhy) override;
bool Recv__delete__() override;
bool mIsOpen;
bool mShuttingDown;
bool mActorDestroyed;
--- a/dom/media/gmp/GMPTypes.ipdlh
+++ b/dom/media/gmp/GMPTypes.ipdlh
@@ -1,15 +1,16 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
using GMPBufferType from "gmp-video-codec.h";
using GMPAudioCodecType from "gmp-audio-codec.h";
+using GMPMediaKeyStatus from "gmp-decryption.h";
namespace mozilla {
namespace gmp {
struct GMPDecryptionData {
uint8_t[] mKeyId;
uint8_t[] mIV;
uint16_t[] mClearBytes;
@@ -71,10 +72,15 @@ struct GMPAudioEncodedSampleData
struct GMPAudioDecodedSampleData
{
int16_t[] mData;
uint64_t mTimeStamp; // microseconds.
uint32_t mChannelCount;
uint32_t mSamplesPerSecond;
};
+struct GMPKeyInformation {
+ uint8_t[] keyId;
+ GMPMediaKeyStatus status;
+};
+
}
}
--- a/dom/media/gmp/PGMPDecryptor.ipdl
+++ b/dom/media/gmp/PGMPDecryptor.ipdl
@@ -2,17 +2,16 @@
/* 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 PGMPContent;
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
@@ -82,12 +81,15 @@ parent:
async KeyStatusChanged(nsCString aSessionId, uint8_t[] aKey,
GMPMediaKeyStatus aStatus);
async ForgetKeyStatus(nsCString aSessionId, uint8_t[] aKey);
async Decrypted(uint32_t aId, GMPErr aResult, uint8_t[] aBuffer);
async Shutdown();
+
+ async BatchedKeyStatusChanged(nsCString aSessionId,
+ GMPKeyInformation[] aKeyInfos);
};
} // namespace gmp
} // namespace mozilla
--- a/dom/media/gmp/gmp-api/gmp-decryption.h
+++ b/dom/media/gmp/gmp-api/gmp-decryption.h
@@ -97,16 +97,30 @@ enum GMPMediaKeyStatus {
kGMPOutputRestricted = 3,
kGMPInternalError = 4,
kGMPUnknown = 5, // Removes key from MediaKeyStatusMap
kGMPReleased = 6,
kGMPStatusPending = 7,
kGMPMediaKeyStatusInvalid = 8 // Must always be last.
};
+struct GMPMediaKeyInfo {
+ GMPMediaKeyInfo() {}
+ GMPMediaKeyInfo(const uint8_t* aKeyId,
+ uint32_t aKeyIdSize,
+ GMPMediaKeyStatus aStatus)
+ : keyid(aKeyId)
+ , keyid_size(aKeyIdSize)
+ , status(aStatus)
+ {}
+ const uint8_t* keyid;
+ uint32_t keyid_size;
+ GMPMediaKeyStatus status;
+};
+
// Time in milliseconds, as offset from epoch, 1 Jan 1970.
typedef int64_t GMPTimestamp;
// Callbacks to be called from the CDM. Threadsafe.
class GMPDecryptorCallback {
public:
// The GMPDecryptor should call this in response to a call to
@@ -187,16 +201,22 @@ public:
GMPMediaKeyStatus aStatus) = 0;
// DEPRECATED; this function has no affect.
virtual void SetCapabilities(uint64_t aCaps) = 0;
// Returns decrypted buffer to Gecko, or reports failure.
virtual void Decrypted(GMPBuffer* aBuffer, GMPErr aResult) = 0;
+ // To aggregate KeyStatusChanged into single callback per session id.
+ virtual void BatchedKeyStatusChanged(const char* aSessionId,
+ uint32_t aSessionIdLength,
+ const GMPMediaKeyInfo* aKeyInfos,
+ uint32_t aKeyInfosLength) = 0;
+
virtual ~GMPDecryptorCallback() {}
};
// Host interface, passed to GetAPIFunc(), with "decrypt".
class GMPDecryptorHost {
public:
virtual void GetSandboxVoucher(const uint8_t** aVoucher,
uint32_t* aVoucherLength) = 0;
--- a/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
+++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
@@ -404,23 +404,25 @@ WidevineDecryptor::OnSessionKeysChange(c
const KeyInformation* aKeysInfo,
uint32_t aKeysInfoCount)
{
if (!mCallback) {
Log("Decryptor::OnSessionKeysChange() FAIL; !mCallback");
return;
}
Log("Decryptor::OnSessionKeysChange()");
+
+ nsTArray<GMPMediaKeyInfo> key_infos;
for (uint32_t i = 0; i < aKeysInfoCount; i++) {
- mCallback->KeyStatusChanged(aSessionId,
- aSessionIdSize,
- aKeysInfo[i].key_id,
- aKeysInfo[i].key_id_size,
- ToGMPKeyStatus(aKeysInfo[i].status));
+ key_infos.AppendElement(GMPMediaKeyInfo(aKeysInfo[i].key_id,
+ aKeysInfo[i].key_id_size,
+ ToGMPKeyStatus(aKeysInfo[i].status)));
}
+ mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdSize,
+ key_infos.Elements(), key_infos.Length());
}
static GMPTimestamp
ToGMPTime(Time aCDMTime)
{
return static_cast<GMPTimestamp>(aCDMTime * 1000);
}
--- a/dom/media/gtest/TestGMPCrossOrigin.cpp
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -1393,16 +1393,20 @@ class GMPStorageTest : public GMPDecrypt
mozilla::dom::MediaKeyStatus aStatus) override { }
void ForgetKeyStatus(const nsCString& aSessionId,
const nsTArray<uint8_t>& aKeyId) override { }
void Decrypted(uint32_t aId,
mozilla::DecryptStatus aResult,
const nsTArray<uint8_t>& aDecryptedData) override { }
+
+ void BatchedKeyStatusChanged(const nsCString& aSessionId,
+ const nsTArray<CDMKeyInfo>& aKeyInfos) override { }
+
void Terminated() override {
if (mDecryptor) {
mDecryptor->Close();
mDecryptor = nullptr;
}
}
private:
--- a/media/gmp-clearkey/0.1/ClearKeySession.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySession.cpp
@@ -36,25 +36,24 @@ ClearKeySession::ClearKeySession(const s
{
CK_LOGD("ClearKeySession ctor %p", this);
}
ClearKeySession::~ClearKeySession()
{
CK_LOGD("ClearKeySession dtor %p", this);
- auto& keyIds = GetKeyIds();
- for (auto it = keyIds.begin(); it != keyIds.end(); it++) {
- assert(ClearKeyDecryptionManager::Get()->HasSeenKeyId(*it));
-
- ClearKeyDecryptionManager::Get()->ReleaseKeyId(*it);
- mCallback->KeyStatusChanged(&mSessionId[0], mSessionId.size(),
- &(*it)[0], it->size(),
- kGMPUnknown);
+ std::vector<GMPMediaKeyInfo> key_infos;
+ for (const KeyId& keyId : mKeyIds) {
+ assert(ClearKeyDecryptionManager::Get()->HasSeenKeyId(keyId));
+ ClearKeyDecryptionManager::Get()->ReleaseKeyId(keyId);
+ key_infos.push_back(GMPMediaKeyInfo(&keyId[0], keyId.size(), kGMPUnknown));
}
+ mCallback->BatchedKeyStatusChanged(&mSessionId[0], mSessionId.size(),
+ key_infos.data(), key_infos.size());
}
void
ClearKeySession::Init(uint32_t aCreateSessionToken,
uint32_t aPromiseId,
const std::string& aInitDataType,
const uint8_t* aInitData, uint32_t aInitDataSize)
{
--- a/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
+++ b/media/gmp-clearkey/0.1/ClearKeySessionManager.cpp
@@ -163,34 +163,43 @@ ClearKeySessionManager::PersistentSessio
}
ClearKeySession* session = new ClearKeySession(aSessionId,
mCallback,
kGMPPersistentSession);
mSessions[aSessionId] = session;
uint32_t numKeys = aKeyDataSize / (2 * CLEARKEY_KEY_LEN);
+
+ vector<GMPMediaKeyInfo> key_infos;
+ vector<KeyIdPair> keyPairs;
for (uint32_t i = 0; i < numKeys; i ++) {
const uint8_t* base = aKeyData + 2 * CLEARKEY_KEY_LEN * i;
- KeyId keyId(base, base + CLEARKEY_KEY_LEN);
- assert(keyId.size() == CLEARKEY_KEY_LEN);
+ KeyIdPair keyPair;
+
+ keyPair.mKeyId = KeyId(base, base + CLEARKEY_KEY_LEN);
+ assert(keyPair.mKeyId.size() == CLEARKEY_KEY_LEN);
- Key key(base + CLEARKEY_KEY_LEN, base + 2 * CLEARKEY_KEY_LEN);
- assert(key.size() == CLEARKEY_KEY_LEN);
+ keyPair.mKey = Key(base + CLEARKEY_KEY_LEN, base + 2 * CLEARKEY_KEY_LEN);
+ assert(keyPair.mKey.size() == CLEARKEY_KEY_LEN);
- session->AddKeyId(keyId);
+ session->AddKeyId(keyPair.mKeyId);
- mDecryptionManager->ExpectKeyId(keyId);
- mDecryptionManager->InitKey(keyId, key);
- mKeyIds.insert(key);
- mCallback->KeyStatusChanged(&aSessionId[0], aSessionId.size(),
- &keyId[0], keyId.size(),
- kGMPUsable);
+ mDecryptionManager->ExpectKeyId(keyPair.mKeyId);
+ mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
+ mKeyIds.insert(keyPair.mKey);
+
+ keyPairs.push_back(keyPair);
+ key_infos.push_back(GMPMediaKeyInfo(&keyPairs[i].mKeyId[0],
+ keyPairs[i].mKeyId.size(),
+ kGMPUsable));
}
+ mCallback->BatchedKeyStatusChanged(&aSessionId[0], aSessionId.size(),
+ key_infos.data(), key_infos.size());
mCallback->ResolveLoadSessionPromise(aPromiseId, true);
}
void
ClearKeySessionManager::UpdateSession(uint32_t aPromiseId,
const char* aSessionId,
uint32_t aSessionIdLength,
@@ -211,23 +220,27 @@ ClearKeySessionManager::UpdateSession(ui
// Parse the response for any (key ID, key) pairs.
vector<KeyIdPair> keyPairs;
if (!ClearKeyUtils::ParseJWK(aResponse, aResponseSize, keyPairs, session->Type())) {
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->KeyStatusChanged(aSessionId, aSessionIdLength,
- &it->mKeyId[0], it->mKeyId.size(),
- kGMPUsable);
+ vector<GMPMediaKeyInfo> key_infos;
+ for (size_t i = 0; i < keyPairs.size(); i++) {
+ KeyIdPair& keyPair = keyPairs[i];
+ mDecryptionManager->InitKey(keyPair.mKeyId, keyPair.mKey);
+ mKeyIds.insert(keyPair.mKeyId);
+ key_infos.push_back(GMPMediaKeyInfo(&keyPair.mKeyId[0],
+ keyPair.mKeyId.size(),
+ kGMPUsable));
}
+ mCallback->BatchedKeyStatusChanged(aSessionId, aSessionIdLength,
+ key_infos.data(), key_infos.size());
if (session->Type() != kGMPPersistentSession) {
mCallback->ResolvePromise(aPromiseId);
return;
}
// Store the keys on disk. We store a record whose name is the sessionId,
// and simply append each keyId followed by its key.