Bug 1317628-[P1] Handling key-needed/key-added for encrypted media playback with Fennec in-process-decode mode on Android L. draft
authorKilik Kuo <kikuo@mozilla.com>
Thu, 22 Dec 2016 15:39:43 +0800
changeset 452783 c60638268ed926688d1fb2d430d12fc7603cd8ca
parent 447759 c2526f6786f074888d71c8e166a02aea3e19e75b
child 452877 a8fc67838e4703ef1f3fe42120ae4102c5050ca4
push id39490
push userbmo:kikuo@mozilla.com
push dateThu, 22 Dec 2016 07:40:44 +0000
bugs1317628
milestone53.0a1
Bug 1317628-[P1] Handling key-needed/key-added for encrypted media playback with Fennec in-process-decode mode on Android L. MozReview-Commit-ID: K8komNNnc0U
dom/media/platforms/android/MediaCodecDataDecoder.cpp
dom/media/platforms/android/MediaCodecDataDecoder.h
mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrm.java
mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV23.java
mobile/android/base/java/org/mozilla/gecko/media/LocalMediaDrmBridge.java
mobile/android/base/java/org/mozilla/gecko/media/MediaCodecUtil.java
mobile/android/base/java/org/mozilla/gecko/media/MediaDrmProxy.java
mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridge.java
mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridgeStub.java
mobile/android/base/moz.build
widget/android/fennec/FennecJNIWrappers.cpp
widget/android/fennec/FennecJNIWrappers.h
--- a/dom/media/platforms/android/MediaCodecDataDecoder.cpp
+++ b/dom/media/platforms/android/MediaCodecDataDecoder.cpp
@@ -146,30 +146,44 @@ public:
    : VideoDataDecoder(aConfig, aFormat, aCallback, aImageContainer, aDrmStubId)
    , mSamplesWaitingForKey(new SamplesWaitingForKey(this, aCallback,
                                                     aTaskQueue, aProxy))
   {
   }
 
   void Input(MediaRawData* aSample) override;
   void Shutdown() override;
+  bool WaitForKeys() override;
 
 private:
   RefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
 };
 
 void
 EMEVideoDataDecoder::Input(MediaRawData* aSample)
 {
-  if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
+  // For Android L or older, we cannot receive meaningful keyId from CDM.
+  // So decoder's not going to wait for certain keyId until the key becomes usable.
+  // Instead, decoder is decoding directly and if no-key exception happens, we'll
+  // retry later when the key may be usable.
+  if (!MediaDrmProxy::IsPreMarshmallow() && mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
     return;
   }
+
   VideoDataDecoder::Input(aSample);
 }
 
+bool
+EMEVideoDataDecoder::WaitForKeys()
+{
+  return MediaDrmProxy::IsPreMarshmallow() ?
+      MediaDrmProxy::WaitForKey(mDrmStubId) :
+      false;
+}
+
 void
 EMEVideoDataDecoder::Shutdown()
 {
   VideoDataDecoder::Shutdown();
 
   mSamplesWaitingForKey->BreakCycles();
   mSamplesWaitingForKey = nullptr;
 }
@@ -264,30 +278,44 @@ public:
    : AudioDataDecoder(aConfig, aFormat, aCallback, aDrmStubId)
    , mSamplesWaitingForKey(new SamplesWaitingForKey(this, aCallback,
                                                     aTaskQueue, aProxy))
   {
   }
 
   void Input(MediaRawData* aSample) override;
   void Shutdown() override;
+  bool WaitForKeys() override;
 
 private:
   RefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
 };
 
 void
 EMEAudioDataDecoder::Input(MediaRawData* aSample)
 {
-  if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
+  // For Android L or older, we cannot receive meaningful keyId from CDM.
+  // So decoder's not going to wait for certain keyId until the key becomes usable.
+  // Instead, decoder is decoding directly and if no-key exception happens, we'll
+  // retry later when the key may be usable.
+  if (!MediaDrmProxy::IsPreMarshmallow() && mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
     return;
   }
+
   AudioDataDecoder::Input(aSample);
 }
 
+bool
+EMEAudioDataDecoder::WaitForKeys()
+{
+  return MediaDrmProxy::IsPreMarshmallow() ?
+      MediaDrmProxy::WaitForKey(mDrmStubId) :
+      false;
+}
+
 void
 EMEAudioDataDecoder::Shutdown()
 {
   AudioDataDecoder::Shutdown();
 
   mSamplesWaitingForKey->BreakCycles();
   mSamplesWaitingForKey = nullptr;
 }
@@ -481,16 +509,43 @@ MediaCodecDataDecoder::PeekNextSample()
     }
     return nullptr;
   }
 
   // We're not stopping or flushing, so try to get a sample.
   return RefPtr<MediaRawData>(mQueue.front()).forget();
 }
 
