Bug 1317628-[P2] Handling key-needed/key-added/notifying for encrypted media playback with Fennec out-of-process-decode mode on Android L.
MozReview-Commit-ID: 52QKbbiNFHM
--- a/mobile/android/base/java/org/mozilla/gecko/media/AsyncCodec.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/AsyncCodec.java
@@ -30,9 +30,12 @@ public interface AsyncCodec {
public abstract void stop();
public abstract void flush();
public abstract void release();
public abstract ByteBuffer getInputBuffer(int index);
public abstract ByteBuffer getOutputBuffer(int index);
public abstract void queueInputBuffer(int index, int offset, int size, long presentationTimeUs, int flags);
public abstract void queueSecureInputBuffer(int index, int offset, CryptoInfo info, long presentationTimeUs, int flags);
public abstract void releaseOutputBuffer(int index, boolean render);
+
+ public abstract void setDrmStubId(String drmStubId);
+ public abstract void notifyKeyAdded();
}
--- a/mobile/android/base/java/org/mozilla/gecko/media/Codec.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/Codec.java
@@ -1,14 +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 org.mozilla.gecko.util.MediaCodecUtil;
+
import android.media.MediaCodec;
import android.media.MediaCodecInfo;
import android.media.MediaCodecList;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.TransactionTooLargeException;
@@ -25,16 +28,31 @@ import java.util.concurrent.ConcurrentLi
/* package */ final class Codec extends ICodec.Stub implements IBinder.DeathRecipient {
private static final String LOGTAG = "GeckoRemoteCodec";
private static final boolean DEBUG = false;
public enum Error {
DECODE, FATAL
};
+ // As a callback listener which is registered into GeckoMediaDrmBridge
+ // for notifying Codec that keys are ready.
+ public final class CodecKeyWaiter implements MediaCodecUtil.KeyWaiter{
+ private AsyncCodec mCodecImpl;
+
+ CodecKeyWaiter(AsyncCodec codec) {
+ mCodecImpl = codec;
+ }
+
+ @Override
+ public void onKeyAdded() {
+ mCodecImpl.notifyKeyAdded();
+ }
+ }
+
private final class Callbacks implements AsyncCodec.Callbacks {
private ICodecCallbacks mRemote;
private boolean mHasInputCapacitySet;
private boolean mHasOutputCapacitySet;
public Callbacks(ICodecCallbacks remote) {
mRemote = remote;
}
@@ -158,16 +176,21 @@ import java.util.concurrent.ConcurrentLi
return false;
}
feedSampleToBuffer();
return true;
}
private void feedSampleToBuffer() {
while (!mAvailableInputBuffers.isEmpty() && !mInputSamples.isEmpty()) {
+ if (AppConstants.Versions.preMarshmallow &&
+ RemoteMediaDrmBridgeStub.waitForKey(mDrmStubId)) {
+ // Wait until keys being added to schedule another run.
+ return;
+ }
int index = mAvailableInputBuffers.poll();
int len = 0;
Sample sample = mInputSamples.poll();
long pts = sample.info.presentationTimeUs;
int flags = sample.info.flags;
MediaCodec.CryptoInfo cryptoInfo = sample.cryptoInfo;
if (!sample.isEOS() && sample.buffer != null) {
len = sample.info.size;
@@ -200,16 +223,17 @@ import java.util.concurrent.ConcurrentLi
private volatile ICodecCallbacks mCallbacks;
private AsyncCodec mCodec;
private InputProcessor mInputProcessor;
private volatile boolean mFlushing = false;
private SamplePool mSamplePool;
private Queue<Sample> mSentOutputs = new ConcurrentLinkedQueue<>();
// Value will be updated after configure called.
private volatile boolean mIsAdaptivePlaybackSupported = false;
+ private String mDrmStubId;
public synchronized void setCallbacks(ICodecCallbacks callbacks) throws RemoteException {
mCallbacks = callbacks;
callbacks.asBinder().linkToDeath(this, 0);
}
// IBinder.DeathRecipient
@Override
@@ -268,16 +292,23 @@ import java.util.concurrent.ConcurrentLi
fmt.setInteger(MediaFormat.KEY_MAX_HEIGHT, 1080);
}
}
codec.configure(fmt, surface, crypto, flags);
mCodec = codec;
mInputProcessor = new InputProcessor();
mSamplePool = new SamplePool(codecName);
+
+ if (crypto != null && AppConstants.Versions.preMarshmallow) {
+ mDrmStubId = drmStubId;
+ mCodec.setDrmStubId(drmStubId);
+ RemoteMediaDrmBridgeStub.setupKeyAddedCallback(drmStubId,
+ new CodecKeyWaiter(mCodec));
+ }
if (DEBUG) Log.d(LOGTAG, codec.toString() + " created");
return true;
} catch (Exception e) {
if (DEBUG) Log.d(LOGTAG, "FAIL: cannot create codec -- " + codecName);
e.printStackTrace();
return false;
}
}
--- 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 org.mozilla.gecko.util.MediaCodecUtil;
public interface GeckoMediaDrm {
public interface Callbacks {
void onSessionCreated(int createSessionToken,
int promiseId,
byte[] sessionId,
byte[] request);
void onSessionUpdated(int promiseId, byte[] sessionId);
@@ -28,12 +29,13 @@ public interface GeckoMediaDrm {
int promiseId,
String initDataType,
byte[] initData);
void updateSession(int promiseId, String sessionId, byte[] response);
void closeSession(int promiseId, String sessionId);
void release();
MediaCrypto getMediaCrypto();
+ void setupKeyAddedCallback(MediaCodecUtil.KeyWaiter waiter);
void maybeNotifyKeyNeeded(int licensedSessions);
boolean waitForKey();
int getLicensedSessionCount();
}
--- a/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV21.java
@@ -1,26 +1,28 @@
/* 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.util.MediaCodecUtil;
import java.lang.*;
import java.io.BufferedReader;
import java.io.IOException;
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.ArrayList;
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;
@@ -28,33 +30,35 @@ import android.media.MediaDrm;
import android.media.MediaDrmException;
import android.media.NotProvisionedException;
import android.util.Log;
public class GeckoMediaDrmBridgeV21 implements GeckoMediaDrm {
protected final String LOGTAG;
private static final String INVALID_SESSION_ID = "Invalid";
private static final String WIDEVINE_KEY_SYSTEM = "com.widevine.alpha";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private static final UUID WIDEVINE_SCHEME_UUID =
new UUID(0xedef8ba979d64aceL, 0xa3c827dcd51d21edL);
// MediaDrm.KeyStatus information listener is supported on M+, adding a
// dummy key id to report key status.
private static final byte[] DUMMY_KEY_ID = new byte[] {0};
private UUID mSchemeUUID;
private Handler mHandler;
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 ArrayList<MediaCodecUtil.KeyWaiter> mKeyWaiters =
+ new ArrayList<MediaCodecUtil.KeyWaiter>();
private boolean mKeyNeeded = false;
private HashSet<String> mOngoingSessionIds = new HashSet<String>();
private int mLicensedSessions = 0;
private MediaCrypto mCrypto;
protected MediaDrm mDrm;
@@ -282,21 +286,31 @@ public class GeckoMediaDrmBridgeV21 impl
@Override
public MediaCrypto getMediaCrypto() {
if (DEBUG) Log.d(LOGTAG, "getMediaCrypto()");
return mCrypto;
}
@Override
+ public void setupKeyAddedCallback(MediaCodecUtil.KeyWaiter callback) {
+ assertTrue(callback != null);
+ if (DEBUG) Log.d(LOGTAG, "setupKeyAddedCallback()");
+ mKeyWaiters.add(callback);
+ }
+
+ @Override
public void maybeNotifyKeyNeeded(int licensedSessions) {
- if (DEBUG) Log.d(LOGTAG, "notifyKeyNeeded()");
+ if (DEBUG) Log.d(LOGTAG, "maybeNotifyKeyNeeded()");
synchronized (this) {
if (mLicensedSessions > licensedSessions && mOngoingSessionIds.isEmpty()) {
if (DEBUG) Log.d(LOGTAG, "Additional license was provided, may not need a new key.");
+ for (MediaCodecUtil.KeyWaiter waiter : mKeyWaiters) {
+ waiter.onKeyAdded();
+ }
return;
}
mKeyNeeded = true;
}
}
@Override
public boolean waitForKey() {
@@ -312,16 +326,19 @@ public class GeckoMediaDrmBridgeV21 impl
private void notifyKeyAdded(String sessionId) {
if (DEBUG) Log.d(LOGTAG, "notifyKeyAdded() ");
synchronized (this) {
mLicensedSessions += 1;
boolean deleted = mOngoingSessionIds.remove(sessionId);
if (deleted && mOngoingSessionIds.isEmpty()) {
mKeyNeeded = false;
+ for (MediaCodecUtil.KeyWaiter waiter : mKeyWaiters) {
+ waiter.onKeyAdded();
+ }
}
}
}
protected void onSessionCreated(int createSessionToken,
int promiseId,
byte[] sessionId,
byte[] request) {
--- a/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV23.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/GeckoMediaDrmBridgeV23.java
@@ -1,13 +1,14 @@
/* 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.util.MediaCodecUtil;
import android.annotation.TargetApi;
import android.media.DeniedByServerException;
import android.media.NotProvisionedException;
import static android.os.Build.VERSION_CODES.M;
import android.media.MediaDrm;
import android.util.Log;
@@ -63,16 +64,22 @@ public class GeckoMediaDrmBridgeV23 exte
protected void onSessionMessage(byte[] sessionId,
int sessionMessageType,
byte[] request) {
assertTrue(mCallbacks != null);
mCallbacks.onSessionMessage(sessionId, sessionMessageType, request);
}
@Override
+ public void setupKeyAddedCallback(MediaCodecUtil.KeyWaiter callback) {
+ // Should not be used after Android M.
+ assertTrue(false);
+ }
+
+ @Override
public void maybeNotifyKeyNeeded(int licensedSessions) {
// Should not be used after Android M.
assertTrue(false);
}
@Override
public boolean waitForKey() {
// Should not be used after Android M.
--- a/mobile/android/base/java/org/mozilla/gecko/media/JellyBeanAsyncCodec.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/JellyBeanAsyncCodec.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 org.mozilla.gecko.util.HardwareCodecCapabilityUtils;
+import org.mozilla.gecko.util.MediaCodecUtil;
import android.media.MediaCodec;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
@@ -21,18 +23,16 @@ import java.nio.ByteBuffer;
// Implement async API using MediaCodec sync mode (API v16).
// This class uses internal worker thread/handler (mBufferPoller) to poll
// input and output buffer and notifies the client through callbacks.
final class JellyBeanAsyncCodec implements AsyncCodec {
private static final String LOGTAG = "GeckoAsyncCodecAPIv16";
private static final boolean DEBUG = false;
- private static final int ERROR_CODEC = -10000;
-
private abstract class CancelableHandler extends Handler {
private static final int MSG_CANCELLATION = 0x434E434C; // 'CNCL'
protected CancelableHandler(Looper looper) {
super(looper);
}
protected void cancel() {
@@ -199,17 +199,17 @@ final class JellyBeanAsyncCodec implemen
case MSG_POLL_OUTPUT_BUFFERS:
pollOutputBuffer();
break;
default:
return false;
}
} catch (IllegalStateException e) {
e.printStackTrace();
- mCallbackSender.notifyError(ERROR_CODEC);
+ mCallbackSender.notifyError(MediaCodecUtil.CODEC_STATUS_ERROR);
}
return true;
}
private void pollInputBuffer() {
int result = mCodec.dequeueInputBuffer(DEQUEUE_TIMEOUT_US);
if (result >= 0) {
@@ -253,16 +253,21 @@ final class JellyBeanAsyncCodec implemen
private ByteBuffer[] mOutputBuffers;
private AsyncCodec.Callbacks mCallbacks;
private CallbackSender mCallbackSender;
private BufferPoller mBufferPoller;
private volatile boolean mInputEnded;
private volatile boolean mOutputEnded;
+ // A lock to block the input procedure when encountering no-key error.
+ final Object waitForKeyLock = new Object();
+ private boolean waitForKey = false;
+ private String mDrmStubId;
+
// Must be called on a thread with looper.
/* package */ JellyBeanAsyncCodec(String name) throws IOException {
mCodec = MediaCodec.createByCodecName(name);
initBufferPoller(name + " buffer poller");
}
private void initBufferPoller(String name) {
if (mBufferPoller != null) {
@@ -331,17 +336,17 @@ final class JellyBeanAsyncCodec implemen
assertCallbacks();
mInputEnded = (flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
try {
mCodec.queueInputBuffer(index, offset, size, presentationTimeUs, flags);
} catch (IllegalStateException e) {
e.printStackTrace();
- mCallbackSender.notifyError(ERROR_CODEC);
+ mCallbackSender.notifyError(MediaCodecUtil.CODEC_STATUS_ERROR);
return;
}
mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_OUTPUT_BUFFERS);
mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_INPUT_BUFFERS);
}
@Override
@@ -349,26 +354,44 @@ final class JellyBeanAsyncCodec implemen
int offset,
MediaCodec.CryptoInfo cryptoInfo,
long presentationTimeUs,
int flags) {
assertCallbacks();
mInputEnded = (flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
- try {
- mCodec.queueSecureInputBuffer(index, offset, cryptoInfo, presentationTimeUs, flags);
- } catch (IllegalStateException e) {
- e.printStackTrace();
- mCallbackSender.notifyError(ERROR_CODEC);
+ int count = AppConstants.Versions.preMarshmallow ?
+ RemoteMediaDrmBridgeStub.getLicensedSessionCount(mDrmStubId) : 0;
+
+ int res = MediaCodecUtil.queueSecureInputBuffer(mCodec, index, offset, cryptoInfo, presentationTimeUs, flags);
+ if (res != MediaCodecUtil.CODEC_STATUS_OK) {
+ if (DEBUG) Log.d(LOGTAG, "Got MediaCodecUtil Error : " + res);
+ if (AppConstants.Versions.preMarshmallow &&
+ res == MediaCodecUtil.CODEC_STATUS_NO_KEY) {
+ RemoteMediaDrmBridgeStub.maybeNotifyKeyNeeded(mDrmStubId, count);
+ if (RemoteMediaDrmBridgeStub.waitForKey(mDrmStubId)) {
+ synchronized (waitForKeyLock) {
+ try {
+ waitForKey = true;
+ waitForKeyLock.wait();
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Got exception while waiting for keys.", e);
+ }
+ }
+ }
+ if (DEBUG) Log.d(LOGTAG, "going to queueSecureInputBuffer 2nd try.");
+ MediaCodecUtil.queueSecureInputBuffer(mCodec, index, offset, cryptoInfo, presentationTimeUs, flags);
+ } else {
+ mCallbackSender.notifyError(res);
+ }
return;
}
-
+ mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_OUTPUT_BUFFERS);
mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_INPUT_BUFFERS);
- mBufferPoller.schedulePolling(BufferPoller.MSG_POLL_OUTPUT_BUFFERS);
}
@Override
public final void releaseOutputBuffer(int index, boolean render) {
assertCallbacks();
mCodec.releaseOutputBuffer(index, render);
}
@@ -418,16 +441,36 @@ final class JellyBeanAsyncCodec implemen
assertCallbacks();
cancelPendingTasks();
mCallbackSender = null;
mCodec.release();
stopBufferPoller();
}
+ @Override
+ public void setDrmStubId(String drmStubId) {
+ mDrmStubId = drmStubId;
+ }
+
+ @Override
+ public void notifyKeyAdded() {
+ if (DEBUG) Log.d(LOGTAG, "notifyKeyAdded()");
+ synchronized (waitForKeyLock) {
+ try {
+ if (waitForKey) {
+ waitForKey = false;
+ waitForKeyLock.notify();
+ }
+ } catch (Exception e) {
+ Log.e(LOGTAG, "Got exception : ", e);
+ }
+ }
+ }
+
private void stopBufferPoller() {
if (mBufferPoller == null) {
Log.e(LOGTAG, "no initialized poller.");
return;
}
mBufferPoller.getLooper().quit();
mBufferPoller = null;
--- a/mobile/android/base/java/org/mozilla/gecko/media/LocalMediaDrmBridge.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/LocalMediaDrmBridge.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 org.mozilla.gecko.AppConstants;
+import org.mozilla.gecko.util.MediaCodecUtil;
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;
@@ -156,16 +157,21 @@ final class LocalMediaDrmBridge implemen
@Override
public synchronized MediaCrypto getMediaCrypto() {
if (DEBUG) Log.d(LOGTAG, "getMediaCrypto()");
return mBridge != null ? mBridge.getMediaCrypto() : null;
}
@Override
+ public void setupKeyAddedCallback(MediaCodecUtil.KeyWaiter callback) {
+ assertTrue(false);
+ }
+
+ @Override
public void maybeNotifyKeyNeeded(int licensedSessions) {
assertTrue(mBridge != null);
mBridge.maybeNotifyKeyNeeded(licensedSessions);
}
@Override
public boolean waitForKey() {
assertTrue(mBridge != null);
--- a/mobile/android/base/java/org/mozilla/gecko/media/MediaDrmProxy.java
+++ b/mobile/android/base/java/org/mozilla/gecko/media/MediaDrmProxy.java
@@ -367,17 +367,16 @@ public final class MediaDrmProxy {
assertTrue(mImpl != null);
if (DEBUG) Log.d(LOGTAG, "waitForKey()");
return mImpl.waitForKey();
}
@WrapForJNI
public static synchronized 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();
}
--- 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,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 org.mozilla.gecko.util.MediaCodecUtil;
+
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;
@@ -146,16 +148,22 @@ 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 setupKeyAddedCallback(MediaCodecUtil.KeyWaiter callback) {
+ // Should not be used in this case.
+ assertTrue(false);
+ }
+
+ @Override
public void maybeNotifyKeyNeeded(int licensedSessions) {
// Should not be used in this case.
assertTrue(false);
}
@Override
public boolean waitForKey() {
// Should not be used in this case.
@@ -164,9 +172,9 @@ final class RemoteMediaDrmBridge impleme
}
@Override
public int getLicensedSessionCount() {
// Should not be used in this case.
assertTrue(false);
return 0;
}
-}
\ 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,25 +1,26 @@
/* 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 org.mozilla.gecko.util.MediaCodecUtil;
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";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
private volatile IMediaDrmBridgeCallbacks mCallbacks = null;
// Underlying bridge implmenetaion, i.e. GeckoMediaDrmBrdigeV21.
private GeckoMediaDrm mBridge = null;
// 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
@@ -38,21 +39,40 @@ final class RemoteMediaDrmBridgeStub ext
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) {
+ if (DEBUG) Log.d(LOGTAG, "getMediaCrypto() error, find no stub !");
return null;
}
return bridgeStub.getMediaCryptoFromBridge();
}
+ public static synchronized void setupKeyAddedCallback(String stubId,
+ MediaCodecUtil.KeyWaiter callback) {
+ assertTrue(AppConstants.Versions.preMarshmallow);
+ if (DEBUG) Log.d(LOGTAG, "setupKeyAddedCallback()");
+
+ RemoteMediaDrmBridgeStub bridgeStub = sBridgeStubMap.get(stubId);
+ if (bridgeStub == null) {
+ if (DEBUG) Log.d(LOGTAG, "setupKeyAddedCallback() error, find no stub !");
+ return;
+ }
+ bridgeStub.setupKeyAddedCallback(callback);
+ }
+
+ private void setupKeyAddedCallback(MediaCodecUtil.KeyWaiter callback) {
+ assertTrue(mBridge != null);
+ mBridge.setupKeyAddedCallback(callback);
+ }
+
public static synchronized void maybeNotifyKeyNeeded(String stubId, int licensedSessions) {
assertTrue(AppConstants.Versions.preMarshmallow);
if (DEBUG) Log.d(LOGTAG, "maybeNotifyKeyNeeded()");
RemoteMediaDrmBridgeStub bridgeStub = sBridgeStubMap.get(stubId);
if (bridgeStub == null) {
if (DEBUG) Log.d(LOGTAG, "maybeNotifyKeyNeeded() error, find no stub !");
return;
}
@@ -75,16 +95,32 @@ final class RemoteMediaDrmBridgeStub ext
return bridgeStub.waitForKey();
}
private boolean waitForKey() {
assertTrue(mBridge != null);
return mBridge.waitForKey();
}
+ public static synchronized int getLicensedSessionCount(String stubId) {
+ assertTrue(AppConstants.Versions.preMarshmallow);
+ if (DEBUG) Log.d(LOGTAG, "getLicensedSessionCount()");
+ RemoteMediaDrmBridgeStub bridgeStub = sBridgeStubMap.get(stubId);
+ if (bridgeStub == null) {
+ if (DEBUG) Log.d(LOGTAG, "getLicensedSessionCount() error, find no stub !");
+ return 0;
+ }
+ return bridgeStub.getLicensedSessionCount();
+ }
+
+ private int getLicensedSessionCount() {
+ assertTrue(mBridge != null);
+ return mBridge.getLicensedSessionCount();
+ }
+
// Callback to RemoteMediaDrmBridge.
private final class Callbacks implements GeckoMediaDrm.Callbacks {
private IMediaDrmBridgeCallbacks mRemoteCallbacks;
public Callbacks(IMediaDrmBridgeCallbacks remote) {
mRemoteCallbacks = remote;
}
--- a/mobile/android/base/java/org/mozilla/gecko/util/MediaCodecUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/util/MediaCodecUtil.java
@@ -38,9 +38,13 @@ public final class MediaCodecUtil {
return CODEC_STATUS_NO_KEY;
}
return CODEC_STATUS_ERROR;
} catch (Exception e) {
return CODEC_STATUS_ERROR;
}
return CODEC_STATUS_OK;
}
+
+ public interface KeyWaiter {
+ public void onKeyAdded();
+ }
}