diff options
Diffstat (limited to '')
-rw-r--r-- | dom/media/eme/mediafoundation/WMFCDMImpl.cpp | 128 | ||||
-rw-r--r-- | dom/media/eme/mediafoundation/WMFCDMImpl.h | 100 | ||||
-rw-r--r-- | dom/media/eme/mediafoundation/WMFCDMProxy.cpp | 306 | ||||
-rw-r--r-- | dom/media/eme/mediafoundation/WMFCDMProxy.h | 134 | ||||
-rw-r--r-- | dom/media/eme/mediafoundation/WMFCDMProxyCallback.cpp | 72 | ||||
-rw-r--r-- | dom/media/eme/mediafoundation/WMFCDMProxyCallback.h | 38 | ||||
-rw-r--r-- | dom/media/eme/mediafoundation/moz.build | 21 |
7 files changed, 799 insertions, 0 deletions
diff --git a/dom/media/eme/mediafoundation/WMFCDMImpl.cpp b/dom/media/eme/mediafoundation/WMFCDMImpl.cpp new file mode 100644 index 0000000000..c2ca31bf15 --- /dev/null +++ b/dom/media/eme/mediafoundation/WMFCDMImpl.cpp @@ -0,0 +1,128 @@ +/* -*- 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/. */ + +#include "WMFCDMImpl.h" + +#include "mozilla/dom/MediaKeySession.h" + +namespace mozilla { + +/* static */ +bool WMFCDMImpl::Supports(const nsAString& aKeySystem) { + static std::map<nsString, bool> supports; + + nsString key(aKeySystem); + if (const auto& s = supports.find(key); s != supports.end()) { + return s->second; + } + + RefPtr<WMFCDMImpl> cdm = MakeRefPtr<WMFCDMImpl>(aKeySystem); + KeySystemConfig c; + bool s = cdm->GetCapabilities(c); + supports[key] = s; + + return s; +} + +static void MFCDMCapabilitiesIPDLToKeySystemConfig( + const MFCDMCapabilitiesIPDL& aCDMConfig, + KeySystemConfig& aKeySystemConfig) { + aKeySystemConfig.mKeySystem = aCDMConfig.keySystem(); + + for (const auto& type : aCDMConfig.initDataTypes()) { + aKeySystemConfig.mInitDataTypes.AppendElement(type); + } + + for (const auto& type : aCDMConfig.sessionTypes()) { + aKeySystemConfig.mSessionTypes.AppendElement(type); + } + + for (const auto& c : aCDMConfig.videoCapabilities()) { + if (!c.robustness().IsEmpty() && + !aKeySystemConfig.mVideoRobustness.Contains(c.robustness())) { + aKeySystemConfig.mVideoRobustness.AppendElement(c.robustness()); + } + aKeySystemConfig.mMP4.SetCanDecryptAndDecode( + NS_ConvertUTF16toUTF8(c.contentType())); + } + for (const auto& c : aCDMConfig.audioCapabilities()) { + if (!c.robustness().IsEmpty() && + !aKeySystemConfig.mAudioRobustness.Contains(c.robustness())) { + aKeySystemConfig.mAudioRobustness.AppendElement(c.robustness()); + } + aKeySystemConfig.mMP4.SetCanDecryptAndDecode( + NS_ConvertUTF16toUTF8(c.contentType())); + } + aKeySystemConfig.mPersistentState = aCDMConfig.persistentState(); + aKeySystemConfig.mDistinctiveIdentifier = aCDMConfig.distinctiveID(); +} + +static const char* EncryptionSchemeStr(const CryptoScheme aScheme) { + switch (aScheme) { + case CryptoScheme::None: + return "none"; + case CryptoScheme::Cenc: + return "cenc"; + case CryptoScheme::Cbcs: + return "cbcs"; + } +} + +bool WMFCDMImpl::GetCapabilities(KeySystemConfig& aConfig) { + nsCOMPtr<nsISerialEventTarget> backgroundTaskQueue; + NS_CreateBackgroundTaskQueue(__func__, getter_AddRefs(backgroundTaskQueue)); + + bool ok = false; + media::Await( + backgroundTaskQueue.forget(), mCDM->GetCapabilities(), + [&ok, &aConfig](const MFCDMCapabilitiesIPDL& capabilities) { + EME_LOG("capabilities: keySystem=%s", + NS_ConvertUTF16toUTF8(capabilities.keySystem()).get()); + for (const auto& v : capabilities.videoCapabilities()) { + EME_LOG("capabilities: video=%s", + NS_ConvertUTF16toUTF8(v.contentType()).get()); + } + for (const auto& a : capabilities.audioCapabilities()) { + EME_LOG("capabilities: audio=%s", + NS_ConvertUTF16toUTF8(a.contentType()).get()); + } + for (const auto& v : capabilities.encryptionSchemes()) { + EME_LOG("capabilities: encryptionScheme=%s", EncryptionSchemeStr(v)); + } + MFCDMCapabilitiesIPDLToKeySystemConfig(capabilities, aConfig); + ok = true; + }, + [](nsresult rv) { + EME_LOG("Fail to get key system capabilities. rv=%x", rv); + }); + return ok; +} + +RefPtr<WMFCDMImpl::InitPromise> WMFCDMImpl::Init( + const WMFCDMImpl::InitParams& aParams) { + MOZ_ASSERT(mCDM); + + RefPtr<WMFCDMImpl> self = this; + mCDM->Init(aParams.mOrigin, aParams.mInitDataTypes, + aParams.mPersistentStateRequired + ? KeySystemConfig::Requirement::Required + : KeySystemConfig::Requirement::Optional, + aParams.mDistinctiveIdentifierRequired + ? KeySystemConfig::Requirement::Required + : KeySystemConfig::Requirement::Optional, + aParams.mHWSecure, aParams.mProxyCallback) + ->Then( + mCDM->ManagerThread(), __func__, + [self, this](const MFCDMInitIPDL& init) { + mInitPromiseHolder.ResolveIfExists(true, __func__); + }, + [self, this](const nsresult rv) { + mInitPromiseHolder.RejectIfExists(rv, __func__); + }); + return mInitPromiseHolder.Ensure(__func__); +} + +} // namespace mozilla diff --git a/dom/media/eme/mediafoundation/WMFCDMImpl.h b/dom/media/eme/mediafoundation/WMFCDMImpl.h new file mode 100644 index 0000000000..fc4ef840d6 --- /dev/null +++ b/dom/media/eme/mediafoundation/WMFCDMImpl.h @@ -0,0 +1,100 @@ +/* -*- 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 DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMIMPL_H_ +#define DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMIMPL_H_ + +#include "MediaData.h" +#include "mozilla/Assertions.h" +#include "mozilla/EMEUtils.h" +#include "mozilla/KeySystemConfig.h" +#include "mozilla/media/MediaUtils.h" +#include "mozilla/MFCDMChild.h" +#include "nsString.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +class WMFCDMProxyCallback; + +/** + * WMFCDMImpl is a helper class for MFCDM protocol clients. It creates, manages, + * and calls MFCDMChild object in the content process on behalf of the client, + * and performs conversion between EME and MFCDM types and constants. + */ +class WMFCDMImpl final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WMFCDMImpl); + + explicit WMFCDMImpl(const nsAString& aKeySystem) + : mCDM(MakeRefPtr<MFCDMChild>(aKeySystem)) {} + + static bool Supports(const nsAString& aKeySystem); + // TODO: make this async? + bool GetCapabilities(KeySystemConfig& aConfig); + + using InitPromise = GenericPromise; + struct InitParams { + nsString mOrigin; + CopyableTArray<nsString> mInitDataTypes; + bool mPersistentStateRequired; + bool mDistinctiveIdentifierRequired; + bool mHWSecure; + WMFCDMProxyCallback* mProxyCallback; + }; + + RefPtr<InitPromise> Init(const InitParams& aParams); + + RefPtr<MFCDMChild::SessionPromise> CreateSession( + uint32_t aPromiseId, const KeySystemConfig::SessionType aSessionType, + const nsAString& aInitDataType, const nsTArray<uint8_t>& aInitData) { + return mCDM->CreateSessionAndGenerateRequest(aPromiseId, aSessionType, + aInitDataType, aInitData); + } + + RefPtr<GenericPromise> LoadSession( + uint32_t aPromiseId, const KeySystemConfig::SessionType aSessionType, + const nsAString& aSessionId) { + return mCDM->LoadSession(aPromiseId, aSessionType, aSessionId); + } + + RefPtr<GenericPromise> UpdateSession(uint32_t aPromiseId, + const nsAString& aSessionId, + nsTArray<uint8_t>& aResponse) { + return mCDM->UpdateSession(aPromiseId, aSessionId, aResponse); + } + + RefPtr<GenericPromise> CloseSession(uint32_t aPromiseId, + const nsAString& aSessionId) { + return mCDM->CloseSession(aPromiseId, aSessionId); + } + + RefPtr<GenericPromise> RemoveSession(uint32_t aPromiseId, + const nsAString& aSessionId) { + return mCDM->RemoveSession(aPromiseId, aSessionId); + } + + uint64_t Id() { + MOZ_ASSERT(mCDM->Id() != 0, + "Should be called only after Init() is resolved"); + return mCDM->Id(); + } + + private: + ~WMFCDMImpl() { + if (mCDM) { + mCDM->Shutdown(); + } + }; + + const RefPtr<MFCDMChild> mCDM; + + MozPromiseHolder<InitPromise> mInitPromiseHolder; +}; + +} // namespace mozilla + +#endif // DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMIMPL_H_ diff --git a/dom/media/eme/mediafoundation/WMFCDMProxy.cpp b/dom/media/eme/mediafoundation/WMFCDMProxy.cpp new file mode 100644 index 0000000000..1d691168d8 --- /dev/null +++ b/dom/media/eme/mediafoundation/WMFCDMProxy.cpp @@ -0,0 +1,306 @@ +/* -*- 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/. */ + +#include "WMFCDMProxy.h" + +#include "mozilla/dom/MediaKeySession.h" +#include "mozilla/WMFCDMProxyCallback.h" +#include "WMFCDMImpl.h" +#include "WMFCDMProxyCallback.h" + +namespace mozilla { + +#define LOG(msg, ...) \ + EME_LOG("WMFCDMProxy[%p]@%s: " msg, this, __func__, ##__VA_ARGS__) + +#define RETURN_IF_SHUTDOWN() \ + do { \ + MOZ_ASSERT(NS_IsMainThread()); \ + if (mIsShutdown) { \ + return; \ + } \ + } while (false) + +#define PERFORM_ON_CDM(operation, promiseId, ...) \ + do { \ + mCDM->operation(promiseId, __VA_ARGS__) \ + ->Then( \ + mMainThread, __func__, \ + [self = RefPtr{this}, this, promiseId]() { \ + RETURN_IF_SHUTDOWN(); \ + if (mKeys.IsNull()) { \ + EME_LOG("WMFCDMProxy(this=%p, pid=%" PRIu32 \ + ") : abort the " #operation " due to empty key", \ + this, promiseId); \ + return; \ + } \ + ResolvePromise(promiseId); \ + }, \ + [self = RefPtr{this}, this, promiseId]() { \ + RETURN_IF_SHUTDOWN(); \ + RejectPromiseWithStateError( \ + promiseId, nsLiteralCString("WMFCDMProxy::" #operation ": " \ + "failed to " #operation)); \ + }); \ + } while (false) + +WMFCDMProxy::WMFCDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem, + const dom::MediaKeySystemConfiguration& aConfig) + : CDMProxy( + aKeys, aKeySystem, + aConfig.mDistinctiveIdentifier == dom::MediaKeysRequirement::Required, + aConfig.mPersistentState == dom::MediaKeysRequirement::Required), + mConfig(aConfig) { + MOZ_ASSERT(NS_IsMainThread()); +} + +WMFCDMProxy::~WMFCDMProxy() {} + +void WMFCDMProxy::Init(PromiseId aPromiseId, const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, + const nsAString& aName) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aOrigin.IsEmpty()); + + MOZ_ASSERT(!mOwnerThread); + if (NS_FAILED( + NS_NewNamedThread("WMFCDMThread", getter_AddRefs(mOwnerThread)))) { + RejectPromiseWithStateError( + aPromiseId, + nsLiteralCString("WMFCDMProxy::Init: couldn't create CDM thread")); + return; + } + + mCDM = MakeRefPtr<WMFCDMImpl>(mKeySystem); + mProxyCallback = new WMFCDMProxyCallback(this); + WMFCDMImpl::InitParams params{nsString(aOrigin), + mConfig.mInitDataTypes, + mPersistentStateRequired, + mDistinctiveIdentifierRequired, + false /* TODO : support HW secure */, + mProxyCallback}; + mCDM->Init(params)->Then( + mMainThread, __func__, + [self = RefPtr{this}, this, aPromiseId](const bool) { + MOZ_ASSERT(mCDM->Id() > 0); + mKeys->OnCDMCreated(aPromiseId, mCDM->Id()); + }, + [self = RefPtr{this}, this, aPromiseId](const nsresult rv) { + RejectPromiseWithStateError( + aPromiseId, + nsLiteralCString("WMFCDMProxy::Init: WMFCDM init error")); + }); +} + +void WMFCDMProxy::ResolvePromise(PromiseId aId) { + auto resolve = [self = RefPtr{this}, this, aId]() { + RETURN_IF_SHUTDOWN(); + EME_LOG("WMFCDMProxy::ResolvePromise(this=%p, pid=%" PRIu32 ")", this, aId); + if (!mKeys.IsNull()) { + mKeys->ResolvePromise(aId); + } else { + NS_WARNING("WMFCDMProxy unable to resolve promise!"); + } + }; + + if (NS_IsMainThread()) { + resolve(); + return; + } + mMainThread->Dispatch( + NS_NewRunnableFunction("WMFCDMProxy::ResolvePromise", resolve)); +} + +void WMFCDMProxy::RejectPromise(PromiseId aId, ErrorResult&& aException, + const nsCString& aReason) { + if (!NS_IsMainThread()) { + // Use CopyableErrorResult to store our exception in the runnable, + // because ErrorResult is not OK to move across threads. + mMainThread->Dispatch( + NewRunnableMethod<PromiseId, StoreCopyPassByRRef<CopyableErrorResult>, + nsCString>("WMFCDMProxy::RejectPromise", this, + &WMFCDMProxy::RejectPromiseOnMainThread, + aId, std::move(aException), aReason), + NS_DISPATCH_NORMAL); + return; + } + EME_LOG("WMFCDMProxy::RejectPromise(this=%p, pid=%" PRIu32 + ", code=0x%x, " + "reason='%s')", + this, aId, aException.ErrorCodeAsInt(), aReason.get()); + if (!mKeys.IsNull()) { + mKeys->RejectPromise(aId, std::move(aException), aReason); + } else { + // We don't have a MediaKeys object to pass the exception to, so silence + // the exception to avoid it asserting due to being unused. + aException.SuppressException(); + } +} + +void WMFCDMProxy::RejectPromiseOnMainThread(PromiseId aId, + CopyableErrorResult&& aException, + const nsCString& aReason) { + RETURN_IF_SHUTDOWN(); + // Moving into or out of a non-copyable ErrorResult will assert that both + // ErorResults are from our current thread. Avoid the assertion by moving + // into a current-thread CopyableErrorResult first. Note that this is safe, + // because CopyableErrorResult never holds state that can't move across + // threads. + CopyableErrorResult rv(std::move(aException)); + RejectPromise(aId, std::move(rv), aReason); +} + +void WMFCDMProxy::RejectPromiseWithStateError(PromiseId aId, + const nsCString& aReason) { + ErrorResult rv; + rv.ThrowInvalidStateError(aReason); + RejectPromise(aId, std::move(rv), aReason); +} + +void WMFCDMProxy::CreateSession(uint32_t aCreateSessionToken, + MediaKeySessionType aSessionType, + PromiseId aPromiseId, + const nsAString& aInitDataType, + nsTArray<uint8_t>& aInitData) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + const auto sessionType = ConvertToKeySystemConfigSessionType(aSessionType); + EME_LOG("WMFCDMProxy::CreateSession(this=%p, pid=%" PRIu32 + "), sessionType=%s", + this, aPromiseId, SessionTypeToStr(sessionType)); + mCDM->CreateSession(aPromiseId, sessionType, aInitDataType, aInitData) + ->Then( + mMainThread, __func__, + [self = RefPtr{this}, this, aCreateSessionToken, + aPromiseId](nsString sessionID) { + RETURN_IF_SHUTDOWN(); + if (mKeys.IsNull()) { + EME_LOG("WMFCDMProxy(this=%p, pid=%" PRIu32 + ") : abort the create session due to " + "empty key", + this, aPromiseId); + return; + } + if (RefPtr<dom::MediaKeySession> session = + mKeys->GetPendingSession(aCreateSessionToken)) { + session->SetSessionId(std::move(sessionID)); + } + ResolvePromise(aPromiseId); + }, + [self = RefPtr{this}, this, aPromiseId]() { + RETURN_IF_SHUTDOWN(); + RejectPromiseWithStateError( + aPromiseId, + nsLiteralCString( + "WMFCDMProxy::CreateSession: cannot create session")); + }); +} + +void WMFCDMProxy::LoadSession(PromiseId aPromiseId, + dom::MediaKeySessionType aSessionType, + const nsAString& aSessionId) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + const auto sessionType = ConvertToKeySystemConfigSessionType(aSessionType); + EME_LOG("WMFCDMProxy::LoadSession(this=%p, pid=%" PRIu32 + "), sessionType=%s, sessionId=%s", + this, aPromiseId, SessionTypeToStr(sessionType), + NS_ConvertUTF16toUTF8(aSessionId).get()); + PERFORM_ON_CDM(LoadSession, aPromiseId, sessionType, aSessionId); +} + +void WMFCDMProxy::UpdateSession(const nsAString& aSessionId, + PromiseId aPromiseId, + nsTArray<uint8_t>& aResponse) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + EME_LOG("WMFCDMProxy::UpdateSession(this=%p, pid=%" PRIu32 + "), sessionId=%s, responseLen=%zu", + this, aPromiseId, NS_ConvertUTF16toUTF8(aSessionId).get(), + aResponse.Length()); + PERFORM_ON_CDM(UpdateSession, aPromiseId, aSessionId, aResponse); +} + +void WMFCDMProxy::CloseSession(const nsAString& aSessionId, + PromiseId aPromiseId) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + EME_LOG("WMFCDMProxy::CloseSession(this=%p, pid=%" PRIu32 "), sessionId=%s", + this, aPromiseId, NS_ConvertUTF16toUTF8(aSessionId).get()); + PERFORM_ON_CDM(CloseSession, aPromiseId, aSessionId); +} + +void WMFCDMProxy::RemoveSession(const nsAString& aSessionId, + PromiseId aPromiseId) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + EME_LOG("WMFCDMProxy::RemoveSession(this=%p, pid=%" PRIu32 "), sessionId=%s", + this, aPromiseId, NS_ConvertUTF16toUTF8(aSessionId).get()); + PERFORM_ON_CDM(RemoveSession, aPromiseId, aSessionId); +} + +void WMFCDMProxy::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mIsShutdown); + if (mProxyCallback) { + mProxyCallback->Shutdown(); + mProxyCallback = nullptr; + } + mIsShutdown = true; +} + +void WMFCDMProxy::OnSessionMessage(const nsAString& aSessionId, + dom::MediaKeyMessageType aMessageType, + const nsTArray<uint8_t>& aMessage) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + if (mKeys.IsNull()) { + return; + } + if (RefPtr<dom::MediaKeySession> session = mKeys->GetSession(aSessionId)) { + LOG("Notify key message for session Id=%s", + NS_ConvertUTF16toUTF8(aSessionId).get()); + session->DispatchKeyMessage(aMessageType, aMessage); + } +} + +void WMFCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + if (mKeys.IsNull()) { + return; + } + if (RefPtr<dom::MediaKeySession> session = mKeys->GetSession(aSessionId)) { + LOG("Notify key statuses for session Id=%s", + NS_ConvertUTF16toUTF8(aSessionId).get()); + session->DispatchKeyStatusesChange(); + } +} + +void WMFCDMProxy::OnExpirationChange(const nsAString& aSessionId, + UnixTime aExpiryTime) { + MOZ_ASSERT(NS_IsMainThread()); + RETURN_IF_SHUTDOWN(); + if (mKeys.IsNull()) { + return; + } + if (RefPtr<dom::MediaKeySession> session = mKeys->GetSession(aSessionId)) { + LOG("Notify expiration for session Id=%s", + NS_ConvertUTF16toUTF8(aSessionId).get()); + session->SetExpiration(static_cast<double>(aExpiryTime)); + } +} + +uint64_t WMFCDMProxy::GetCDMProxyId() const { + MOZ_DIAGNOSTIC_ASSERT(mCDM); + return mCDM->Id(); +} + +#undef LOG +#undef RETURN_IF_SHUTDOWN +#undef PERFORM_ON_CDM + +} // namespace mozilla diff --git a/dom/media/eme/mediafoundation/WMFCDMProxy.h b/dom/media/eme/mediafoundation/WMFCDMProxy.h new file mode 100644 index 0000000000..7766b73f21 --- /dev/null +++ b/dom/media/eme/mediafoundation/WMFCDMProxy.h @@ -0,0 +1,134 @@ +/* -*- 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 DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMPROXY_H_ +#define DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMPROXY_H_ + +#include "mozilla/CDMProxy.h" +#include "mozilla/CDMCaps.h" +#include "mozilla/MozPromise.h" +#include "mozilla/dom/MediaKeys.h" +#include "mozilla/dom/MediaKeySession.h" +#include "mozilla/RefPtr.h" +#include "mozilla/WMFCDMImpl.h" + +#include "nsString.h" + +namespace mozilla { + +class WMFCDMProxyCallback; + +class WMFCDMProxy : public CDMProxy { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WMFCDMProxy, override) + + WMFCDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem, + const dom::MediaKeySystemConfiguration& aConfig); + + // CDMProxy interface + void Init(PromiseId aPromiseId, const nsAString& aOrigin, + const nsAString& aTopLevelOrigin, const nsAString& aName) override; + + void CreateSession(uint32_t aCreateSessionToken, + MediaKeySessionType aSessionType, PromiseId aPromiseId, + const nsAString& aInitDataType, + nsTArray<uint8_t>& aInitData) override; + + void LoadSession(PromiseId aPromiseId, dom::MediaKeySessionType aSessionType, + const nsAString& aSessionId) override; + + void SetServerCertificate(PromiseId aPromiseId, + nsTArray<uint8_t>& aCert) override {} + + void UpdateSession(const nsAString& aSessionId, PromiseId aPromiseId, + nsTArray<uint8_t>& aResponse) override; + + void CloseSession(const nsAString& aSessionId, PromiseId aPromiseId) override; + + void RemoveSession(const nsAString& aSessionId, + PromiseId aPromiseId) override; + + void QueryOutputProtectionStatus() override {} + + void NotifyOutputProtectionStatus( + OutputProtectionCheckStatus aCheckStatus, + OutputProtectionCaptureStatus aCaptureStatus) override {} + + void Shutdown() override; + + void Terminated() override {} + + void OnSetSessionId(uint32_t aCreateSessionToken, + const nsAString& aSessionId) override {} + + void OnResolveLoadSessionPromise(uint32_t aPromiseId, + bool aSuccess) override {} + + void OnSessionMessage(const nsAString& aSessionId, + dom::MediaKeyMessageType aMessageType, + const nsTArray<uint8_t>& aMessage) override; + + void OnExpirationChange(const nsAString& aSessionId, + UnixTime aExpiryTime) override; + + void OnSessionClosed(const nsAString& aSessionId) override {} + + void OnSessionError(const nsAString& aSessionId, nsresult aException, + uint32_t aSystemCode, const nsAString& aMsg) override {} + + void OnRejectPromise(uint32_t aPromiseId, ErrorResult&& aException, + const nsCString& aMsg) override {} + + RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample) override { + return nullptr; + } + void OnDecrypted(uint32_t aId, DecryptStatus aResult, + const nsTArray<uint8_t>& aDecryptedData) override {} + + void ResolvePromise(PromiseId aId) override; + void RejectPromise(PromiseId aId, ErrorResult&& aException, + const nsCString& aReason) override; + + void OnKeyStatusesChange(const nsAString& aSessionId) override; + + void GetStatusForPolicy(PromiseId aPromiseId, + const nsAString& aMinHdcpVersion) override {} + +#ifdef DEBUG + bool IsOnOwnerThread() override { + return NS_GetCurrentThread() == mOwnerThread; + } +#endif + + WMFCDMProxy* AsWMFCDMProxy() override { return this; } + + // Can only be called after initialization succeeded. + uint64_t GetCDMProxyId() const; + + private: + virtual ~WMFCDMProxy(); + + template <typename T> + void ResolvePromiseWithResult(PromiseId aId, const T& aResult) {} + void RejectPromiseOnMainThread(PromiseId aId, + CopyableErrorResult&& aException, + const nsCString& aReason); + // Reject promise with an InvalidStateError and the given message. + void RejectPromiseWithStateError(PromiseId aId, const nsCString& aReason); + + RefPtr<WMFCDMImpl> mCDM; + + const dom::MediaKeySystemConfiguration mConfig; + + RefPtr<WMFCDMProxyCallback> mProxyCallback; + + // It can only be used on the main thread. + bool mIsShutdown = false; +}; + +} // namespace mozilla + +#endif // DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMPROXY_H_ diff --git a/dom/media/eme/mediafoundation/WMFCDMProxyCallback.cpp b/dom/media/eme/mediafoundation/WMFCDMProxyCallback.cpp new file mode 100644 index 0000000000..6464cec4c7 --- /dev/null +++ b/dom/media/eme/mediafoundation/WMFCDMProxyCallback.cpp @@ -0,0 +1,72 @@ +/* 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 "WMFCDMProxyCallback.h" + +#include "mozilla/WMFCDMProxy.h" + +#define RETURN_IF_NULL(proxy) \ + if (!proxy) { \ + return; \ + } + +namespace mozilla { + +WMFCDMProxyCallback::WMFCDMProxyCallback(WMFCDMProxy* aProxy) : mProxy(aProxy) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mProxy); +} + +WMFCDMProxyCallback::~WMFCDMProxyCallback() { MOZ_ASSERT(!mProxy); } + +void WMFCDMProxyCallback::OnSessionMessage(const MFCDMKeyMessage& aMessage) { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "WMFCDMProxyCallback::OnSessionMessage", + [self = RefPtr{this}, this, message = aMessage]() { + RETURN_IF_NULL(mProxy); + mProxy->OnSessionMessage(message.sessionId(), message.type(), + std::move(message.message())); + })); +} + +void WMFCDMProxyCallback::OnSessionKeyStatusesChange( + const MFCDMKeyStatusChange& aKeyStatuses) { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "WMFCDMProxyCallback::OnSessionKeyStatusesChange", + [self = RefPtr{this}, this, keyStatuses = aKeyStatuses]() { + RETURN_IF_NULL(mProxy); + bool keyStatusesChange = false; + { + auto caps = mProxy->Capabilites().Lock(); + for (const auto& keyInfo : keyStatuses.keyInfo()) { + keyStatusesChange |= caps->SetKeyStatus( + keyInfo.keyId(), keyStatuses.sessionId(), + dom::Optional<dom::MediaKeyStatus>(keyInfo.status())); + } + } + if (keyStatusesChange) { + mProxy->OnKeyStatusesChange(keyStatuses.sessionId()); + } + })); +} + +void WMFCDMProxyCallback::OnSessionKeyExpiration( + const MFCDMKeyExpiration& aExpiration) { + NS_DispatchToMainThread(NS_NewRunnableFunction( + "WMFCDMProxyCallback::OnSessionKeyExpiration", + [self = RefPtr{this}, this, expiration = aExpiration]() { + RETURN_IF_NULL(mProxy); + mProxy->OnExpirationChange( + expiration.sessionId(), + expiration.expiredTimeMilliSecondsSinceEpoch()); + })); +} + +void WMFCDMProxyCallback::Shutdown() { + MOZ_ASSERT(NS_IsMainThread()); + mProxy = nullptr; +} + +#undef RETURN_IF_NULL +} // namespace mozilla diff --git a/dom/media/eme/mediafoundation/WMFCDMProxyCallback.h b/dom/media/eme/mediafoundation/WMFCDMProxyCallback.h new file mode 100644 index 0000000000..6e76f63ccc --- /dev/null +++ b/dom/media/eme/mediafoundation/WMFCDMProxyCallback.h @@ -0,0 +1,38 @@ +/* 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 DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMPROXYCALLBACK_H_ +#define DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMPROXYCALLBACK_H_ + +#include "mozilla/PMFCDM.h" +#include "nsThreadUtils.h" + +namespace mozilla { + +class WMFCDMProxy; + +// This class is used to notify CDM related events called from MFCDMChild, and +// it will forward the relative calls to WMFCDMProxy on the main thread. +class WMFCDMProxyCallback final { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WMFCDMProxyCallback); + + explicit WMFCDMProxyCallback(WMFCDMProxy* aProxy); + + void OnSessionMessage(const MFCDMKeyMessage& aMessage); + + void OnSessionKeyStatusesChange(const MFCDMKeyStatusChange& aKeyStatuses); + + void OnSessionKeyExpiration(const MFCDMKeyExpiration& aExpiration); + + void Shutdown(); + + private: + ~WMFCDMProxyCallback(); + RefPtr<WMFCDMProxy> mProxy; +}; + +} // namespace mozilla + +#endif // DOM_MEDIA_EME_MEDIAFOUNDATION_WMFCDMPROXYCALLBACK_H_ diff --git a/dom/media/eme/mediafoundation/moz.build b/dom/media/eme/mediafoundation/moz.build new file mode 100644 index 0000000000..8c54b381ff --- /dev/null +++ b/dom/media/eme/mediafoundation/moz.build @@ -0,0 +1,21 @@ +# -*- 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.mozilla += [ + "WMFCDMImpl.h", + "WMFCDMProxy.h", + "WMFCDMProxyCallback.h", +] + +UNIFIED_SOURCES += [ + "WMFCDMImpl.cpp", + "WMFCDMProxy.cpp", + "WMFCDMProxyCallback.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" |