+static nsresult
+QueueSecureInputBufferHelper(MediaCodec::LocalRef aDecoder,
+                             const nsString& aDrmStubId,
+                             int32_t aInputIdx, int32_t aOffset,
+                             CryptoInfo::LocalRef aCryptoInfo, int64_t aPTS,
+                             int32_t aFlags)
+{
+  int status = MediaCodecUtil::CODEC_STATUS_OK;
+  if (MediaDrmProxy::IsPreMarshmallow()) {
+    status = MediaCodecUtil::QueueSecureInputBufferV21(aDecoder,
+                                                       aDrmStubId,
+                                                       aInputIdx,
+                                                       aOffset,
+                                                       aCryptoInfo,
+                                                       aPTS,
+                                                       aFlags);
+  } else {
+    status = MediaCodecUtil::QueueSecureInputBufferV23(aDecoder,
+                                                       aInputIdx,
+                                                       aOffset,
+                                                       aCryptoInfo,
+                                                       aPTS,
+                                                       aFlags);
+  }
+  return status == MediaCodecUtil::CODEC_STATUS_OK ? NS_OK : NS_ERROR_FAILURE;
+}
+
 nsresult
 MediaCodecDataDecoder::QueueSample(const MediaRawData* aSample)
 {
   MOZ_ASSERT(aSample);
   AutoLocalJNIFrame frame(jni::GetEnvForThread(), 1);
 
   // We have a sample, try to feed it to the decoder.
   int32_t inputIndex = -1;
@@ -515,18 +570,19 @@ MediaCodecDataDecoder::QueueSample(const
   MOZ_ASSERT(frame.GetEnv()->GetDirectBufferCapacity(buffer.Get()) >=
              aSample->Size(),
              "Decoder buffer is not large enough for sample");
 
   PodCopy(static_cast<uint8_t*>(directBuffer), aSample->Data(), aSample->Size());
 
   CryptoInfo::LocalRef cryptoInfo = GetCryptoInfoFromSample(aSample);
   if (cryptoInfo) {
-    res = mDecoder->QueueSecureInputBuffer(inputIndex, 0, cryptoInfo,
-                                           aSample->mTime, 0);
+    res = QueueSecureInputBufferHelper(mDecoder, mDrmStubId,
+                                       inputIndex, 0, cryptoInfo,
+                                       aSample->mTime, 0);
   } else {
     res = mDecoder->QueueInputBuffer(inputIndex, 0, aSample->Size(),
                                      aSample->mTime, 0);
   }
 
   if (NS_FAILED(res)) {
     return res;
   }
@@ -627,24 +683,35 @@ MediaCodecDataDecoder::DecoderLoop()
       MonitorAutoLock lock(mMonitor);
       if (mState == ModuleState::kDrainDecoder) {
         MOZ_ASSERT(!sample, "Shouldn't have a sample when pushing EOF frame");
         res = QueueEOS();
         BREAK_ON_DECODER_ERROR();
       }
     }
 
+    // Both Audio/Video are blocked even if only one of them needs a keys.
+    if (WaitForKeys()) {
+      // Loop will be blocked until new keys are updated.
+      MediaDrmProxy::DoWait(mDrmStubId);
+      continue;
+    }
+
     if (sample) {
       res = QueueSample(sample);
       if (NS_SUCCEEDED(res)) {
         // We've fed this into the decoder, so remove it from the queue.
         MonitorAutoLock lock(mMonitor);
         MOZ_RELEASE_ASSERT(mQueue.size(), "Queue may not be empty");
         mQueue.pop_front();
         isOutputDone = false;
+      } else {
+        if (WaitForKeys()) {
+          continue;
+        }
       }
     }
 
     if (isOutputDone) {
       continue;
     }
 
     BufferInfo::LocalRef bufferInfo;
--- a/dom/media/platforms/android/MediaCodecDataDecoder.h
+++ b/dom/media/platforms/android/MediaCodecDataDecoder.h
@@ -85,16 +85,17 @@ protected:
 
   virtual void Cleanup() {};
 
   nsresult ResetInputBuffers();
   nsresult ResetOutputBuffers();
 
   nsresult GetInputBuffer(JNIEnv* env, int index, jni::Object::LocalRef* buffer);
   bool WaitForInput();
+  virtual bool WaitForKeys() { return false; }
   already_AddRefed<MediaRawData> PeekNextSample();
   nsresult QueueSample(const MediaRawData* aSample);
   nsresult QueueEOS();
   void HandleEOS(int32_t aOutputStatus);
   Maybe<media::TimeUnit> GetOutputDuration();
   nsresult ProcessOutput(java::sdk::BufferInfo::Param aInfo,
                          java::sdk::MediaFormat::Param aFormat,
                          int32_t aStatus);
--- a/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrm.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrm.java
@@ -1,15 +1,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/. */
 
 package org.mozilla.gecko.media;
 
 import android.media.MediaCrypto;
+import java.util.concurrent.locks.ReentrantLock;
 
 public interface GeckoMediaDrm {
     public interface Callbacks {
         void onSessionCreated(int createSessionToken,
                               int promiseId,
                               byte[] sessionId,
                               byte[] request);
         void onSessionUpdated(int promiseId, byte[] sessionId);
@@ -27,9 +28,15 @@ public interface GeckoMediaDrm {
     void createSession(int createSessionToken,
                        int promiseId,
                        String initDataType,
                        byte[] initData);
     void updateSession(int promiseId, String sessionId, byte[] response);
     void closeSession(int promiseId, String sessionId);
     void release();
     MediaCrypto getMediaCrypto();
+
+    // The following interfaces are used for Android L or older.
+    void notifyKeyNeeded();
+    boolean waitForKey();
+    void doWait();
+    ReentrantLock getKeyNeededLock();
 }
--- a/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
@@ -11,16 +11,18 @@ import java.io.InputStreamReader;
 import java.net.HttpURLConnection;
 import java.net.URL;
 import java.net.URLEncoder;
 import java.nio.ByteBuffer;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.UUID;
 import java.util.ArrayDeque;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.concurrent.locks.Condition;
 
 import android.annotation.SuppressLint;
 import android.os.AsyncTask;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.media.DeniedByServerException;
 import android.media.MediaCrypto;
 import android.media.MediaCryptoException;
@@ -45,21 +47,28 @@ public class GeckoMediaDrmBridgeV21 impl
     private HandlerThread mHandlerThread;
     private ByteBuffer mCryptoSessionId;
 
     // mProvisioningPromiseId is great than 0 only during provisioning.
     private int mProvisioningPromiseId;
     private HashSet<ByteBuffer> mSessionIds;
     private HashMap<ByteBuffer, String> mSessionMIMETypes;
     private ArrayDeque<PendingCreateSessionData> mPendingCreateSessionDataQueue;
-    private GeckoMediaDrm.Callbacks mCallbacks;
+
+    private boolean mKeyNeeded = false;
+    private final ReentrantLock mKeyNeededLock = new ReentrantLock();
+    private final Condition mKeyNeededCondition = mKeyNeededLock.newCondition();
 
     private MediaCrypto mCrypto;
+
     protected MediaDrm mDrm;
+    protected GeckoMediaDrm.Callbacks mCallbacks;
 
+    // These flags are actually supported on API 23+, so we are able to use
+    // LICENSE_REQUEST_INITIAL only as the returing message type to web.
     public static final int LICENSE_REQUEST_INITIAL = 0; /*MediaKeyMessageType::License_request*/
     public static final int LICENSE_REQUEST_RENEWAL = 1; /*MediaKeyMessageType::License_renewal*/
     public static final int LICENSE_REQUEST_RELEASE = 2; /*MediaKeyMessageType::License_release*/
 
     // Store session data while provisioning
     private static class PendingCreateSessionData {
         public final int mToken;
         public final int mPromiseId;
@@ -77,20 +86,20 @@ public class GeckoMediaDrmBridgeV21 impl
 
     public boolean isSecureDecoderComonentRequired(String mimeType) {
         if (mCrypto != null) {
             return mCrypto.requiresSecureDecoderComponent(mimeType);
         }
         return false;
       }
 
-    private static void assertTrue(boolean condition) {
-      if (DEBUG && !condition) {
-        throw new AssertionError("Expected condition to be true");
-      }
+    protected static void assertTrue(boolean condition) {
+        if (DEBUG && !condition) {
+            throw new AssertionError("Expected condition to be true");
+        }
     }
 
     @SuppressLint("WrongConstant")
     private void configureVendorSpecificProperty() {
         assertTrue(mDrm != null);
         // Support L3 for now
         mDrm.setPropertyString("securityLevel", "L3");
         // Refer to chromium, set multi-session mode for Widevine.
@@ -113,16 +122,17 @@ public class GeckoMediaDrmBridgeV21 impl
 
         if (DEBUG) Log.d(LOGTAG, "mSchemeUUID : " + mSchemeUUID.toString());
 
         // The caller of GeckoMediaDrmBridgeV21 ctor should handle exceptions
         // threw by the following steps.
         mDrm = new MediaDrm(mSchemeUUID);
         configureVendorSpecificProperty();
         mDrm.setOnEventListener(new MediaDrmListener());
+        ensureMediaCryptoCreated();
     }
 
     @Override
     public void setCallbacks(GeckoMediaDrm.Callbacks callbacks) {
         assertTrue(callbacks != null);
         mCallbacks = callbacks;
     }
 
@@ -198,32 +208,35 @@ public class GeckoMediaDrmBridgeV21 impl
         }
 
         ByteBuffer session = ByteBuffer.wrap(sessionId.getBytes());
         if (!sessionExists(session)) {
             onRejectPromise(promiseId, "Invalid session during updateSession.");
             return;
         }
 
+        mKeyNeededLock.lock();
         try {
             final byte [] keySetId = mDrm.provideKeyResponse(session.array(), response);
             if (DEBUG) {
                 HashMap<String, String> infoMap = mDrm.queryKeyStatus(session.array());
                 for (String strKey : infoMap.keySet()) {
                     String strValue = infoMap.get(strKey);
                     Log.d(LOGTAG, "InfoMap : key(" + strKey + ")/value(" + strValue + ")");
                 }
             }
-            HandleKeyStatusChangeByDummyKey(sessionId);
+            notifyKeyAdded();
             onSessionUpdated(promiseId, session.array());
             return;
         } catch (final NotProvisionedException | DeniedByServerException | IllegalStateException e) {
             if (DEBUG) Log.d(LOGTAG, "Failed to provide key response:", e);
             onSessionError(session.array(), "Got exception during updateSession.");
             onRejectPromise(promiseId, "Got exception during updateSession.");
+        } finally {
+            mKeyNeededLock.unlock();
         }
         release();
         return;
     }
 
     @Override
     public void closeSession(int promiseId, String sessionId) {
         if (DEBUG) Log.d(LOGTAG, "closeSession()");
@@ -276,23 +289,69 @@ public class GeckoMediaDrmBridgeV21 impl
     }
 
     @Override
     public MediaCrypto getMediaCrypto() {
         if (DEBUG) Log.d(LOGTAG, "getMediaCrypto()");
         return mCrypto;
     }
 
-    protected void HandleKeyStatusChangeByDummyKey(String sessionId)
-    {
-        SessionKeyInfo[] keyInfos = new SessionKeyInfo[1];
-        keyInfos[0] = new SessionKeyInfo(DUMMY_KEY_ID,
-                                         MediaDrm.KeyStatus.STATUS_USABLE);
-        onSessionBatchedKeyChanged(sessionId.getBytes(), keyInfos);
-        if (DEBUG) Log.d(LOGTAG, "Key successfully added for session " + sessionId);
+    @Override
+    public void notifyKeyNeeded() {
+        if (DEBUG) Log.d(LOGTAG, "notifyKeyNeeded()");
+        mKeyNeededLock.lock();
+        try {
+            mKeyNeeded = true;
+        } finally {
+            mKeyNeededLock.unlock();
+        }
+    }
+
+    @Override
+    public void doWait() {
+        if (DEBUG) Log.d(LOGTAG, "doWait()");
+        mKeyNeededLock.lock();
+        try {
+            if (mKeyNeeded) {
+                if (DEBUG) Log.d(LOGTAG, "doWait() => going to await");
+                mKeyNeededCondition.await();
+            }
+        } catch (Exception e) {
+            Log.e(LOGTAG, "Exception while calling doWait(),", e);
+        } finally {
+            mKeyNeededLock.unlock();
+        }
+    }
+
+    @Override
+    public synchronized boolean waitForKey() {
+        if (DEBUG) Log.d(LOGTAG, "waitForKey():" + mKeyNeeded);
+        return mKeyNeeded;
+    }
+
+    @Override
+    public ReentrantLock getKeyNeededLock() {
+        if (DEBUG) Log.d(LOGTAG, "getKeyNeededLock()");
+        return mKeyNeededLock;
+    }
+
+    protected void notifyKeyAdded() {
+        if (DEBUG) Log.d(LOGTAG, "notifyKeyAdded()");
+        mKeyNeededLock.lock();
+        try {
+            if (mKeyNeeded) {
+                if (DEBUG) Log.d(LOGTAG, "notifyKeyAdded() => keyAdded, going to signalAll");
+                mKeyNeeded = false;
+                mKeyNeededCondition.signalAll();
+            }
+        } catch (Exception e) {
+            Log.e(LOGTAG, "Exception while calling notifyKeyAdded()", e);
+        } finally {
+            mKeyNeededLock.unlock();
+        }
     }
 
     protected void onSessionCreated(int createSessionToken,
                                     int promiseId,
                                     byte[] sessionId,
                                     byte[] request) {
         assertTrue(mCallbacks != null);
         mCallbacks.onSessionCreated(createSessionToken, promiseId, sessionId, request);
--- a/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV23.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV23.java
@@ -10,16 +10,17 @@ import android.media.NotProvisionedExcep
 
 import static android.os.Build.VERSION_CODES.M;
 import android.media.MediaDrm;
 import android.util.Log;
 import java.lang.IllegalStateException;
 import java.nio.ByteBuffer;
 import java.util.HashMap;
 import java.util.List;
+import java.util.concurrent.locks.ReentrantLock;
 
 @TargetApi(M)
 public class GeckoMediaDrmBridgeV23 extends GeckoMediaDrmBridgeV21 {
     private static final boolean DEBUG = false;
 
     GeckoMediaDrmBridgeV23(String keySystem) throws Exception {
         super(keySystem);
         if (DEBUG) Log.d(LOGTAG, "GeckoMediaDrmBridgeV23 ctor");
@@ -43,14 +44,38 @@ public class GeckoMediaDrmBridgeV23 exte
                                                  keyStatus.getStatusCode());
             }
             onSessionBatchedKeyChanged(sessionId, keyInfos);
             if (DEBUG) Log.d(LOGTAG, "Key successfully added for session " + new String(sessionId));
         }
     }
 
     @Override
-    protected void HandleKeyStatusChangeByDummyKey(String sessionId)
-    {
-        // MediaDrm.KeyStatus information listener is supported on M+, there is no need to use
-        // dummy key id to report key status anymore.
+    protected void notifyKeyAdded() {
+        // Do nothing on Android M+.
+    }
+
+    @Override
+    public void notifyKeyNeeded() {
+        // Should not be used on Android M+.
+        assertTrue(false);
+    }
+
+    @Override
+    public boolean waitForKey() {
+        // Should not be used on Android M+.
+        assertTrue(false);
+        return false;
+    }
+
+    @Override
+    public void doWait() {
+        // Should not be used on Android M+.
+        assertTrue(false);
+    }
+
+    @Override
+    public ReentrantLock getKeyNeededLock() {
+        // Should not be used on Android M+.
+        assertTrue(false);
+        return null;
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/media/LocalMediaDrmBridge.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/LocalMediaDrmBridge.java
@@ -1,15 +1,17 @@
 /* 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/. */
 
 package org.mozilla.gecko.media;
 import org.mozilla.gecko.AppConstants;
 
+import java.util.concurrent.locks.ReentrantLock;
+
 import android.media.MediaCrypto;
 import android.util.Log;
 
 final class LocalMediaDrmBridge implements GeckoMediaDrm {
     private static final String LOGTAG = "GeckoLocalMediaDrmBridge";
     private static final boolean DEBUG = false;
     private GeckoMediaDrm mBridge = null;
     private CallbacksForwarder mCallbacksFwd;
@@ -77,17 +79,17 @@ final class LocalMediaDrmBridge implemen
             mProxyCallbacks.onRejectPromise(promiseId, message);
         }
     } // CallbacksForwarder
 
     private static void assertTrue(boolean condition) {
         if (DEBUG && !condition) {
           throw new AssertionError("Expected condition to be true");
         }
-      }
+    }
 
     LocalMediaDrmBridge(String keySystem) throws Exception {
         if (AppConstants.Versions.preLollipop) {
             Log.e(LOGTAG, "Pre-Lollipop should never enter here!!");
             mBridge = null;
         } else if (AppConstants.Versions.preMarshmallow) {
             mBridge = new GeckoMediaDrmBridgeV21(keySystem);
         } else {
@@ -154,9 +156,33 @@ final class LocalMediaDrmBridge implemen
         }
     }
 
     @Override
     public synchronized MediaCrypto getMediaCrypto() {
         if (DEBUG) Log.d(LOGTAG, "getMediaCrypto()");
         return mBridge != null ? mBridge.getMediaCrypto() : null;
     }
+
+    @Override
+    public void notifyKeyNeeded() {
+        assertTrue(mBridge != null);
+        mBridge.notifyKeyNeeded();
+    }
+
+    @Override
+    public boolean waitForKey() {
+        assertTrue(mBridge != null);
+        return mBridge.waitForKey();
+    }
+
+    @Override
+    public void doWait() {
+        assertTrue(mBridge != null);
+        mBridge.doWait();
+    }
+
+    @Override
+    public ReentrantLock getKeyNeededLock() {
+        assertTrue(mBridge != null);
+        return mBridge.getKeyNeededLock();
+    }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/media/MediaCodecUtil.java
@@ -0,0 +1,77 @@
+/* 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/. */
+
+
+package org.mozilla.gecko.media;
+
+import java.util.concurrent.locks.ReentrantLock;
+import android.media.MediaCodec;
+import android.util.Log;
+
+import org.mozilla.gecko.annotation.WrapForJNI;
+
+public final class MediaCodecUtil {
+    private static final String LOGTAG = "GeckoMediaCodecUtil";
+    private static final boolean DEBUG = false;
+
+    @WrapForJNI
+    public static final int CODEC_STATUS_OK = 0;
+    @WrapForJNI
+    public static final int CODEC_STATUS_NO_KEY = 1;
+    @WrapForJNI
+    public static final int CODEC_STATUS_ERROR = 2;
+
+    @WrapForJNI
+    public static int queueSecureInputBufferV23(MediaCodec codec,
+                                                int index,
+                                                int offset,
+                                                MediaCodec.CryptoInfo cryptoInfo,
+                                                long presentationTimeUs,
+                                                int flags) {
+        try {
+            codec.queueSecureInputBuffer(index, offset, cryptoInfo, presentationTimeUs, flags);
+        } catch (MediaCodec.CryptoException e) {
+            if (e.getErrorCode() == MediaCodec.CryptoException.ERROR_NO_KEY) {
+                if (DEBUG) Log.d(LOGTAG, "Got CryptoException.ERROR_NO_KEY");
+                return CODEC_STATUS_NO_KEY;
+            }
+            return CODEC_STATUS_ERROR;
+        } catch (Exception e) {
+            return CODEC_STATUS_ERROR;
+        }
+        return CODEC_STATUS_OK;
+    }
+
+    @WrapForJNI
+    public static int queueSecureInputBufferV21(MediaCodec codec,
+                                                   String drmStubId,
+                                                   int index,
+                                                   int offset,
+                                                   MediaCodec.CryptoInfo cryptoInfo,
+                                                   long presentationTimeUs,
+                                                   int flags) {
+        ReentrantLock lock = MediaDrmProxy.getKeyNeededLock(drmStubId);
+        if (lock == null) {
+            return CODEC_STATUS_ERROR;
+        }
+        int status = CODEC_STATUS_OK;
+        lock.lock();
+        try {
+            codec.queueSecureInputBuffer(index, offset, cryptoInfo, presentationTimeUs, flags);
+        } catch (MediaCodec.CryptoException e) {
+            if (e.getErrorCode() == MediaCodec.CryptoException.ERROR_NO_KEY) {
+                if (DEBUG) Log.d(LOGTAG, "Got CryptoException.ERROR_NO_KEY");
+                MediaDrmProxy.notifyKeyNeeded(drmStubId);
+                status = CODEC_STATUS_NO_KEY;
+            } else {
+                status = CODEC_STATUS_ERROR;
+            }
+        } catch (Exception e) {
+            status = CODEC_STATUS_ERROR;
+        } finally {
+            lock.unlock();
+        }
+        return status;
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaDrmProxy.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/MediaDrmProxy.java
@@ -1,17 +1,18 @@
 /* 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/. */
 
 
 package org.mozilla.gecko.media;
 
-import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.UUID;
+import java.util.concurrent.locks.ReentrantLock;
 
 import org.mozilla.gecko.mozglue.JNIObject;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.AppConstants;
 
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecList;
 import android.media.MediaCrypto;
@@ -34,17 +35,21 @@ public final class MediaDrmProxy {
     private static final String VORBIS = "audio/vorbis";
     @WrapForJNI
     private static final String VP8 = "video/x-vnd.on2.vp8";
     @WrapForJNI
     private static final String VP9 = "video/x-vnd.on2.vp9";
     @WrapForJNI
     private static final String OPUS = "audio/opus";
 
-    public static final ArrayList<MediaDrmProxy> sProxyList = new ArrayList<MediaDrmProxy>();
+    @WrapForJNI
+    private static boolean isPreMarshmallow = AppConstants.Versions.preMarshmallow;
+
+    public static final HashMap<String, MediaDrmProxy> sProxyMap =
+        new HashMap<String, MediaDrmProxy>();
 
     // A flag to avoid using the native object that has been destroyed.
     private boolean mDestroyed;
     private GeckoMediaDrm mImpl;
     private String mDrmStubId;
 
     private static boolean isSystemSupported() {
         // Support versions >= LOLLIPOP
@@ -263,17 +268,17 @@ public final class MediaDrmProxy {
             if (isRemote) {
                 IMediaDrmBridge remoteBridge =
                     RemoteManager.getInstance().createRemoteMediaDrmBridge(keySystem, mDrmStubId);
                 mImpl = new RemoteMediaDrmBridge(remoteBridge);
             } else {
                 mImpl = new LocalMediaDrmBridge(keySystem);
             }
             mImpl.setCallbacks(new MediaDrmProxyCallbacks(this, nativeCallbacks));
-            sProxyList.add(this);
+            sProxyMap.put(mDrmStubId, this);
         } catch (Exception e) {
             Log.e(LOGTAG, "Constructing MediaDrmProxy ... error", e);
         }
     }
 
     @WrapForJNI
     private void createSession(int createSessionToken,
                                int promiseId,
@@ -302,37 +307,111 @@ public final class MediaDrmProxy {
     private String getStubId() {
         return mDrmStubId;
     }
 
     // Get corresponding MediaCrypto object by a generated UUID for MediaCodec.
     // Will be called on MediaFormatReader's TaskQueue.
     @WrapForJNI
     public static MediaCrypto getMediaCrypto(String stubId) {
-        for (MediaDrmProxy proxy : sProxyList) {
-            if (proxy.getStubId().equals(stubId)) {
-                return proxy.getMediaCryptoFromBridge();
-            }
+        MediaDrmProxy proxy = sProxyMap.get(stubId);
+        if (proxy == null) {
+            if (DEBUG) Log.d(LOGTAG, " NULL crytpo ");
+            return null;
         }
-        if (DEBUG) Log.d(LOGTAG, " NULL crytpo ");
-        return null;
+        return proxy.getMediaCryptoFromBridge();
     }
 
     @WrapForJNI // Called when natvie object is destroyed.
     private void destroy() {
         if (DEBUG) Log.d(LOGTAG, "destroy!! Native object is destroyed.");
         if (mDestroyed) {
             return;
         }
         mDestroyed = true;
         release();
     }
 
     private void release() {
         if (DEBUG) Log.d(LOGTAG, "release");
-        sProxyList.remove(this);
+        sProxyMap.remove(mDrmStubId);
         mImpl.release();
     }
 
     private MediaCrypto getMediaCryptoFromBridge() {
         return mImpl != null ? mImpl.getMediaCrypto() : null;
     }
+
+    private static void assertTrue(boolean condition) {
+        if (DEBUG && !condition) {
+          throw new AssertionError("Expected condition to be true");
+        }
+    }
+
+    private void notifyKeyNeeded() {
+        assertTrue(mImpl != null);
+        if (DEBUG) Log.d(LOGTAG, "notifyKeyNeeded()");
+        mImpl.notifyKeyNeeded();
+    }
+
+    @WrapForJNI
+    public static void notifyKeyNeeded(String stubId) {
+        assertTrue(AppConstants.Versions.preMarshmallow);
+        MediaDrmProxy proxy = sProxyMap.get(stubId);
+        if (proxy == null) {
+            if (DEBUG) Log.d(LOGTAG, "notifyKeyNeeded() error, cannot find targe DRM stub :" + stubId);
+        }
+        proxy.notifyKeyNeeded();
+    }
+
+    private boolean waitForKey() {
+        assertTrue(mImpl != null);
+        if (DEBUG) Log.d(LOGTAG, "waitForKey()");
+        return mImpl.waitForKey();
+    }
+
+    @WrapForJNI
+    public static boolean waitForKey(String stubId) {
+        assertTrue(AppConstants.Versions.preMarshmallow);
+
+        MediaDrmProxy proxy = sProxyMap.get(stubId);
+        if (proxy == null) {
+            if (DEBUG) Log.d(LOGTAG, "waitForKey() error, cannot find targe DRM stub :" + stubId);
+            return false;
+        }
+        return proxy.waitForKey();
+    }
+
+    private void doWait() {
+        assertTrue(mImpl != null);
+        if (DEBUG) Log.d(LOGTAG, "doWait()");
+        mImpl.doWait();
+    }
+
+    @WrapForJNI
+    public static void doWait(String stubId) {
+        assertTrue(AppConstants.Versions.preMarshmallow);
+
+        MediaDrmProxy proxy = sProxyMap.get(stubId);
+        if (proxy == null) {
+            if (DEBUG) Log.d(LOGTAG, "doWait() error, cannot find targe DRM stub :" + stubId);
+            return;
+        }
+        proxy.doWait();
+    }
+
+    private ReentrantLock getKeyNeededLock() {
+        assertTrue(mImpl != null);
+        if (DEBUG) Log.d(LOGTAG, "getKeyNeededLock()");
+        return mImpl.getKeyNeededLock();
+    }
+
+    public static ReentrantLock getKeyNeededLock(String stubId) {
+        assertTrue(AppConstants.Versions.preMarshmallow);
+
+        MediaDrmProxy proxy = sProxyMap.get(stubId);
+        if (proxy == null) {
+            if (DEBUG) Log.d(LOGTAG, "getKeyNeededLock() error, cannot find targe DRM stub :" + stubId);
+            return null;
+        }
+        return proxy.getKeyNeededLock();
+    }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridge.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridge.java
@@ -1,14 +1,15 @@
 /* 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/. */
 
 package org.mozilla.gecko.media;
 
+import java.util.concurrent.locks.ReentrantLock;
 import android.media.MediaCrypto;
 import android.util.Log;
 
 final class RemoteMediaDrmBridge implements GeckoMediaDrm {
     private static final String LOGTAG = "GeckoRemoteMediaDrmBridge";
     private static final boolean DEBUG = false;
     private CallbacksForwarder mCallbacksFwd;
     private IMediaDrmBridge mRemote;
@@ -144,9 +145,35 @@ final class RemoteMediaDrmBridge impleme
     }
 
     @Override
     public synchronized MediaCrypto getMediaCrypto() {
         if (DEBUG) Log.d(LOGTAG, "getMediaCrypto(), should not enter here!");
         assertTrue(false);
         return null;
     }
+
+    @Override
+    public void notifyKeyNeeded() {
+        // Should not be used in this case.
+        assertTrue(false);
+    }
+
+    @Override
+    public boolean waitForKey() {
+        // Should not be used in this case.
+        assertTrue(false);
+        return false;
+    }
+
+    @Override
+    public void doWait() {
+        // Should not be used in this case.
+        assertTrue(false);
+    }
+
+    @Override
+    public ReentrantLock getKeyNeededLock() {
+        // Should not be used in this case.
+        assertTrue(false);
+        return null;
+    }
 }
\ No newline at end of file
--- a/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridgeStub.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/RemoteMediaDrmBridgeStub.java
@@ -1,16 +1,17 @@
 /* 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/. */
 
 package org.mozilla.gecko.media;
 import org.mozilla.gecko.AppConstants;
 
-import java.util.ArrayList;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.HashMap;
 
 import android.media.MediaCrypto;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Log;
 
 final class RemoteMediaDrmBridgeStub extends IMediaDrmBridge.Stub implements IBinder.DeathRecipient {
     private static final String LOGTAG = "GeckoRemoteMediaDrmBridgeStub";
@@ -23,37 +24,98 @@ final class RemoteMediaDrmBridgeStub ext
     // mStubId is initialized during stub construction. It should be a unique
     // string which is generated in MediaDrmProxy in Fennec App process and is
     // used for Codec to obtain corresponding MediaCrypto as input to achieve
     // decryption.
     // The generated stubId will be delivered to Codec via a code path starting
     // from MediaDrmProxy -> MediaDrmCDMProxy -> RemoteDataDecoder => IPC => Codec.
     private String mStubId = "";
 
-    public static ArrayList<RemoteMediaDrmBridgeStub> mBridgeStubs =
-        new ArrayList<RemoteMediaDrmBridgeStub>();
+    public static HashMap<String, RemoteMediaDrmBridgeStub> sBridgeStubMap =
+        new HashMap<String, RemoteMediaDrmBridgeStub>();
 
     private String getId() {
         return mStubId;
     }
 
     private MediaCrypto getMediaCryptoFromBridge() {
         return mBridge != null ? mBridge.getMediaCrypto() : null;
     }
 
     public static synchronized MediaCrypto getMediaCrypto(String stubId) {
         if (DEBUG) Log.d(LOGTAG, "getMediaCrypto()");
+        RemoteMediaDrmBridgeStub bridgeStub = sBridgeStubMap.get(stubId);
+        if (bridgeStub == null) {
+            return null;
+        }
+        return bridgeStub.getMediaCryptoFromBridge();
+    }
 
-        for (int i = 0; i < mBridgeStubs.size(); i++) {
-            if (mBridgeStubs.get(i) != null &&
-                mBridgeStubs.get(i).getId().equals(stubId)) {
-                return mBridgeStubs.get(i).getMediaCryptoFromBridge();
-            }
+    public static void notifyKeyNeeded(String stubId) {
+        assertTrue(AppConstants.Versions.preMarshmallow);
+        if (DEBUG) Log.d(LOGTAG, "notifyKeyNeeded()");
+        RemoteMediaDrmBridgeStub bridgeStub = sBridgeStubMap.get(stubId);
+        if (bridgeStub == null) {
+            if (DEBUG) Log.d(LOGTAG, "notifyKeyNeeded() error, find no stub !");
+            return;
+        }
+        bridgeStub.notifyKeyNeeded();
+    }
+
+    private void notifyKeyNeeded() {
+        assertTrue(mBridge != null);
+        mBridge.notifyKeyNeeded();
+    }
+
+    public static boolean waitForKey(String stubId) {
+        assertTrue(AppConstants.Versions.preMarshmallow);
+        if (DEBUG) Log.d(LOGTAG, "waitForKey()");
+        RemoteMediaDrmBridgeStub bridgeStub = sBridgeStubMap.get(stubId);
+        if (bridgeStub == null) {
+            if (DEBUG) Log.d(LOGTAG, "waitForKey() error, find no stub !");
+            return false;
         }
-        return null;
+        return bridgeStub.waitForKey();
+    }
+
+    private void doWait() {
+        assertTrue(mBridge != null);
+        mBridge.doWait();
+    }
+
+    public static void doWait(String stubId) {
+        assertTrue(AppConstants.Versions.preMarshmallow);
+        if (DEBUG) Log.d(LOGTAG, "wait()");
+        RemoteMediaDrmBridgeStub bridgeStub = sBridgeStubMap.get(stubId);
+        if (bridgeStub == null) {
+            if (DEBUG) Log.d(LOGTAG, "wait() error, find no stub !");
+            return;
+        }
+        bridgeStub.doWait();
+    }
+
+    private boolean waitForKey() {
+        assertTrue(mBridge != null);
+        return mBridge.waitForKey();
+    }
+
+    private ReentrantLock getKeyNeededLock() {
+        assertTrue(mBridge != null);
+        return mBridge.getKeyNeededLock();
+    }
+
+    public static ReentrantLock getKeyNeededLock(String stubId) {
+        assertTrue(AppConstants.Versions.preMarshmallow);
+        if (DEBUG) Log.d(LOGTAG, "getKeyNeededLock()");
+        RemoteMediaDrmBridgeStub bridgeStub = sBridgeStubMap.get(stubId);
+        if (bridgeStub == null) {
+            if (DEBUG) Log.d(LOGTAG, "getKeyNeededLock() error, find no stub !");
+            return null;
+        }
+        return bridgeStub.getKeyNeededLock();
     }
 
     // Callback to RemoteMediaDrmBridge.
     private final class Callbacks implements GeckoMediaDrm.Callbacks {
         private IMediaDrmBridgeCallbacks mRemoteCallbacks;
 
         public Callbacks(IMediaDrmBridgeCallbacks remote) {
             mRemoteCallbacks = remote;
@@ -134,17 +196,17 @@ final class RemoteMediaDrmBridgeStub ext
             try {
                 mRemoteCallbacks.onRejectPromise(promiseId, message);
             } catch (RemoteException e) {
                 Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
             }
         }
     }
 
-    /* package-private */ void assertTrue(boolean condition) {
+    /* package-private */ static void assertTrue(boolean condition) {
         if (DEBUG && !condition) {
             throw new AssertionError("Expected condition to be true");
         }
     }
 
     RemoteMediaDrmBridgeStub(String keySystem, String stubId) throws RemoteException {
         if (AppConstants.Versions.preLollipop) {
             Log.e(LOGTAG, "Pre-Lollipop should never enter here!!");
@@ -152,17 +214,17 @@ final class RemoteMediaDrmBridgeStub ext
         }
         try {
             if (AppConstants.Versions.preMarshmallow) {
                 mBridge = new GeckoMediaDrmBridgeV21(keySystem);
             } else {
                 mBridge = new GeckoMediaDrmBridgeV23(keySystem);
             }
             mStubId = stubId;
-            mBridgeStubs.add(this);
+            sBridgeStubMap.put(mStubId, this);
         } catch (Exception e) {
             throw new RemoteException("RemoteMediaDrmBridgeStub cannot create bridge implementation.");
         }
     }
 
     @Override
     public synchronized void setCallbacks(IMediaDrmBridgeCallbacks callbacks) throws RemoteException {
         if (DEBUG) Log.d(LOGTAG, "setCallbacks()");
@@ -229,17 +291,17 @@ final class RemoteMediaDrmBridgeStub ext
         } catch (Exception e) {
             Log.e(LOGTAG, "Exception ! Dead recipient !!", e);
         }
     }
 
     @Override
     public synchronized void release() {
         if (DEBUG) Log.d(LOGTAG, "release()");
-        mBridgeStubs.remove(this);
+        sBridgeStubMap.remove(mStubId);
         if (mBridge != null) {
             mBridge.release();
             mBridge = null;
         }
         mCallbacks.asBinder().unlinkToDeath(this, 0);
         mCallbacks = null;
         mStubId = "";
     }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -562,16 +562,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'media/Codec.java',
     'media/CodecProxy.java',
     'media/FormatParam.java',
     'media/GeckoMediaDrm.java',
     'media/GeckoMediaDrmBridgeV21.java',
     'media/GeckoMediaDrmBridgeV23.java',
     'media/JellyBeanAsyncCodec.java',
     'media/LocalMediaDrmBridge.java',
+    'media/MediaCodecUtil.java',
     'media/MediaControlService.java',
     'media/MediaDrmProxy.java',
     'media/MediaManager.java',
     'media/RemoteManager.java',
     'media/RemoteMediaDrmBridge.java',
     'media/RemoteMediaDrmBridgeStub.java',
     'media/Sample.java',
     'media/SamplePool.java',
--- a/widget/android/fennec/FennecJNIWrappers.cpp
+++ b/widget/android/fennec/FennecJNIWrappers.cpp
@@ -242,16 +242,35 @@ constexpr char CodecProxy::NativeCallbac
 constexpr char CodecProxy::NativeCallbacks::OnInputExhausted_t::signature[];
 
 constexpr char CodecProxy::NativeCallbacks::OnOutput_t::name[];
 constexpr char CodecProxy::NativeCallbacks::OnOutput_t::signature[];
 
 constexpr char CodecProxy::NativeCallbacks::OnOutputFormatChanged_t::name[];
 constexpr char CodecProxy::NativeCallbacks::OnOutputFormatChanged_t::signature[];
 
+const char MediaCodecUtil::name[] =
+        "org/mozilla/gecko/media/MediaCodecUtil";
+
+constexpr char MediaCodecUtil::QueueSecureInputBufferV21_t::name[];
+constexpr char MediaCodecUtil::QueueSecureInputBufferV21_t::signature[];
+
+auto MediaCodecUtil::QueueSecureInputBufferV21(mozilla::jni::Object::Param a0, mozilla::jni::String::Param a1, int32_t a2, int32_t a3, mozilla::jni::Object::Param a4, int64_t a5, int32_t a6) -> int32_t
+{
+    return mozilla::jni::Method<QueueSecureInputBufferV21_t>::Call(MediaCodecUtil::Context(), nullptr, a0, a1, a2, a3, a4, a5, a6);
+}
+
+constexpr char MediaCodecUtil::QueueSecureInputBufferV23_t::name[];
+constexpr char MediaCodecUtil::QueueSecureInputBufferV23_t::signature[];
+
+auto MediaCodecUtil::QueueSecureInputBufferV23(mozilla::jni::Object::Param a0, int32_t a1, int32_t a2, mozilla::jni::Object::Param a3, int64_t a4, int32_t a5) -> int32_t
+{
+    return mozilla::jni::Method<QueueSecureInputBufferV23_t>::Call(MediaCodecUtil::Context(), nullptr, a0, a1, a2, a3, a4, a5);
+}
+
 const char MediaDrmProxy::name[] =
         "org/mozilla/gecko/media/MediaDrmProxy";
 
 constexpr char MediaDrmProxy::CanDecode_t::name[];
 constexpr char MediaDrmProxy::CanDecode_t::signature[];
 
 auto MediaDrmProxy::CanDecode(mozilla::jni::String::Param a0) -> bool
 {
@@ -293,16 +312,24 @@ auto MediaDrmProxy::CreateSession(int32_
 constexpr char MediaDrmProxy::Destroy_t::name[];
 constexpr char MediaDrmProxy::Destroy_t::signature[];
 
 auto MediaDrmProxy::Destroy() const -> void
 {
     return mozilla::jni::Method<Destroy_t>::Call(MediaDrmProxy::mCtx, nullptr);
 }
 
+constexpr char MediaDrmProxy::DoWait_t::name[];
+constexpr char MediaDrmProxy::DoWait_t::signature[];
+
+auto MediaDrmProxy::DoWait(mozilla::jni::String::Param a0) -> void
+{
+    return mozilla::jni::Method<DoWait_t>::Call(MediaDrmProxy::Context(), nullptr, a0);
+}
+
 constexpr char MediaDrmProxy::GetMediaCrypto_t::name[];
 constexpr char MediaDrmProxy::GetMediaCrypto_t::signature[];
 
 auto MediaDrmProxy::GetMediaCrypto(mozilla::jni::String::Param a0) -> mozilla::jni::Object::LocalRef
 {
     return mozilla::jni::Method<GetMediaCrypto_t>::Call(MediaDrmProxy::Context(), nullptr, a0);
 }
 
@@ -317,36 +344,65 @@ auto MediaDrmProxy::GetStubId() const ->
 constexpr char MediaDrmProxy::IsSchemeSupported_t::name[];
 constexpr char MediaDrmProxy::IsSchemeSupported_t::signature[];
 
 auto MediaDrmProxy::IsSchemeSupported(mozilla::jni::String::Param a0) -> bool
 {
     return mozilla::jni::Method<IsSchemeSupported_t>::Call(MediaDrmProxy::Context(), nullptr, a0);
 }
 
+constexpr char MediaDrmProxy::NotifyKeyNeeded_t::name[];
+constexpr char MediaDrmProxy::NotifyKeyNeeded_t::signature[];
+
+auto MediaDrmProxy::NotifyKeyNeeded(mozilla::jni::String::Param a0) -> void
+{
+    return mozilla::jni::Method<NotifyKeyNeeded_t>::Call(MediaDrmProxy::Context(), nullptr, a0);
+}
+
 constexpr char MediaDrmProxy::UpdateSession_t::name[];
 constexpr char MediaDrmProxy::UpdateSession_t::signature[];
 
 auto MediaDrmProxy::UpdateSession(int32_t a0, mozilla::jni::String::Param a1, mozilla::jni::ByteArray::Param a2) const -> void
 {
     return mozilla::jni::Method<UpdateSession_t>::Call(MediaDrmProxy::mCtx, nullptr, a0, a1, a2);
 }
 
+constexpr char MediaDrmProxy::WaitForKey_t::name[];
+constexpr char MediaDrmProxy::WaitForKey_t::signature[];
+
+auto MediaDrmProxy::WaitForKey(mozilla::jni::String::Param a0) -> bool
+{
+    return mozilla::jni::Method<WaitForKey_t>::Call(MediaDrmProxy::Context(), nullptr, a0);
+}
+
 const char16_t MediaDrmProxy::AAC[] = u"audio/mp4a-latm";
 
 const char16_t MediaDrmProxy::AVC[] = u"video/avc";
 
 const char16_t MediaDrmProxy::OPUS[] = u"audio/opus";
 
 const char16_t MediaDrmProxy::VORBIS[] = u"audio/vorbis";
 
 const char16_t MediaDrmProxy::VP8[] = u"video/x-vnd.on2.vp8";
 
 const char16_t MediaDrmProxy::VP9[] = u"video/x-vnd.on2.vp9";
 
+constexpr char MediaDrmProxy::IsPreMarshmallow_t::name[];
+constexpr char MediaDrmProxy::IsPreMarshmallow_t::signature[];
+
+auto MediaDrmProxy::IsPreMarshmallow() -> bool
+{
+    return mozilla::jni::Field<IsPreMarshmallow_t>::Get(MediaDrmProxy::Context(), nullptr);
+}
+
+auto MediaDrmProxy::IsPreMarshmallow(bool a0) -> void
+{
+    return mozilla::jni::Field<IsPreMarshmallow_t>::Set(MediaDrmProxy::Context(), nullptr, a0);
+}
+
 const char MediaDrmProxy::NativeMediaDrmProxyCallbacks::name[] =
         "org/mozilla/gecko/media/MediaDrmProxy$NativeMediaDrmProxyCallbacks";
 
 constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::New_t::name[];
 constexpr char MediaDrmProxy::NativeMediaDrmProxyCallbacks::New_t::signature[];
 
 auto MediaDrmProxy::NativeMediaDrmProxyCallbacks::New() -> NativeMediaDrmProxyCallbacks::LocalRef
 {
--- a/widget/android/fennec/FennecJNIWrappers.h
+++ b/widget/android/fennec/FennecJNIWrappers.h
@@ -885,16 +885,85 @@ public:
     };
 
     static const mozilla::jni::CallingThread callingThread =
             mozilla::jni::CallingThread::ANY;
 
     template<class Impl> class Natives;
 };
 
+class MediaCodecUtil : public mozilla::jni::ObjectBase<MediaCodecUtil>
+{
+public:
+    static const char name[];
+
+    explicit MediaCodecUtil(const Context& ctx) : ObjectBase<MediaCodecUtil>(ctx) {}
+
+    struct QueueSecureInputBufferV21_t {
+        typedef MediaCodecUtil Owner;
+        typedef int32_t ReturnType;
+        typedef int32_t SetterType;
+        typedef mozilla::jni::Args<
+                mozilla::jni::Object::Param,
+                mozilla::jni::String::Param,
+                int32_t,
+                int32_t,
+                mozilla::jni::Object::Param,
+                int64_t,
+                int32_t> Args;
+        static constexpr char name[] = "queueSecureInputBufferV21";
+        static constexpr char signature[] =
+                "(Landroid/media/MediaCodec;Ljava/lang/String;IILandroid/media/MediaCodec$CryptoInfo;JI)I";
+        static const bool isStatic = true;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    static auto QueueSecureInputBufferV21(mozilla::jni::Object::Param, mozilla::jni::String::Param, int32_t, int32_t, mozilla::jni::Object::Param, int64_t, int32_t) -> int32_t;
+
+    struct QueueSecureInputBufferV23_t {
+        typedef MediaCodecUtil Owner;
+        typedef int32_t ReturnType;
+        typedef int32_t SetterType;
+        typedef mozilla::jni::Args<
+                mozilla::jni::Object::Param,
+                int32_t,
+                int32_t,
+                mozilla::jni::Object::Param,
+                int64_t,
+                int32_t> Args;
+        static constexpr char name[] = "queueSecureInputBufferV23";
+        static constexpr char signature[] =
+                "(Landroid/media/MediaCodec;IILandroid/media/MediaCodec$CryptoInfo;JI)I";
+        static const bool isStatic = true;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    static auto QueueSecureInputBufferV23(mozilla::jni::Object::Param, int32_t, int32_t, mozilla::jni::Object::Param, int64_t, int32_t) -> int32_t;
+
+    static const int32_t CODEC_STATUS_ERROR = 2;
+
+    static const int32_t CODEC_STATUS_NO_KEY = 1;
+
+    static const int32_t CODEC_STATUS_OK = 0;
+
+    static const mozilla::jni::CallingThread callingThread =
+            mozilla::jni::CallingThread::ANY;
+
+};
+
 class MediaDrmProxy : public mozilla::jni::ObjectBase<MediaDrmProxy>
 {
 public:
     static const char name[];
 
     explicit MediaDrmProxy(const Context& ctx) : ObjectBase<MediaDrmProxy>(ctx) {}
 
     class NativeMediaDrmProxyCallbacks;
@@ -1020,16 +1089,36 @@ public:
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::ANY;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
     auto Destroy() const -> void;
 
+    struct DoWait_t {
+        typedef MediaDrmProxy Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                mozilla::jni::String::Param> Args;
+        static constexpr char name[] = "doWait";
+        static constexpr char signature[] =
+                "(Ljava/lang/String;)V";
+        static const bool isStatic = true;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    static auto DoWait(mozilla::jni::String::Param) -> void;
+
     struct GetMediaCrypto_t {
         typedef MediaDrmProxy Owner;
         typedef mozilla::jni::Object::LocalRef ReturnType;
         typedef mozilla::jni::Object::Param SetterType;
         typedef mozilla::jni::Args<
                 mozilla::jni::String::Param> Args;
         static constexpr char name[] = "getMediaCrypto";
         static constexpr char signature[] =
@@ -1079,16 +1168,36 @@ public:
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::ANY;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
     static auto IsSchemeSupported(mozilla::jni::String::Param) -> bool;
 
+    struct NotifyKeyNeeded_t {
+        typedef MediaDrmProxy Owner;
+        typedef void ReturnType;
+        typedef void SetterType;
+        typedef mozilla::jni::Args<
+                mozilla::jni::String::Param> Args;
+        static constexpr char name[] = "notifyKeyNeeded";
+        static constexpr char signature[] =
+                "(Ljava/lang/String;)V";
+        static const bool isStatic = true;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    static auto NotifyKeyNeeded(mozilla::jni::String::Param) -> void;
+
     struct UpdateSession_t {
         typedef MediaDrmProxy Owner;
         typedef void ReturnType;
         typedef void SetterType;
         typedef mozilla::jni::Args<
                 int32_t,
                 mozilla::jni::String::Param,
                 mozilla::jni::ByteArray::Param> Args;
@@ -1101,28 +1210,69 @@ public:
         static const mozilla::jni::CallingThread callingThread =
                 mozilla::jni::CallingThread::ANY;
         static const mozilla::jni::DispatchTarget dispatchTarget =
                 mozilla::jni::DispatchTarget::CURRENT;
     };
 
     auto UpdateSession(int32_t, mozilla::jni::String::Param, mozilla::jni::ByteArray::Param) const -> void;
 
+    struct WaitForKey_t {
+        typedef MediaDrmProxy Owner;
+        typedef bool ReturnType;
+        typedef bool SetterType;
+        typedef mozilla::jni::Args<
+                mozilla::jni::String::Param> Args;
+        static constexpr char name[] = "waitForKey";
+        static constexpr char signature[] =
+                "(Ljava/lang/String;)Z";
+        static const bool isStatic = true;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    static auto WaitForKey(mozilla::jni::String::Param) -> bool;
+
     static const char16_t AAC[];
 
     static const char16_t AVC[];
 
     static const char16_t OPUS[];
 
     static const char16_t VORBIS[];
 
     static const char16_t VP8[];
 
     static const char16_t VP9[];
 
+    struct IsPreMarshmallow_t {
+        typedef MediaDrmProxy Owner;
+        typedef bool ReturnType;
+        typedef bool SetterType;
+        typedef mozilla::jni::Args<> Args;
+        static constexpr char name[] = "isPreMarshmallow";
+        static constexpr char signature[] =
+                "Z";
+        static const bool isStatic = true;
+        static const mozilla::jni::ExceptionMode exceptionMode =
+                mozilla::jni::ExceptionMode::ABORT;
+        static const mozilla::jni::CallingThread callingThread =
+                mozilla::jni::CallingThread::ANY;
+        static const mozilla::jni::DispatchTarget dispatchTarget =
+                mozilla::jni::DispatchTarget::CURRENT;
+    };
+
+    static auto IsPreMarshmallow() -> bool;
+
+    static auto IsPreMarshmallow(bool) -> void;
+
     static const mozilla::jni::CallingThread callingThread =
             mozilla::jni::CallingThread::ANY;
 
 };
 
 class MediaDrmProxy::NativeMediaDrmProxyCallbacks : public mozilla::jni::ObjectBase<NativeMediaDrmProxyCallbacks>
 {
 public: