summaryrefslogtreecommitdiffstats
path: root/dom/media/eme/mediafoundation/WMFCDMProxy.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/eme/mediafoundation/WMFCDMProxy.cpp')
-rw-r--r--dom/media/eme/mediafoundation/WMFCDMProxy.cpp414
1 files changed, 414 insertions, 0 deletions
diff --git a/dom/media/eme/mediafoundation/WMFCDMProxy.cpp b/dom/media/eme/mediafoundation/WMFCDMProxy.cpp
new file mode 100644
index 0000000000..21207ecc22
--- /dev/null
+++ b/dom/media/eme/mediafoundation/WMFCDMProxy.cpp
@@ -0,0 +1,414 @@
+/* -*- 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/MediaKeysBinding.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozilla/dom/MediaKeySystemAccessBinding.h"
+#include "mozilla/EMEUtils.h"
+#include "mozilla/WMFCDMProxyCallback.h"
+#include "mozilla/WindowsVersion.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);
+ // SWDRM has a PMP process leakage problem on Windows 10 due to the internal
+ // issue in the media foundation. Microsoft only lands the solution on Windows
+ // 11 and doesn't have plan to port it back to Windows 10. Therefore, for
+ // PlayReady, we need to force to covert SL2000 to SL3000 for our underlying
+ // CDM to avoid that issue. In addition, as we only support L1 for Widevine,
+ // this issue won't happen on it.
+ Maybe<nsString> forcedRobustness =
+ IsPlayReadyKeySystemAndSupported(mKeySystem) && !IsWin11OrLater()
+ ? Some(nsString(u"3000"))
+ : Nothing();
+ WMFCDMImpl::InitParams params{
+ nsString(aOrigin),
+ mConfig.mInitDataTypes,
+ mPersistentStateRequired,
+ mDistinctiveIdentifierRequired,
+ mProxyCallback,
+ GenerateMFCDMMediaCapabilities(mConfig.mAudioCapabilities),
+ GenerateMFCDMMediaCapabilities(mConfig.mVideoCapabilities,
+ forcedRobustness)};
+ 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"));
+ });
+}
+
+CopyableTArray<MFCDMMediaCapability>
+WMFCDMProxy::GenerateMFCDMMediaCapabilities(
+ const dom::Sequence<dom::MediaKeySystemMediaCapability>& aCapabilities,
+ const Maybe<nsString>& forcedRobustness) {
+ CopyableTArray<MFCDMMediaCapability> outCapabilites;
+ for (const auto& capabilities : aCapabilities) {
+ if (!forcedRobustness) {
+ EME_LOG("WMFCDMProxy::Init %p, robustness=%s", this,
+ NS_ConvertUTF16toUTF8(capabilities.mRobustness).get());
+ outCapabilites.AppendElement(MFCDMMediaCapability{
+ capabilities.mContentType, capabilities.mRobustness});
+ } else {
+ EME_LOG("WMFCDMProxy::Init %p, force to robustness=%s", this,
+ NS_ConvertUTF16toUTF8(*forcedRobustness).get());
+ outCapabilites.AppendElement(
+ MFCDMMediaCapability{capabilities.mContentType, *forcedRobustness});
+ }
+ }
+ return outCapabilites;
+}
+
+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::ResolvePromiseWithKeyStatus(
+ const PromiseId& aId, const dom::MediaKeyStatus& aStatus) {
+ auto resolve = [self = RefPtr{this}, this, aId, aStatus]() {
+ RETURN_IF_SHUTDOWN();
+ EME_LOG("WMFCDMProxy::ResolvePromiseWithKeyStatus(this=%p, pid=%" PRIu32
+ ", status=%s)",
+ this, aId, ToMediaKeyStatusStr(aStatus));
+ if (!mKeys.IsNull()) {
+ mKeys->ResolvePromiseWithKeyStatus(aId, aStatus);
+ } else {
+ NS_WARNING("WMFCDMProxy unable to resolve promise!");
+ }
+ };
+
+ if (NS_IsMainThread()) {
+ resolve();
+ return;
+ }
+ mMainThread->Dispatch(NS_NewRunnableFunction(
+ "WMFCDMProxy::ResolvePromiseWithKeyStatus", 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));
+ }
+}
+
+void WMFCDMProxy::SetServerCertificate(PromiseId aPromiseId,
+ nsTArray<uint8_t>& aCert) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RETURN_IF_SHUTDOWN();
+ EME_LOG("WMFCDMProxy::SetServerCertificate(this=%p, pid=%" PRIu32 ")", this,
+ aPromiseId);
+ mCDM->SetServerCertificate(aPromiseId, aCert)
+ ->Then(
+ mMainThread, __func__,
+ [self = RefPtr{this}, this, aPromiseId]() {
+ RETURN_IF_SHUTDOWN();
+ ResolvePromise(aPromiseId);
+ },
+ [self = RefPtr{this}, this, aPromiseId]() {
+ RETURN_IF_SHUTDOWN();
+ RejectPromiseWithStateError(
+ aPromiseId,
+ nsLiteralCString("WMFCDMProxy::SetServerCertificate failed!"));
+ });
+}
+
+void WMFCDMProxy::GetStatusForPolicy(PromiseId aPromiseId,
+ const dom::HDCPVersion& aMinHdcpVersion) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RETURN_IF_SHUTDOWN();
+ EME_LOG("WMFCDMProxy::GetStatusForPolicy(this=%p, pid=%" PRIu32
+ ", minHDCP=%s)",
+ this, aPromiseId,
+ dom::HDCPVersionValues::GetString(aMinHdcpVersion).data());
+ mCDM->GetStatusForPolicy(aPromiseId, aMinHdcpVersion)
+ ->Then(
+ mMainThread, __func__,
+ [self = RefPtr{this}, this, aPromiseId]() {
+ RETURN_IF_SHUTDOWN();
+ ResolvePromiseWithKeyStatus(aPromiseId,
+ dom::MediaKeyStatus::Usable);
+ },
+ [self = RefPtr{this}, this, aPromiseId]() {
+ RETURN_IF_SHUTDOWN();
+ ResolvePromiseWithKeyStatus(aPromiseId,
+ dom::MediaKeyStatus::Output_restricted);
+ });
+}
+
+bool WMFCDMProxy::IsHardwareDecryptionSupported() const {
+ return mozilla::IsHardwareDecryptionSupported(mConfig);
+}
+
+uint64_t WMFCDMProxy::GetCDMProxyId() const {
+ MOZ_DIAGNOSTIC_ASSERT(mCDM);
+ return mCDM->Id();
+}
+
+#undef LOG
+#undef RETURN_IF_SHUTDOWN
+#undef PERFORM_ON_CDM
+
+} // namespace mozilla