Provisioning: Fix some (admittedly quite theoretical) issues:

1. Only tell sessions that want provisioning when provisioning occurs.
2. Also propagate failure to provision to these sessions.
3. If a session responsible for provisioning is released, start
   provisioning using another session instead.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=168713918
This commit is contained in:
olly 2017-09-14 11:09:18 -07:00 committed by Oliver Woodman
parent a479afff5f
commit a3a2fb506c
2 changed files with 97 additions and 50 deletions

View File

@ -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<T extends ExoMediaCrypto> implements DrmSession<T> {
/**
* Listener of {@link DefaultDrmSession} events.
* Manages provisioning requests.
*/
public interface EventListener {
public interface ProvisioningManager<T extends ExoMediaCrypto> {
/**
* Called each time provision is completed.
* Called when a session requires provisioning. The manager <em>may</em> call
* {@link #provision()} to have this session perform the provisioning operation. The manager
* <em>will</em> call {@link DefaultDrmSession#onProvisionCompleted()} when provisioning has
* completed, or {@link DefaultDrmSession#onProvisionError} if provisioning fails.
*
* @param session The session.
*/
void provisionRequired(DefaultDrmSession<T> 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<T> mediaDrm;
private final ProvisioningManager<T> provisioningManager;
private final byte[] initData;
private final String mimeType;
private final @DefaultDrmSessionManager.Mode int mode;
private final HashMap<String, String> 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<T> mediaDrm, byte[] initData, String mimeType,
public DefaultDrmSession(UUID uuid, ExoMediaDrm<T> mediaDrm,
ProvisioningManager<T> provisioningManager, byte[] initData, String mimeType,
@DefaultDrmSessionManager.Mode int mode, byte[] offlineLicenseKeySetId,
HashMap<String, String> 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;

View File

@ -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<T extends ExoMediaCrypto> implements DrmSessionManager<T>,
DefaultDrmSession.EventListener {
ProvisioningManager<T> {
/**
* Listener of {@link DefaultDrmSessionManager} events.
@ -107,7 +107,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> implements DrmSe
private final boolean multiSession;
private final List<DefaultDrmSession<T>> sessions;
private final AtomicBoolean provisioningInProgress;
private final List<DefaultDrmSession<T>> provisioningSessions;
private Looper playbackLooper;
private int mode;
@ -223,7 +223,7 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> 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<T extends ExoMediaCrypto> implements DrmSe
}
}
for (DefaultDrmSession<T> s : sessions) {
if (!multiSession || s.canReuse(initData)) {
session = s;
if (!multiSession) {
// Look for an existing session to use.
for (DefaultDrmSession<T> existingSession : sessions) {
if (existingSession.hasInitData(initData)) {
session = existingSession;
break;
}
}
}
if (session == null) {
session = new DefaultDrmSession<T>(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<T extends ExoMediaCrypto> implements DrmSe
DefaultDrmSession<T> drmSession = (DefaultDrmSession<T>) 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<T> 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<T> session : sessions) {
for (DefaultDrmSession<T> session : provisioningSessions) {
session.onProvisionCompleted();
}
provisioningSessions.clear();
}
@Override
public void onProvisionError(Exception error) {
for (DefaultDrmSession<T> session : provisioningSessions) {
session.onProvisionError(error);
}
provisioningSessions.clear();
}
// Internal methods.
/**
* Extracts {@link SchemeData} suitable for the given DRM scheme {@link UUID}.
*