Bug 1306572 - Part3 - Implement MediaCDMProxy and the callback proxy. r=cpearce
authorJames Cheng <jacheng@mozilla.com>
Tue, 01 Nov 2016 14:39:34 +0800
changeset 321115 3d1ccb0ce457e5414edc88758b8711f321b875da
parent 321114 f6e81941c5370ac75ce2ce2ff0b03d9e6cdde687
child 321116 8ea2413ea36e0a0e250191c77fb3fae05a940356
push id30919
push userphilringnalda@gmail.com
push dateSat, 05 Nov 2016 20:28:20 +0000
treeherdermozilla-central@572249b2ffb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerscpearce
bugs1306572
milestone52.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1306572 - Part3 - Implement MediaCDMProxy and the callback proxy. r=cpearce MozReview-Commit-ID: 3dFawuvhfWu
dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.cpp
dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.h
dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
dom/media/eme/mediadrm/MediaDrmCDMProxy.h
dom/media/eme/mediadrm/moz.build
dom/media/eme/moz.build
new file mode 100644
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.cpp
@@ -0,0 +1,140 @@
+/* -*- 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 "MediaDrmCDMCallbackProxy.h"
+#include "mozilla/CDMProxy.h"
+#include "nsString.h"
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "mozIGeckoMediaPluginService.h"
+#include "nsContentCID.h"
+#include "nsServiceManagerUtils.h"
+#include "MainThreadUtils.h"
+#include "mozilla/EMEUtils.h"
+
+namespace mozilla {
+
+MediaDrmCDMCallbackProxy::MediaDrmCDMCallbackProxy(CDMProxy* aProxy)
+  : mProxy(aProxy)
+{
+
+}
+
+void
+MediaDrmCDMCallbackProxy::SetSessionId(uint32_t aToken,
+                                       const nsCString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mProxy->OnSetSessionId(aToken, NS_ConvertUTF8toUTF16(aSessionId));
+}
+
+void
+MediaDrmCDMCallbackProxy::ResolveLoadSessionPromise(uint32_t aPromiseId,
+                                                    bool aSuccess)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mProxy->OnResolveLoadSessionPromise(aPromiseId, aSuccess);
+}
+
+void
+MediaDrmCDMCallbackProxy::ResolvePromise(uint32_t aPromiseId)
+{
+  // Note: CDMProxy proxies this from non-main threads to main thread.
+  mProxy->ResolvePromise(aPromiseId);
+}
+
+void
+MediaDrmCDMCallbackProxy::RejectPromise(uint32_t aPromiseId,
+                                        nsresult aException,
+                                        const nsCString& aMessage)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mProxy->OnRejectPromise(aPromiseId, aException, aMessage);
+}
+
+void
+MediaDrmCDMCallbackProxy::SessionMessage(const nsCString& aSessionId,
+                                         dom::MediaKeyMessageType aMessageType,
+                                         const nsTArray<uint8_t>& aMessage)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  // For removing constness
+  nsTArray<uint8_t> message(aMessage);
+  mProxy->OnSessionMessage(NS_ConvertUTF8toUTF16(aSessionId), aMessageType, message);
+}
+
+void
+MediaDrmCDMCallbackProxy::ExpirationChange(const nsCString& aSessionId,
+                                           UnixTime aExpiryTime)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mProxy->OnExpirationChange(NS_ConvertUTF8toUTF16(aSessionId), aExpiryTime);
+}
+
+void
+MediaDrmCDMCallbackProxy::SessionClosed(const nsCString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  bool keyStatusesChange = false;
+  {
+    CDMCaps::AutoLock caps(mProxy->Capabilites());
+    keyStatusesChange = caps.RemoveKeysForSession(NS_ConvertUTF8toUTF16(aSessionId));
+  }
+  if (keyStatusesChange) {
+    mProxy->OnKeyStatusesChange(NS_ConvertUTF8toUTF16(aSessionId));
+  }
+  mProxy->OnSessionClosed(NS_ConvertUTF8toUTF16(aSessionId));
+}
+
+void
+MediaDrmCDMCallbackProxy::SessionError(const nsCString& aSessionId,
+                                       nsresult aException,
+                                       uint32_t aSystemCode,
+                                       const nsCString& aMessage)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  mProxy->OnSessionError(NS_ConvertUTF8toUTF16(aSessionId),
+                         aException,
+                         aSystemCode,
+                         NS_ConvertUTF8toUTF16(aMessage));
+}
+
+void
+MediaDrmCDMCallbackProxy::BatchedKeyStatusChanged(const nsCString& aSessionId,
+                                                  const nsTArray<CDMKeyInfo>& aKeyInfos)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  BatchedKeyStatusChangedInternal(aSessionId, aKeyInfos);
+}
+
+void
+MediaDrmCDMCallbackProxy::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) {
+    mProxy->OnKeyStatusesChange(NS_ConvertUTF8toUTF16(aSessionId));
+  }
+}
+
+void
+MediaDrmCDMCallbackProxy::Decrypted(uint32_t aId,
+                                    DecryptStatus aResult,
+                                    const nsTArray<uint8_t>& aDecryptedData)
+{
+  MOZ_ASSERT_UNREACHABLE("Fennec could not handle decrypted event");
+}
+
+} // namespace mozilla
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmCDMCallbackProxy.h
@@ -0,0 +1,66 @@
+/* -*- 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/. */
+
+#ifndef MediaDrmCDMCallbackProxy_h_
+#define MediaDrmCDMCallbackProxy_h_
+
+#include "mozilla/CDMProxy.h"
+#include "mozilla/DecryptorProxyCallback.h"
+
+namespace mozilla {
+class CDMProxy;
+// Proxies call backs from the MediaDrmProxy -> MediaDrmProxySupport back to the MediaKeys
+// object on the main thread.
+// We used annotation calledFrom = "gecko" to ensure running on main thread.
+class MediaDrmCDMCallbackProxy : public DecryptorProxyCallback {
+public:
+  void SetSessionId(uint32_t aCreateSessionToken,
+                    const nsCString& aSessionId) override;
+
+  void ResolveLoadSessionPromise(uint32_t aPromiseId,
+                                 bool aSuccess) override;
+
+  void ResolvePromise(uint32_t aPromiseId) override;
+
+  void RejectPromise(uint32_t aPromiseId,
+                     nsresult aException,
+                     const nsCString& aSessionId) override;
+
+  void SessionMessage(const nsCString& aSessionId,
+                      dom::MediaKeyMessageType aMessageType,
+                      const nsTArray<uint8_t>& aMessage) override;
+
+  void ExpirationChange(const nsCString& aSessionId,
+                        UnixTime aExpiryTime) override;
+
+  void SessionClosed(const nsCString& aSessionId) override;
+
+  void SessionError(const nsCString& aSessionId,
+                    nsresult aException,
+                    uint32_t aSystemCode,
+                    const nsCString& aMessage) override;
+
+  void Decrypted(uint32_t aId,
+                 DecryptStatus aResult,
+                 const nsTArray<uint8_t>& aDecryptedData) override;
+
+  void BatchedKeyStatusChanged(const nsCString& aSessionId,
+                               const nsTArray<CDMKeyInfo>& aKeyInfos) override;
+
+   ~MediaDrmCDMCallbackProxy() {}
+
+private:
+  friend class MediaDrmCDMProxy;
+  explicit MediaDrmCDMCallbackProxy(CDMProxy* aProxy);
+
+  void BatchedKeyStatusChangedInternal(const nsCString& aSessionId,
+                                       const nsTArray<CDMKeyInfo>& aKeyInfos);
+  // Warning: Weak ref.
+  CDMProxy* mProxy;
+};
+
+} // namespace mozilla
+#endif
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
@@ -0,0 +1,467 @@
+/* -*- 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"
+
+using namespace mozilla::java::sdk;
+
+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,
+                       bool aInPrivateBrowsing)
+{
+  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(),
+          (aInPrivateBrowsing ? "PrivateBrowsing" : "NonPrivateBrowsing"));
+
+  // Create a thread to work with cdm.
+  if (!mOwnerThread) {
+    nsresult rv = NS_NewNamedThread("MDCDMThread", getter_AddRefs(mOwnerThread));
+    if (NS_FAILED(rv)) {
+      RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+                    NS_LITERAL_CSTRING("Couldn't create CDM thread MediaDrmCDMProxy::Init"));
+      return;
+    }
+  }
+
+  mCDM = mozilla::MakeUnique<MediaDrmProxySupport>(mKeySystem);
+  nsCOMPtr<nsIRunnable> task(NewRunnableMethod<uint32_t>(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);
+
+  nsAutoPtr<CreateSessionData> data(new CreateSessionData());
+  data->mSessionType = aSessionType;
+  data->mCreateSessionToken = aCreateSessionToken;
+  data->mPromiseId = aPromiseId;
+  data->mInitDataType = NS_ConvertUTF16toUTF8(aInitDataType);
+  data->mInitData = Move(aInitData);
+
+  nsCOMPtr<nsIRunnable> task(
+    NewRunnableMethod<nsAutoPtr<CreateSessionData>>(this,
+                                                    &MediaDrmCDMProxy::md_CreateSession,
+                                                    data));
+  mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+MediaDrmCDMProxy::LoadSession(PromiseId aPromiseId,
+                              const nsAString& aSessionId)
+{
+  // TODO: Implement LoadSession.
+  RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+                NS_LITERAL_CSTRING("Currently Fennec did not support LoadSession"));
+}
+
+void
+MediaDrmCDMProxy::SetServerCertificate(PromiseId aPromiseId,
+                                     nsTArray<uint8_t>& aCert)
+{
+  // TODO: Implement SetServerCertificate.
+  RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+                NS_LITERAL_CSTRING("Currently Fennec did not support SetServerCertificate"));
+}
+
+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());
+
+  nsAutoPtr<UpdateSessionData> data(new UpdateSessionData());
+  data->mPromiseId = aPromiseId;
+  data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+  data->mResponse = Move(aResponse);
+
+  nsCOMPtr<nsIRunnable> task(
+    NewRunnableMethod<nsAutoPtr<UpdateSessionData>>(this,
+                                                    &MediaDrmCDMProxy::md_UpdateSession,
+                                                    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());
+
+  nsAutoPtr<SessionOpData> data(new SessionOpData());
+  data->mPromiseId = aPromiseId;
+  data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
+
+  nsCOMPtr<nsIRunnable> task(
+    NewRunnableMethod<nsAutoPtr<SessionOpData>>(this,
+                                                &MediaDrmCDMProxy::md_CloseSession,
+                                                data));
+  mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
+}
+
+void
+MediaDrmCDMProxy::RemoveSession(const nsAString& aSessionId,
+                              PromiseId aPromiseId)
+{
+  // TODO: Implement RemoveSession.
+  RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+                NS_LITERAL_CSTRING("Currently Fennec did not support RemoveSession"));
+}
+
+void
+MediaDrmCDMProxy::Shutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mOwnerThread);
+  nsCOMPtr<nsIRunnable> task(
+    NewRunnableMethod(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,
+                                   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,
+                                  nsresult aDOMException,
+                                  const nsCString& aMsg)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  RejectPromise(aPromiseId, aDOMException, aMsg);
+}
+
+RefPtr<MediaDrmCDMProxy::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, nsresult aCode,
+                                const nsCString& aReason)
+{
+  if (NS_IsMainThread()) {
+    if (!mKeys.IsNull()) {
+      mKeys->RejectPromise(aId, aCode, aReason);
+    }
+  } else {
+    nsCOMPtr<nsIRunnable> task(new RejectPromiseTask(this, aId, aCode,
+                                                     aReason));
+    NS_DispatchToMainThread(task);
+  }
+}
+
+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>(this,
+                                        &MediaDrmCDMProxy::ResolvePromise,
+                                        aId);
+    NS_DispatchToMainThread(task);
+  }
+}
+
+const nsString&
+MediaDrmCDMProxy::KeySystem() const
+{
+  return mKeySystem;
+}
+
+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::GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+                                      nsTArray<nsCString>& aSessionIds)
+{
+  CDMCaps::AutoLock caps(Capabilites());
+  caps.GetSessionIdsForKeyId(aKeyId, aSessionIds);
+}
+
+#ifdef DEBUG
+bool
+MediaDrmCDMProxy::IsOnOwnerThread()
+{
+  return NS_GetCurrentThread() == mOwnerThread;
+}
+#endif
+
+void
+MediaDrmCDMProxy::OnCDMCreated(uint32_t aPromiseId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mKeys.IsNull()) {
+    return;
+  }
+
+  if (mCDM) {
+    mKeys->OnCDMCreated(aPromiseId, GetNodeId(), 0);
+    return;
+  }
+
+  // No CDM? Just reject the promise.
+  mKeys->RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+                       NS_LITERAL_CSTRING("Null CDM in OnCDMCreated()"));
+}
+
+void
+MediaDrmCDMProxy::md_Init(uint32_t aPromiseId)
+{
+  MOZ_ASSERT(IsOnOwnerThread());
+  MOZ_ASSERT(mCDM);
+
+  mCallback = new MediaDrmCDMCallbackProxy(this);
+  mCDM->Init(mCallback);
+  nsCOMPtr<nsIRunnable> task(
+    NewRunnableMethod<uint32_t>(this,
+                                &MediaDrmCDMProxy::OnCDMCreated,
+                                aPromiseId));
+  NS_DispatchToMainThread(task);
+}
+
+void
+MediaDrmCDMProxy::md_CreateSession(nsAutoPtr<CreateSessionData> aData)
+{
+  MOZ_ASSERT(IsOnOwnerThread());
+
+  if (!mCDM) {
+    RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Null CDM in md_CreateSession"));
+    return;
+  }
+
+  mCDM->CreateSession(aData->mCreateSessionToken,
+                      aData->mPromiseId,
+                      aData->mInitDataType,
+                      aData->mInitData,
+                      ToMediaDrmSessionType(aData->mSessionType));
+}
+
+void
+MediaDrmCDMProxy::md_UpdateSession(nsAutoPtr<UpdateSessionData> aData)
+{
+  MOZ_ASSERT(IsOnOwnerThread());
+
+  if (!mCDM) {
+    RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Null CDM in md_UpdateSession"));
+    return;
+  }
+  mCDM->UpdateSession(aData->mPromiseId,
+                      aData->mSessionId,
+                      aData->mResponse);
+}
+
+void
+MediaDrmCDMProxy::md_CloseSession(nsAutoPtr<SessionOpData> aData)
+{
+  MOZ_ASSERT(IsOnOwnerThread());
+
+  if (!mCDM) {
+    RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Null CDM in md_CloseSession"));
+    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
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.h
@@ -0,0 +1,184 @@
+/* -*- 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/. */
+
+#ifndef MediaDrmCDMProxy_h_
+#define MediaDrmCDMProxy_h_
+
+#include <jni.h>
+#include "mozilla/jni/Types.h"
+#include "GeneratedJNINatives.h"
+
+#include "mozilla/CDMProxy.h"
+#include "mozilla/CDMCaps.h"
+#include "mozilla/dom/MediaKeys.h"
+#include "mozilla/MediaDrmProxySupport.h"
+#include "mozilla/UniquePtr.h"
+
+#include "MediaCodec.h"
+#include "nsString.h"
+#include "nsAutoPtr.h"
+
+using namespace mozilla::java;
+
+namespace mozilla {
+class MediaDrmCDMCallbackProxy;
+class MediaDrmCDMProxy : public CDMProxy {
+public:
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDrmCDMProxy)
+
+  MediaDrmCDMProxy(dom::MediaKeys* aKeys,
+                   const nsAString& aKeySystem,
+                   bool aDistinctiveIdentifierRequired,
+                   bool aPersistentStateRequired);
+
+  void Init(PromiseId aPromiseId,
+            const nsAString& aOrigin,
+            const nsAString& aTopLevelOrigin,
+            const nsAString& aGMPName,
+            bool aInPrivateBrowsing) override;
+
+  void CreateSession(uint32_t aCreateSessionToken,
+                     MediaKeySessionType aSessionType,
+                     PromiseId aPromiseId,
+                     const nsAString& aInitDataType,
+                     nsTArray<uint8_t>& aInitData) override;
+
+  void LoadSession(PromiseId aPromiseId,
+                   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 Shutdown() override;
+
+  void Terminated() override;
+
+  const nsCString& GetNodeId() const 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,
+                        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,
+                       nsresult aCode,
+                       const nsCString& aMsg) override;
+
+  RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample) override;
+  void OnDecrypted(uint32_t aId,
+                   DecryptStatus aResult,
+                   const nsTArray<uint8_t>& aDecryptedData) override;
+
+  void RejectPromise(PromiseId aId, nsresult aCode,
+                     const nsCString& aReason) override;
+
+  // Resolves promise with "undefined".
+  // Can be called from any thread.
+  void ResolvePromise(PromiseId aId) override;
+
+  // Threadsafe.
+  const nsString& KeySystem() const override;
+
+  CDMCaps& Capabilites() override;
+
+  void OnKeyStatusesChange(const nsAString& aSessionId) override;
+
+  void GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+                             nsTArray<nsCString>& aSessionIds) override;
+
+#ifdef DEBUG
+  bool IsOnOwnerThread() override;
+#endif
+
+private:
+  virtual ~MediaDrmCDMProxy();
+
+  void OnCDMCreated(uint32_t aPromiseId);
+
+  struct CreateSessionData {
+    MediaKeySessionType mSessionType;
+    uint32_t mCreateSessionToken;
+    PromiseId mPromiseId;
+    nsCString mInitDataType;
+    nsTArray<uint8_t> mInitData;
+  };
+
+  struct UpdateSessionData {
+    PromiseId mPromiseId;
+    nsCString mSessionId;
+    nsTArray<uint8_t> mResponse;
+  };
+
+  struct SessionOpData {
+    PromiseId mPromiseId;
+    nsCString mSessionId;
+  };
+
+  class RejectPromiseTask : public Runnable {
+  public:
+    RejectPromiseTask(MediaDrmCDMProxy* aProxy,
+                      PromiseId aId,
+                      nsresult aCode,
+                      const nsCString& aReason)
+      : mProxy(aProxy)
+      , mId(aId)
+      , mCode(aCode)
+      , mReason(aReason)
+    {
+    }
+    NS_METHOD Run() {
+      mProxy->RejectPromise(mId, mCode, mReason);
+      return NS_OK;
+    }
+  private:
+    RefPtr<MediaDrmCDMProxy> mProxy;
+    PromiseId mId;
+    nsresult mCode;
+    nsCString mReason;
+  };
+
+  nsCString mNodeId;
+  mozilla::UniquePtr<MediaDrmProxySupport> mCDM;
+  nsAutoPtr<MediaDrmCDMCallbackProxy> mCallback;
+  bool mShutdownCalled;
+
+// =====================================================================
+// For MediaDrmProxySupport
+  void md_Init(uint32_t aPromiseId);
+  void md_CreateSession(nsAutoPtr<CreateSessionData> aData);
+  void md_UpdateSession(nsAutoPtr<UpdateSessionData> aData);
+  void md_CloseSession(nsAutoPtr<SessionOpData> aData);
+  void md_Shutdown();
+// =====================================================================
+};
+
+} // namespace mozilla
+#endif // MediaDrmCDMProxy_h_
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/media/eme/mediadrm/moz.build
@@ -0,0 +1,19 @@
+# -*- 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 += [
+    'MediaDrmCDMCallbackProxy.h',
+    'MediaDrmCDMProxy.h',
+    'MediaDrmProxySupport.h',
+]
+
+UNIFIED_SOURCES += [
+    'MediaDrmCDMCallbackProxy.cpp',
+    'MediaDrmCDMProxy.cpp',
+    'MediaDrmProxySupport.cpp',
+]
+
+FINAL_LIBRARY = 'xul'
\ No newline at end of file
--- a/dom/media/eme/moz.build
+++ b/dom/media/eme/moz.build
@@ -32,11 +32,14 @@ UNIFIED_SOURCES += [
     'MediaKeyMessageEvent.cpp',
     'MediaKeys.cpp',
     'MediaKeySession.cpp',
     'MediaKeyStatusMap.cpp',
     'MediaKeySystemAccess.cpp',
     'MediaKeySystemAccessManager.cpp',
 ]
 
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
+  DIRS += ['mediadrm']
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'