summaryrefslogtreecommitdiffstats
path: root/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp')
-rw-r--r--dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp457
1 files changed, 457 insertions, 0 deletions
diff --git a/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp b/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
new file mode 100644
index 0000000000..521db07d16
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
@@ -0,0 +1,457 @@
+/* -*- 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 "mozilla/dom/MediaKeySession.h"
+#include "mozilla/MediaDrmCDMProxy.h"
+#include "MediaDrmCDMCallbackProxy.h"
+
+namespace mozilla {
+
+MediaDrmSessionType ToMediaDrmSessionType(
+ dom::MediaKeySessionType aSessionType) {
+ switch (aSessionType) {
+ case dom::MediaKeySessionType::Temporary:
+ return kKeyStreaming;
+ case dom::MediaKeySessionType::Persistent_license:
+ return kKeyOffline;
+ default:
+ return kKeyStreaming;
+ };
+}
+
+MediaDrmCDMProxy::MediaDrmCDMProxy(dom::MediaKeys* aKeys,
+ const nsAString& aKeySystem,
+ bool aDistinctiveIdentifierRequired,
+ bool aPersistentStateRequired)
+ : CDMProxy(aKeys, aKeySystem, aDistinctiveIdentifierRequired,
+ aPersistentStateRequired),
+ mCDM(nullptr),
+ mShutdownCalled(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_COUNT_CTOR(MediaDrmCDMProxy);
+}
+
+MediaDrmCDMProxy::~MediaDrmCDMProxy() { MOZ_COUNT_DTOR(MediaDrmCDMProxy); }
+
+void MediaDrmCDMProxy::Init(PromiseId aPromiseId, const nsAString& aOrigin,
+ const nsAString& aTopLevelOrigin,
+ const nsAString& aName) {
+ MOZ_ASSERT(NS_IsMainThread());
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ EME_LOG("MediaDrmCDMProxy::Init (%s, %s) %s",
+ NS_ConvertUTF16toUTF8(aOrigin).get(),
+ NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
+ NS_ConvertUTF16toUTF8(aName).get());
+
+ // Create a thread to work with cdm.
+ if (!mOwnerThread) {
+ nsresult rv =
+ NS_NewNamedThread("MDCDMThread", getter_AddRefs(mOwnerThread));
+ if (NS_FAILED(rv)) {
+ RejectPromiseWithStateError(
+ aPromiseId, nsLiteralCString(
+ "Couldn't create CDM thread MediaDrmCDMProxy::Init"));
+ return;
+ }
+ }
+
+ mCDM = mozilla::MakeUnique<MediaDrmProxySupport>(mKeySystem);
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<uint32_t>("MediaDrmCDMProxy::md_Init", this,
+ &MediaDrmCDMProxy::md_Init, aPromiseId));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void MediaDrmCDMProxy::CreateSession(uint32_t aCreateSessionToken,
+ MediaKeySessionType aSessionType,
+ PromiseId aPromiseId,
+ const nsAString& aInitDataType,
+ nsTArray<uint8_t>& aInitData) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+
+ UniquePtr<CreateSessionData> data(new CreateSessionData());
+ data->mSessionType = aSessionType;
+ data->mCreateSessionToken = aCreateSessionToken;
+ data->mPromiseId = aPromiseId;
+ data->mInitDataType = NS_ConvertUTF16toUTF8(aInitDataType);
+ data->mInitData = std::move(aInitData);
+
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<CreateSessionData>&&>(
+ "MediaDrmCDMProxy::md_CreateSession", this,
+ &MediaDrmCDMProxy::md_CreateSession, std::move(data)));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void MediaDrmCDMProxy::LoadSession(PromiseId aPromiseId,
+ dom::MediaKeySessionType aSessionType,
+ const nsAString& aSessionId) {
+ // TODO: Implement LoadSession.
+ RejectPromiseWithStateError(
+ aPromiseId, "Currently Fennec does not support LoadSession"_ns);
+}
+
+void MediaDrmCDMProxy::SetServerCertificate(PromiseId aPromiseId,
+ nsTArray<uint8_t>& aCert) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+
+ mOwnerThread->Dispatch(NewRunnableMethod<PromiseId, const nsTArray<uint8_t>>(
+ "MediaDrmCDMProxy::md_SetServerCertificate", this,
+ &MediaDrmCDMProxy::md_SetServerCertificate,
+ aPromiseId, std::move(aCert)),
+ NS_DISPATCH_NORMAL);
+}
+
+void MediaDrmCDMProxy::UpdateSession(const nsAString& aSessionId,
+ PromiseId aPromiseId,
+ nsTArray<uint8_t>& aResponse) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ UniquePtr<UpdateSessionData> data(new UpdateSessionData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+ data->mResponse = std::move(aResponse);
+
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<UpdateSessionData>&&>(
+ "MediaDrmCDMProxy::md_UpdateSession", this,
+ &MediaDrmCDMProxy::md_UpdateSession, std::move(data)));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void MediaDrmCDMProxy::CloseSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+ NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+ UniquePtr<SessionOpData> data(new SessionOpData());
+ data->mPromiseId = aPromiseId;
+ data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod<UniquePtr<SessionOpData>&&>(
+ "MediaDrmCDMProxy::md_CloseSession", this,
+ &MediaDrmCDMProxy::md_CloseSession, std::move(data)));
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void MediaDrmCDMProxy::RemoveSession(const nsAString& aSessionId,
+ PromiseId aPromiseId) {
+ // TODO: Implement RemoveSession.
+ RejectPromiseWithStateError(
+ aPromiseId, "Currently Fennec does not support RemoveSession"_ns);
+}
+
+void MediaDrmCDMProxy::QueryOutputProtectionStatus() {
+ // TODO(bryce): determine if this is needed for Android and implement as
+ // needed. See also `NotifyOutputProtectionStatus`.
+}
+
+void MediaDrmCDMProxy::NotifyOutputProtectionStatus(
+ OutputProtectionCheckStatus aCheckStatus,
+ OutputProtectionCaptureStatus aCaptureStatus) {
+ // TODO(bryce): determine if this is needed for Android and implement as
+ // needed. See also `QueryOutputProtectionStatus`.
+}
+
+void MediaDrmCDMProxy::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mOwnerThread);
+ nsCOMPtr<nsIRunnable> task(NewRunnableMethod(
+ "MediaDrmCDMProxy::md_Shutdown", this, &MediaDrmCDMProxy::md_Shutdown));
+
+ mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+ mOwnerThread->Shutdown();
+ mOwnerThread = nullptr;
+}
+
+void MediaDrmCDMProxy::Terminated() {
+ // TODO: Implement Terminated.
+ // Should find a way to handle the case when remote side MediaDrm crashed.
+}
+
+const nsCString& MediaDrmCDMProxy::GetNodeId() const { return mNodeId; }
+
+void MediaDrmCDMProxy::OnSetSessionId(uint32_t aCreateSessionToken,
+ const nsAString& aSessionId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+
+ RefPtr<dom::MediaKeySession> session(
+ mKeys->GetPendingSession(aCreateSessionToken));
+ if (session) {
+ session->SetSessionId(aSessionId);
+ }
+}
+
+void MediaDrmCDMProxy::OnResolveLoadSessionPromise(uint32_t aPromiseId,
+ bool aSuccess) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ mKeys->OnSessionLoaded(aPromiseId, aSuccess);
+}
+
+void MediaDrmCDMProxy::OnSessionMessage(const nsAString& aSessionId,
+ dom::MediaKeyMessageType aMessageType,
+ const nsTArray<uint8_t>& aMessage) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyMessage(aMessageType, aMessage);
+ }
+}
+
+void MediaDrmCDMProxy::OnExpirationChange(const nsAString& aSessionId,
+ UnixTime aExpiryTime) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->SetExpiration(static_cast<double>(aExpiryTime));
+ }
+}
+
+void MediaDrmCDMProxy::OnSessionClosed(const nsAString& aSessionId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->OnClosed();
+ }
+}
+
+void MediaDrmCDMProxy::OnSessionError(const nsAString& aSessionId,
+ nsresult aException, uint32_t aSystemCode,
+ const nsAString& aMsg) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyError(aSystemCode);
+ }
+}
+
+void MediaDrmCDMProxy::OnRejectPromise(uint32_t aPromiseId,
+ ErrorResult&& aException,
+ const nsCString& aMsg) {
+ MOZ_ASSERT(NS_IsMainThread());
+ RejectPromise(aPromiseId, std::move(aException), aMsg);
+}
+
+RefPtr<DecryptPromise> MediaDrmCDMProxy::Decrypt(MediaRawData* aSample) {
+ MOZ_ASSERT_UNREACHABLE("Fennec could not handle decrypting individually");
+ return nullptr;
+}
+
+void MediaDrmCDMProxy::OnDecrypted(uint32_t aId, DecryptStatus aResult,
+ const nsTArray<uint8_t>& aDecryptedData) {
+ MOZ_ASSERT_UNREACHABLE("Fennec could not handle decrypted event");
+}
+
+void MediaDrmCDMProxy::RejectPromise(PromiseId aId, ErrorResult&& aException,
+ const nsCString& aReason) {
+ if (NS_IsMainThread()) {
+ if (!mKeys.IsNull()) {
+ mKeys->RejectPromise(aId, std::move(aException), aReason);
+ }
+ } else {
+ nsCOMPtr<nsIRunnable> task(
+ new RejectPromiseTask(this, aId, std::move(aException), aReason));
+ mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
+ }
+}
+
+void MediaDrmCDMProxy::RejectPromiseWithStateError(PromiseId aId,
+ const nsCString& aReason) {
+ ErrorResult rv;
+ rv.ThrowInvalidStateError(aReason);
+ RejectPromise(aId, std::move(rv), aReason);
+}
+
+void MediaDrmCDMProxy::ResolvePromise(PromiseId aId) {
+ if (NS_IsMainThread()) {
+ if (!mKeys.IsNull()) {
+ mKeys->ResolvePromise(aId);
+ } else {
+ NS_WARNING("MediaDrmCDMProxy unable to resolve promise!");
+ }
+ } else {
+ nsCOMPtr<nsIRunnable> task;
+ task =
+ NewRunnableMethod<PromiseId>("MediaDrmCDMProxy::ResolvePromise", this,
+ &MediaDrmCDMProxy::ResolvePromise, aId);
+ mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
+ }
+}
+
+template <typename T>
+void MediaDrmCDMProxy::ResolvePromiseWithResult(PromiseId aId,
+ const T& aResult) {
+ if (NS_IsMainThread()) {
+ if (!mKeys.IsNull()) {
+ mKeys->ResolvePromiseWithResult(aId, aResult);
+ } else {
+ NS_WARNING("MediaDrmCDMProxy unable to resolve promise!");
+ }
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> task;
+ task = NewRunnableMethod<PromiseId, T>(
+ "MediaDrmCDMProxy::ResolvePromiseWithResult", this,
+ &MediaDrmCDMProxy::ResolvePromiseWithResult<T>, aId, aResult);
+ mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
+}
+
+const nsString& MediaDrmCDMProxy::KeySystem() const { return mKeySystem; }
+
+DataMutex<CDMCaps>& MediaDrmCDMProxy::Capabilites() { return mCapabilites; }
+
+void MediaDrmCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+ RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+ if (session) {
+ session->DispatchKeyStatusesChange();
+ }
+}
+
+void MediaDrmCDMProxy::GetStatusForPolicy(PromiseId aPromiseId,
+ const nsAString& aMinHdcpVersion) {
+ // TODO: Implement GetStatusForPolicy.
+ constexpr auto err =
+ "Currently Fennec does not support GetStatusForPolicy"_ns;
+
+ ErrorResult rv;
+ rv.ThrowNotSupportedError(err);
+ RejectPromise(aPromiseId, std::move(rv), err);
+}
+
+#ifdef DEBUG
+bool MediaDrmCDMProxy::IsOnOwnerThread() {
+ return NS_GetCurrentThread() == mOwnerThread;
+}
+#endif
+
+const nsString& MediaDrmCDMProxy::GetMediaDrmStubId() const {
+ MOZ_ASSERT(mCDM);
+ return mCDM->GetMediaDrmStubId();
+}
+
+void MediaDrmCDMProxy::OnCDMCreated(uint32_t aPromiseId) {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mKeys.IsNull()) {
+ return;
+ }
+
+ if (mCDM) {
+ mKeys->OnCDMCreated(aPromiseId, 0);
+ return;
+ }
+
+ // No CDM? Just reject the promise.
+ constexpr auto err = "Null CDM in OnCDMCreated()"_ns;
+ ErrorResult rv;
+ rv.ThrowInvalidStateError(err);
+ mKeys->RejectPromise(aPromiseId, std::move(rv), err);
+}
+
+void MediaDrmCDMProxy::md_Init(uint32_t aPromiseId) {
+ MOZ_ASSERT(IsOnOwnerThread());
+ MOZ_ASSERT(mCDM);
+
+ mCallback.reset(new MediaDrmCDMCallbackProxy(this));
+ mCDM->Init(mCallback.get());
+ nsCOMPtr<nsIRunnable> task(
+ NewRunnableMethod<uint32_t>("MediaDrmCDMProxy::OnCDMCreated", this,
+ &MediaDrmCDMProxy::OnCDMCreated, aPromiseId));
+ mMainThread->Dispatch(task.forget(), NS_DISPATCH_NORMAL);
+}
+
+void MediaDrmCDMProxy::md_CreateSession(UniquePtr<CreateSessionData>&& aData) {
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ RejectPromiseWithStateError(aData->mPromiseId,
+ "Null CDM in md_CreateSession"_ns);
+ return;
+ }
+
+ mCDM->CreateSession(aData->mCreateSessionToken, aData->mPromiseId,
+ aData->mInitDataType, aData->mInitData,
+ ToMediaDrmSessionType(aData->mSessionType));
+}
+
+void MediaDrmCDMProxy::md_SetServerCertificate(PromiseId aPromiseId,
+ const nsTArray<uint8_t>& aCert) {
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ RejectPromiseWithStateError(aPromiseId,
+ "Null CDM in md_SetServerCertificate"_ns);
+ return;
+ }
+
+ if (mCDM->SetServerCertificate(aCert)) {
+ ResolvePromiseWithResult(aPromiseId, true);
+ } else {
+ RejectPromiseWithStateError(
+ aPromiseId, "MediaDrmCDMProxy unable to set server certificate"_ns);
+ }
+}
+
+void MediaDrmCDMProxy::md_UpdateSession(UniquePtr<UpdateSessionData>&& aData) {
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ RejectPromiseWithStateError(aData->mPromiseId,
+ "Null CDM in md_UpdateSession"_ns);
+ return;
+ }
+ mCDM->UpdateSession(aData->mPromiseId, aData->mSessionId, aData->mResponse);
+}
+
+void MediaDrmCDMProxy::md_CloseSession(UniquePtr<SessionOpData>&& aData) {
+ MOZ_ASSERT(IsOnOwnerThread());
+
+ if (!mCDM) {
+ RejectPromiseWithStateError(aData->mPromiseId,
+ "Null CDM in md_CloseSession"_ns);
+ return;
+ }
+ mCDM->CloseSession(aData->mPromiseId, aData->mSessionId);
+}
+
+void MediaDrmCDMProxy::md_Shutdown() {
+ MOZ_ASSERT(IsOnOwnerThread());
+ MOZ_ASSERT(mCDM);
+ if (mShutdownCalled) {
+ return;
+ }
+ mShutdownCalled = true;
+ mCDM->Shutdown();
+ mCDM = nullptr;
+}
+
+} // namespace mozilla