diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java index 4f187264a6..e82c7e46b2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSession.java @@ -32,7 +32,6 @@ import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; /** * A {@link DrmSession} that supports playbacks using {@link ExoMediaDrm}. @@ -41,12 +40,29 @@ import java.util.concurrent.atomic.AtomicBoolean; /* package */ class DefaultDrmSession implements DrmSession { /** - * Listener of {@link DefaultDrmSession} events. + * Manages provisioning requests. */ - public interface EventListener { + public interface ProvisioningManager { /** - * Called each time provision is completed. + * Called when a session requires provisioning. The manager may call + * {@link #provision()} to have this session perform the provisioning operation. The manager + * will call {@link DefaultDrmSession#onProvisionCompleted()} when provisioning has + * completed, or {@link DefaultDrmSession#onProvisionError} if provisioning fails. + * + * @param session The session. + */ + void provisionRequired(DefaultDrmSession session); + + /** + * Called by a session when it fails to perform a provisioning operation. + * + * @param error The error that occurred. + */ + void onProvisionError(Exception error); + + /** + * Called by a session when it successfully completes a provisioning operation. */ void onProvisionCompleted(); @@ -59,14 +75,13 @@ import java.util.concurrent.atomic.AtomicBoolean; private static final int MAX_LICENSE_DURATION_TO_RENEW = 60; private final ExoMediaDrm mediaDrm; + private final ProvisioningManager provisioningManager; private final byte[] initData; private final String mimeType; private final @DefaultDrmSessionManager.Mode int mode; private final HashMap optionalKeyRequestParameters; private final Handler eventHandler; private final DefaultDrmSessionManager.EventListener eventListener; - private final AtomicBoolean provisioningInProgress; - private final EventListener sessionEventListener; /* package */ final MediaDrmCallback callback; /* package */ final UUID uuid; @@ -87,6 +102,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * * @param uuid The UUID of the drm scheme. * @param mediaDrm The media DRM. + * @param provisioningManager The manager for provisioning. * @param initData The DRM init data. * @param mode The DRM mode. * @param offlineLicenseKeySetId The offlineLicense KeySetId. @@ -96,13 +112,14 @@ import java.util.concurrent.atomic.AtomicBoolean; * @param eventHandler The handler to post listener events. * @param eventListener The DRM session manager event listener. */ - public DefaultDrmSession(UUID uuid, ExoMediaDrm mediaDrm, byte[] initData, String mimeType, + public DefaultDrmSession(UUID uuid, ExoMediaDrm mediaDrm, + ProvisioningManager provisioningManager, byte[] initData, String mimeType, @DefaultDrmSessionManager.Mode int mode, byte[] offlineLicenseKeySetId, HashMap optionalKeyRequestParameters, MediaDrmCallback callback, Looper playbackLooper, Handler eventHandler, - DefaultDrmSessionManager.EventListener eventListener, AtomicBoolean provisioningInProgress, - EventListener sessionEventListener) { + DefaultDrmSessionManager.EventListener eventListener) { this.uuid = uuid; + this.provisioningManager = provisioningManager; this.mediaDrm = mediaDrm; this.mode = mode; this.offlineLicenseKeySetId = offlineLicenseKeySetId; @@ -111,8 +128,6 @@ import java.util.concurrent.atomic.AtomicBoolean; this.eventHandler = eventHandler; this.eventListener = eventListener; - this.provisioningInProgress = provisioningInProgress; - this.sessionEventListener = sessionEventListener; state = STATE_OPENING; postResponseHandler = new PostResponseHandler(playbackLooper); @@ -164,7 +179,7 @@ import java.util.concurrent.atomic.AtomicBoolean; return false; } - public boolean canReuse(byte[] initData) { + public boolean hasInitData(byte[] initData) { return Arrays.equals(this.initData, initData); } @@ -172,7 +187,24 @@ import java.util.concurrent.atomic.AtomicBoolean; return Arrays.equals(this.sessionId, sessionId); } - // DrmSession Implementation. + // Provisioning implementation. + + public void provision() { + ProvisionRequest request = mediaDrm.getProvisionRequest(); + postRequestHandler.obtainMessage(MSG_PROVISION, request).sendToTarget(); + } + + public void onProvisionCompleted() { + if (openInternal(false)) { + doLicense(); + } + } + + public void onProvisionError(Exception error) { + onError(error); + } + + // DrmSession implementation. @Override @DrmSession.State @@ -221,7 +253,7 @@ import java.util.concurrent.atomic.AtomicBoolean; return true; } catch (NotProvisionedException e) { if (allowProvisioning) { - postProvisionRequest(); + provisioningManager.provisionRequired(this); } else { onError(e); } @@ -232,42 +264,25 @@ import java.util.concurrent.atomic.AtomicBoolean; return false; } - private void postProvisionRequest() { - if (provisioningInProgress.getAndSet(true)) { + private void onProvisionResponse(Object response) { + if (state != STATE_OPENING && !isOpen()) { + // This event is stale. return; } - ProvisionRequest request = mediaDrm.getProvisionRequest(); - postRequestHandler.obtainMessage(MSG_PROVISION, request).sendToTarget(); - } - private void onProvisionResponse(Object response) { - provisioningInProgress.set(false); if (response instanceof Exception) { - onError((Exception) response); + provisioningManager.onProvisionError((Exception) response); return; } try { mediaDrm.provideProvisionResponse((byte[]) response); } catch (DeniedByServerException e) { - onError(e); + provisioningManager.onProvisionError(e); return; } - if (sessionEventListener != null) { - sessionEventListener.onProvisionCompleted(); - } - } - - public void onProvisionCompleted() { - if (state != STATE_OPENING && !isOpen()) { - // This event is stale. - return; - } - - if (openInternal(false)) { - doLicense(); - } + provisioningManager.onProvisionCompleted(); } private void doLicense() { @@ -405,7 +420,7 @@ import java.util.concurrent.atomic.AtomicBoolean; private void onKeysError(Exception e) { if (e instanceof NotProvisionedException) { - postProvisionRequest(); + provisioningManager.provisionRequired(this); } else { onError(e); } @@ -447,7 +462,7 @@ import java.util.concurrent.atomic.AtomicBoolean; break; case ExoMediaDrm.EVENT_PROVISION_REQUIRED: state = STATE_OPENED; - postProvisionRequest(); + provisioningManager.provisionRequired(this); break; default: break; diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java index cff9f9da0f..a0d5a932f2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java @@ -24,6 +24,7 @@ import android.support.annotation.IntDef; import android.support.annotation.NonNull; import android.text.TextUtils; import com.google.android.exoplayer2.C; +import com.google.android.exoplayer2.drm.DefaultDrmSession.ProvisioningManager; import com.google.android.exoplayer2.drm.DrmInitData.SchemeData; import com.google.android.exoplayer2.drm.ExoMediaDrm.OnEventListener; import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil; @@ -36,14 +37,13 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; /** * A {@link DrmSessionManager} that supports playbacks using {@link ExoMediaDrm}. */ @TargetApi(18) public class DefaultDrmSessionManager implements DrmSessionManager, - DefaultDrmSession.EventListener { + ProvisioningManager { /** * Listener of {@link DefaultDrmSessionManager} events. @@ -107,7 +107,7 @@ public class DefaultDrmSessionManager implements DrmSe private final boolean multiSession; private final List> sessions; - private final AtomicBoolean provisioningInProgress; + private final List> provisioningSessions; private Looper playbackLooper; private int mode; @@ -223,7 +223,7 @@ public class DefaultDrmSessionManager implements DrmSe this.multiSession = multiSession; mode = MODE_PLAYBACK; sessions = new ArrayList<>(); - provisioningInProgress = new AtomicBoolean(false); + provisioningSessions = new ArrayList<>(); if (multiSession) { mediaDrm.setPropertyString("sessionSharing", "enable"); } @@ -363,17 +363,21 @@ public class DefaultDrmSessionManager implements DrmSe } } - for (DefaultDrmSession s : sessions) { - if (!multiSession || s.canReuse(initData)) { - session = s; - break; + if (!multiSession) { + // Look for an existing session to use. + for (DefaultDrmSession existingSession : sessions) { + if (existingSession.hasInitData(initData)) { + session = existingSession; + break; + } } } if (session == null) { - session = new DefaultDrmSession(uuid, mediaDrm, initData, mimeType, mode, + // Create a new session. + session = new DefaultDrmSession<>(uuid, mediaDrm, this, initData, mimeType, mode, offlineLicenseKeySetId, optionalKeyRequestParameters, callback, playbackLooper, - eventHandler, eventListener, provisioningInProgress, this); + eventHandler, eventListener); sessions.add(session); } session.acquire(); @@ -385,16 +389,44 @@ public class DefaultDrmSessionManager implements DrmSe DefaultDrmSession drmSession = (DefaultDrmSession) session; if (drmSession.release()) { sessions.remove(drmSession); + if (provisioningSessions.size() > 1 && provisioningSessions.get(0) == drmSession) { + // Other sessions were waiting for the released session to complete a provision operation. + // We need to have one of those sessions perform the provision operation instead. + provisioningSessions.get(1).provision(); + } + provisioningSessions.remove(drmSession); + } + } + + // ProvisioningManager implementation. + + @Override + public void provisionRequired(DefaultDrmSession session) { + provisioningSessions.add(session); + if (provisioningSessions.size() == 1) { + // This is the first session requesting provisioning, so have it perform the operation. + session.provision(); } } @Override public void onProvisionCompleted() { - for (DefaultDrmSession session : sessions) { + for (DefaultDrmSession session : provisioningSessions) { session.onProvisionCompleted(); } + provisioningSessions.clear(); } + @Override + public void onProvisionError(Exception error) { + for (DefaultDrmSession session : provisioningSessions) { + session.onProvisionError(error); + } + provisioningSessions.clear(); + } + + // Internal methods. + /** * Extracts {@link SchemeData} suitable for the given DRM scheme {@link UUID}. *