mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
Allow repeated DRM provisioning in DefaultDrmSessionManager
Also change to explicitly track the provisioning session, which makes the code easier to reason about than always using the zero'th element of the list. PiperOrigin-RevId: 380181453
This commit is contained in:
parent
775507088c
commit
607fa8bf74
5 changed files with 129 additions and 34 deletions
|
|
@ -67,6 +67,8 @@
|
|||
* Support changing ad break positions in the player logic
|
||||
([#5067](https://github.com/google/ExoPlayer/issues/5067).
|
||||
* Support resuming content with an offset after an ad group.
|
||||
* DRM:
|
||||
* Allow repeated provisioning in `DefaultDrmSession(Manager)`.
|
||||
* PlayerNotificationManager:
|
||||
* Add `PendingIntent.FLAG_IMMUTABLE` flag to BroadcastReceiver to support
|
||||
Android 12.
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||
}
|
||||
|
||||
public void onProvisionCompleted() {
|
||||
if (openInternal(false)) {
|
||||
if (openInternal()) {
|
||||
doLicense(true);
|
||||
}
|
||||
}
|
||||
|
|
@ -290,7 +290,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||
requestHandlerThread = new HandlerThread("ExoPlayer:DrmRequestHandler");
|
||||
requestHandlerThread.start();
|
||||
requestHandler = new RequestHandler(requestHandlerThread.getLooper());
|
||||
if (openInternal(true)) {
|
||||
if (openInternal()) {
|
||||
doLicense(true);
|
||||
}
|
||||
} else if (eventDispatcher != null
|
||||
|
|
@ -338,12 +338,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||
/**
|
||||
* Try to open a session, do provisioning if necessary.
|
||||
*
|
||||
* @param allowProvisioning if provisioning is allowed, set this to false when calling from
|
||||
* processing provision response.
|
||||
* @return true on success, false otherwise.
|
||||
*/
|
||||
@EnsuresNonNullIf(result = true, expression = "sessionId")
|
||||
private boolean openInternal(boolean allowProvisioning) {
|
||||
private boolean openInternal() {
|
||||
if (isOpen()) {
|
||||
// Already opened
|
||||
return true;
|
||||
|
|
@ -359,11 +357,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||
Assertions.checkNotNull(sessionId);
|
||||
return true;
|
||||
} catch (NotProvisionedException e) {
|
||||
if (allowProvisioning) {
|
||||
provisioningManager.provisionRequired(this);
|
||||
} else {
|
||||
onError(e);
|
||||
}
|
||||
provisioningManager.provisionRequired(this);
|
||||
} catch (Exception e) {
|
||||
onError(e);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import java.lang.annotation.Retention;
|
|||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
|
@ -289,7 +290,6 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
|||
private final long sessionKeepaliveMs;
|
||||
|
||||
private final List<DefaultDrmSession> sessions;
|
||||
private final List<DefaultDrmSession> provisioningSessions;
|
||||
private final Set<PreacquiredSessionReference> preacquiredSessionReferences;
|
||||
private final Set<DefaultDrmSession> keepaliveSessions;
|
||||
|
||||
|
|
@ -411,7 +411,6 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
|||
referenceCountListener = new ReferenceCountListenerImpl();
|
||||
mode = MODE_PLAYBACK;
|
||||
sessions = new ArrayList<>();
|
||||
provisioningSessions = new ArrayList<>();
|
||||
preacquiredSessionReferences = Sets.newIdentityHashSet();
|
||||
keepaliveSessions = Sets.newIdentityHashSet();
|
||||
this.sessionKeepaliveMs = sessionKeepaliveMs;
|
||||
|
|
@ -842,33 +841,60 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
|||
}
|
||||
|
||||
private class ProvisioningManagerImpl implements DefaultDrmSession.ProvisioningManager {
|
||||
|
||||
private final Set<DefaultDrmSession> sessionsAwaitingProvisioning;
|
||||
@Nullable private DefaultDrmSession provisioningSession;
|
||||
|
||||
public ProvisioningManagerImpl() {
|
||||
sessionsAwaitingProvisioning = new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void provisionRequired(DefaultDrmSession session) {
|
||||
if (provisioningSessions.contains(session)) {
|
||||
// The session has already requested provisioning.
|
||||
sessionsAwaitingProvisioning.add(session);
|
||||
if (provisioningSession != null) {
|
||||
// Provisioning is already in-flight.
|
||||
return;
|
||||
}
|
||||
provisioningSessions.add(session);
|
||||
if (provisioningSessions.size() == 1) {
|
||||
// This is the first session requesting provisioning, so have it perform the operation.
|
||||
session.provision();
|
||||
}
|
||||
provisioningSession = session;
|
||||
session.provision();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvisionCompleted() {
|
||||
for (DefaultDrmSession session : provisioningSessions) {
|
||||
provisioningSession = null;
|
||||
ImmutableList<DefaultDrmSession> sessionsToNotify =
|
||||
ImmutableList.copyOf(sessionsAwaitingProvisioning);
|
||||
// Clear the list before calling onProvisionComplete in case provisioning is re-requested.
|
||||
sessionsAwaitingProvisioning.clear();
|
||||
for (DefaultDrmSession session : sessionsToNotify) {
|
||||
session.onProvisionCompleted();
|
||||
}
|
||||
provisioningSessions.clear();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProvisionError(Exception error) {
|
||||
for (DefaultDrmSession session : provisioningSessions) {
|
||||
provisioningSession = null;
|
||||
ImmutableList<DefaultDrmSession> sessionsToNotify =
|
||||
ImmutableList.copyOf(sessionsAwaitingProvisioning);
|
||||
// Clear the list before calling onProvisionError in case provisioning is re-requested.
|
||||
sessionsAwaitingProvisioning.clear();
|
||||
for (DefaultDrmSession session : sessionsToNotify) {
|
||||
session.onProvisionError(error);
|
||||
}
|
||||
provisioningSessions.clear();
|
||||
}
|
||||
|
||||
public void onSessionFullyReleased(DefaultDrmSession session) {
|
||||
sessionsAwaitingProvisioning.remove(session);
|
||||
if (provisioningSession == session) {
|
||||
provisioningSession = null;
|
||||
if (!sessionsAwaitingProvisioning.isEmpty()) {
|
||||
// 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.
|
||||
provisioningSession = sessionsAwaitingProvisioning.iterator().next();
|
||||
provisioningSession.provision();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -902,12 +928,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
|||
if (noMultiSessionDrmSession == session) {
|
||||
noMultiSessionDrmSession = null;
|
||||
}
|
||||
if (provisioningSessions.size() > 1 && provisioningSessions.get(0) == session) {
|
||||
// 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(session);
|
||||
provisioningManagerImpl.onSessionFullyReleased(session);
|
||||
if (sessionKeepaliveMs != C.TIME_UNSET) {
|
||||
checkNotNull(playbackHandler).removeCallbacksAndMessages(session);
|
||||
keepaliveSessions.remove(session);
|
||||
|
|
|
|||
|
|
@ -563,6 +563,69 @@ public class DefaultDrmSessionManagerTest {
|
|||
.containsExactly(FakeExoMediaDrm.KEY_STATUS_KEY, FakeExoMediaDrm.KEY_STATUS_AVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deviceNotProvisioned_doubleProvisioningHandledAndOpenSessionRetried() {
|
||||
FakeExoMediaDrm.LicenseServer licenseServer =
|
||||
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
|
||||
|
||||
DefaultDrmSessionManager drmSessionManager =
|
||||
new DefaultDrmSessionManager.Builder()
|
||||
.setUuidAndExoMediaDrmProvider(
|
||||
DRM_SCHEME_UUID,
|
||||
uuid -> new FakeExoMediaDrm.Builder().setProvisionsRequired(2).build())
|
||||
.build(/* mediaDrmCallback= */ licenseServer);
|
||||
drmSessionManager.prepare();
|
||||
DrmSession drmSession =
|
||||
checkNotNull(
|
||||
drmSessionManager.acquireSession(
|
||||
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
|
||||
/* eventDispatcher= */ null,
|
||||
FORMAT_WITH_DRM_INIT_DATA));
|
||||
// Confirm the device isn't provisioned (otherwise state would be OPENED)
|
||||
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENING);
|
||||
waitForOpenedWithKeys(drmSession);
|
||||
|
||||
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||
assertThat(drmSession.queryKeyStatus())
|
||||
.containsExactly(FakeExoMediaDrm.KEY_STATUS_KEY, FakeExoMediaDrm.KEY_STATUS_AVAILABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void provisioningUndoneWhileManagerIsActive_deviceReprovisioned() {
|
||||
FakeExoMediaDrm.LicenseServer licenseServer =
|
||||
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
|
||||
|
||||
FakeExoMediaDrm mediaDrm = new FakeExoMediaDrm.Builder().setProvisionsRequired(2).build();
|
||||
DefaultDrmSessionManager drmSessionManager =
|
||||
new DefaultDrmSessionManager.Builder()
|
||||
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, new AppManagedProvider(mediaDrm))
|
||||
.setSessionKeepaliveMs(C.TIME_UNSET)
|
||||
.build(/* mediaDrmCallback= */ licenseServer);
|
||||
drmSessionManager.prepare();
|
||||
DrmSession drmSession =
|
||||
checkNotNull(
|
||||
drmSessionManager.acquireSession(
|
||||
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
|
||||
/* eventDispatcher= */ null,
|
||||
FORMAT_WITH_DRM_INIT_DATA));
|
||||
// Confirm the device isn't provisioned (otherwise state would be OPENED)
|
||||
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENING);
|
||||
waitForOpenedWithKeys(drmSession);
|
||||
drmSession.release(/* eventDispatcher= */ null);
|
||||
|
||||
mediaDrm.resetProvisioning();
|
||||
|
||||
drmSession =
|
||||
checkNotNull(
|
||||
drmSessionManager.acquireSession(
|
||||
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
|
||||
/* eventDispatcher= */ null,
|
||||
FORMAT_WITH_DRM_INIT_DATA));
|
||||
// Confirm the device isn't provisioned (otherwise state would be OPENED)
|
||||
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENING);
|
||||
waitForOpenedWithKeys(drmSession);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void managerNotPrepared_acquireSessionAndPreacquireSessionFail() throws Exception {
|
||||
FakeExoMediaDrm.LicenseServer licenseServer =
|
||||
|
|
|
|||
|
|
@ -78,8 +78,8 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
|
|||
* to be provisioned.
|
||||
*
|
||||
* <p>An unprovisioned {@link FakeExoMediaDrm} will throw {@link NotProvisionedException} from
|
||||
* {@link FakeExoMediaDrm#openSession()} until enough valid provisioning responses are passed to
|
||||
* {@link FakeExoMediaDrm#provideProvisionResponse(byte[])}.
|
||||
* methods that declare it until enough valid provisioning responses are passed to {@link
|
||||
* FakeExoMediaDrm#provideProvisionResponse(byte[])}.
|
||||
*
|
||||
* <p>Defaults to 0 (i.e. device is already provisioned).
|
||||
*/
|
||||
|
|
@ -182,9 +182,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
|
|||
@Override
|
||||
public byte[] openSession() throws MediaDrmException {
|
||||
Assertions.checkState(referenceCount > 0);
|
||||
if (provisionsReceived < provisionsRequired) {
|
||||
throw new NotProvisionedException("Not provisioned.");
|
||||
}
|
||||
assertProvisioned();
|
||||
if (openSessionIds.size() >= maxConcurrentSessions) {
|
||||
throw new ResourceBusyException("Too many sessions open. max=" + maxConcurrentSessions);
|
||||
}
|
||||
|
|
@ -218,6 +216,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
|
|||
throw new UnsupportedOperationException("Offline key requests are not supported.");
|
||||
}
|
||||
Assertions.checkArgument(keyType == KEY_TYPE_STREAMING, "Unrecognised keyType: " + keyType);
|
||||
assertProvisioned();
|
||||
Assertions.checkState(openSessionIds.contains(toByteList(scope)));
|
||||
Assertions.checkNotNull(schemeDatas);
|
||||
KeyRequestData requestData =
|
||||
|
|
@ -238,6 +237,7 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
|
|||
public byte[] provideKeyResponse(byte[] scope, byte[] response)
|
||||
throws NotProvisionedException, DeniedByServerException {
|
||||
Assertions.checkState(referenceCount > 0);
|
||||
assertProvisioned();
|
||||
List<Byte> responseAsList = Bytes.asList(response);
|
||||
if (responseAsList.equals(VALID_KEY_RESPONSE)) {
|
||||
sessionIdsWithValidKeys.add(Bytes.asList(scope));
|
||||
|
|
@ -365,6 +365,21 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets the provisioning state of this instance, so it requires {@link
|
||||
* Builder#setProvisionsRequired(int) provisionsRequired} (possibly zero) provision operations
|
||||
* before it's operational again.
|
||||
*/
|
||||
public void resetProvisioning() {
|
||||
provisionsReceived = 0;
|
||||
}
|
||||
|
||||
private void assertProvisioned() throws NotProvisionedException {
|
||||
if (provisionsReceived < provisionsRequired) {
|
||||
throw new NotProvisionedException("Not provisioned.");
|
||||
}
|
||||
}
|
||||
|
||||
private static ImmutableList<Byte> toByteList(byte[] byteArray) {
|
||||
return ImmutableList.copyOf(Bytes.asList(byteArray));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue