mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Keep DRM sessions alive for a while before fully releasing them
Issue: #7011 Issue: #6725 Issue: #7066 This also mitigates (but doesn't fix) Issue: #4133 because it prevents a second key load after a short clear section. PiperOrigin-RevId: 319184325
This commit is contained in:
parent
e4e743a35f
commit
316f8a88cd
5 changed files with 377 additions and 58 deletions
|
|
@ -144,6 +144,10 @@
|
||||||
`OfflineLicenseHelper`
|
`OfflineLicenseHelper`
|
||||||
([#7078](https://github.com/google/ExoPlayer/issues/7078)).
|
([#7078](https://github.com/google/ExoPlayer/issues/7078)).
|
||||||
* Remove generics from DRM components.
|
* Remove generics from DRM components.
|
||||||
|
* Keep DRM sessions alive for a short time before fully releasing them
|
||||||
|
([#7011](https://github.com/google/ExoPlayer/issues/7011),
|
||||||
|
[#6725](https://github.com/google/ExoPlayer/issues/6725),
|
||||||
|
[#7066](https://github.com/google/ExoPlayer/issues/7066)).
|
||||||
* Downloads and caching:
|
* Downloads and caching:
|
||||||
* Support passing an `Executor` to `DefaultDownloaderFactory` on which
|
* Support passing an `Executor` to `DefaultDownloaderFactory` on which
|
||||||
data downloads are performed.
|
data downloads are performed.
|
||||||
|
|
|
||||||
|
|
@ -85,15 +85,26 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
void onProvisionCompleted();
|
void onProvisionCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Callback to be notified when the session is released. */
|
/** Callback to be notified when the reference count of this session changes. */
|
||||||
public interface ReleaseCallback {
|
public interface ReferenceCountListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called immediately after releasing session resources.
|
* Called when the internal reference count of this session is incremented.
|
||||||
*
|
*
|
||||||
* @param session The session.
|
* @param session This session.
|
||||||
|
* @param newReferenceCount The reference count after being incremented.
|
||||||
*/
|
*/
|
||||||
void onSessionReleased(DefaultDrmSession session);
|
void onReferenceCountIncremented(DefaultDrmSession session, int newReferenceCount);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the internal reference count of this session is decremented.
|
||||||
|
*
|
||||||
|
* <p>{@code newReferenceCount == 0} indicates this session is in {@link #STATE_RELEASED}.
|
||||||
|
*
|
||||||
|
* @param session This session.
|
||||||
|
* @param newReferenceCount The reference count after being decremented.
|
||||||
|
*/
|
||||||
|
void onReferenceCountDecremented(DefaultDrmSession session, int newReferenceCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final String TAG = "DefaultDrmSession";
|
private static final String TAG = "DefaultDrmSession";
|
||||||
|
|
@ -107,7 +118,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
|
||||||
private final ExoMediaDrm mediaDrm;
|
private final ExoMediaDrm mediaDrm;
|
||||||
private final ProvisioningManager provisioningManager;
|
private final ProvisioningManager provisioningManager;
|
||||||
private final ReleaseCallback releaseCallback;
|
private final ReferenceCountListener referenceCountListener;
|
||||||
private final @DefaultDrmSessionManager.Mode int mode;
|
private final @DefaultDrmSessionManager.Mode int mode;
|
||||||
private final boolean playClearSamplesWithoutKeys;
|
private final boolean playClearSamplesWithoutKeys;
|
||||||
private final boolean isPlaceholderSession;
|
private final boolean isPlaceholderSession;
|
||||||
|
|
@ -137,7 +148,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
* @param uuid The UUID of the drm scheme.
|
* @param uuid The UUID of the drm scheme.
|
||||||
* @param mediaDrm The media DRM.
|
* @param mediaDrm The media DRM.
|
||||||
* @param provisioningManager The manager for provisioning.
|
* @param provisioningManager The manager for provisioning.
|
||||||
* @param releaseCallback The {@link ReleaseCallback}.
|
* @param referenceCountListener The {@link ReferenceCountListener}.
|
||||||
* @param schemeDatas DRM scheme datas for this session, or null if an {@code
|
* @param schemeDatas DRM scheme datas for this session, or null if an {@code
|
||||||
* offlineLicenseKeySetId} is provided or if {@code isPlaceholderSession} is true.
|
* offlineLicenseKeySetId} is provided or if {@code isPlaceholderSession} is true.
|
||||||
* @param mode The DRM mode. Ignored if {@code isPlaceholderSession} is true.
|
* @param mode The DRM mode. Ignored if {@code isPlaceholderSession} is true.
|
||||||
|
|
@ -154,7 +165,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
UUID uuid,
|
UUID uuid,
|
||||||
ExoMediaDrm mediaDrm,
|
ExoMediaDrm mediaDrm,
|
||||||
ProvisioningManager provisioningManager,
|
ProvisioningManager provisioningManager,
|
||||||
ReleaseCallback releaseCallback,
|
ReferenceCountListener referenceCountListener,
|
||||||
@Nullable List<SchemeData> schemeDatas,
|
@Nullable List<SchemeData> schemeDatas,
|
||||||
@DefaultDrmSessionManager.Mode int mode,
|
@DefaultDrmSessionManager.Mode int mode,
|
||||||
boolean playClearSamplesWithoutKeys,
|
boolean playClearSamplesWithoutKeys,
|
||||||
|
|
@ -170,7 +181,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
}
|
}
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
this.provisioningManager = provisioningManager;
|
this.provisioningManager = provisioningManager;
|
||||||
this.releaseCallback = releaseCallback;
|
this.referenceCountListener = referenceCountListener;
|
||||||
this.mediaDrm = mediaDrm;
|
this.mediaDrm = mediaDrm;
|
||||||
this.mode = mode;
|
this.mode = mode;
|
||||||
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
|
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
|
||||||
|
|
@ -280,6 +291,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
eventDispatcher.dispatch(
|
eventDispatcher.dispatch(
|
||||||
DrmSessionEventListener::onDrmSessionAcquired, DrmSessionEventListener.class);
|
DrmSessionEventListener::onDrmSessionAcquired, DrmSessionEventListener.class);
|
||||||
}
|
}
|
||||||
|
referenceCountListener.onReferenceCountIncremented(this, referenceCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -300,7 +312,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
mediaDrm.closeSession(sessionId);
|
mediaDrm.closeSession(sessionId);
|
||||||
sessionId = null;
|
sessionId = null;
|
||||||
}
|
}
|
||||||
releaseCallback.onSessionReleased(this);
|
|
||||||
dispatchEvent(DrmSessionEventListener::onDrmSessionReleased);
|
dispatchEvent(DrmSessionEventListener::onDrmSessionReleased);
|
||||||
}
|
}
|
||||||
if (eventDispatcher != null) {
|
if (eventDispatcher != null) {
|
||||||
|
|
@ -312,6 +323,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
}
|
}
|
||||||
eventDispatchers.remove(eventDispatcher);
|
eventDispatchers.remove(eventDispatcher);
|
||||||
}
|
}
|
||||||
|
referenceCountListener.onReferenceCountDecremented(this, referenceCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,11 @@
|
||||||
package com.google.android.exoplayer2.drm;
|
package com.google.android.exoplayer2.drm;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
|
import android.media.ResourceBusyException;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
|
import android.os.SystemClock;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
|
@ -32,15 +34,18 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
|
import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/** A {@link DrmSessionManager} that supports playbacks using {@link ExoMediaDrm}. */
|
/** A {@link DrmSessionManager} that supports playbacks using {@link ExoMediaDrm}. */
|
||||||
@RequiresApi(18)
|
@RequiresApi(18)
|
||||||
|
|
@ -60,6 +65,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
private int[] useDrmSessionsForClearContentTrackTypes;
|
private int[] useDrmSessionsForClearContentTrackTypes;
|
||||||
private boolean playClearSamplesWithoutKeys;
|
private boolean playClearSamplesWithoutKeys;
|
||||||
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||||
|
private long sessionKeepaliveMs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a builder with default values. The default values are:
|
* Creates a builder with default values. The default values are:
|
||||||
|
|
@ -82,6 +88,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
exoMediaDrmProvider = FrameworkMediaDrm.DEFAULT_PROVIDER;
|
exoMediaDrmProvider = FrameworkMediaDrm.DEFAULT_PROVIDER;
|
||||||
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
||||||
useDrmSessionsForClearContentTrackTypes = new int[0];
|
useDrmSessionsForClearContentTrackTypes = new int[0];
|
||||||
|
sessionKeepaliveMs = DEFAULT_SESSION_KEEPALIVE_MS;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -180,6 +187,27 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the time to keep {@link DrmSession DrmSessions} alive when they're not in use.
|
||||||
|
*
|
||||||
|
* <p>It can be useful to keep sessions alive during playback of short clear sections of media
|
||||||
|
* (e.g. ad breaks) to avoid opening new DRM sessions (and re-requesting keys) at the transition
|
||||||
|
* back into secure content. This assumes the secure sections before and after the clear section
|
||||||
|
* are encrypted with the same keys.
|
||||||
|
*
|
||||||
|
* <p>Defaults to {@link #DEFAULT_SESSION_KEEPALIVE_MS}. Pass {@link C#TIME_UNSET} to disable
|
||||||
|
* keep-alive.
|
||||||
|
*
|
||||||
|
* @param sessionKeepaliveMs The time to keep {@link DrmSession}s alive before fully releasing,
|
||||||
|
* in milliseconds. Must be > 0 or {@link C#TIME_UNSET} to disable keep-alive.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
public Builder setSessionKeepaliveMs(long sessionKeepaliveMs) {
|
||||||
|
Assertions.checkArgument(sessionKeepaliveMs > 0 || sessionKeepaliveMs == C.TIME_UNSET);
|
||||||
|
this.sessionKeepaliveMs = sessionKeepaliveMs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/** Builds a {@link DefaultDrmSessionManager} instance. */
|
/** Builds a {@link DefaultDrmSessionManager} instance. */
|
||||||
public DefaultDrmSessionManager build(MediaDrmCallback mediaDrmCallback) {
|
public DefaultDrmSessionManager build(MediaDrmCallback mediaDrmCallback) {
|
||||||
return new DefaultDrmSessionManager(
|
return new DefaultDrmSessionManager(
|
||||||
|
|
@ -190,7 +218,8 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
multiSession,
|
multiSession,
|
||||||
useDrmSessionsForClearContentTrackTypes,
|
useDrmSessionsForClearContentTrackTypes,
|
||||||
playClearSamplesWithoutKeys,
|
playClearSamplesWithoutKeys,
|
||||||
loadErrorHandlingPolicy);
|
loadErrorHandlingPolicy,
|
||||||
|
sessionKeepaliveMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,6 +261,8 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
public static final int MODE_RELEASE = 3;
|
public static final int MODE_RELEASE = 3;
|
||||||
/** Number of times to retry for initial provisioning and key request for reporting error. */
|
/** Number of times to retry for initial provisioning and key request for reporting error. */
|
||||||
public static final int INITIAL_DRM_REQUEST_RETRY_COUNT = 3;
|
public static final int INITIAL_DRM_REQUEST_RETRY_COUNT = 3;
|
||||||
|
/** Default value for {@link Builder#setSessionKeepaliveMs(long)}. */
|
||||||
|
public static final long DEFAULT_SESSION_KEEPALIVE_MS = 5 * 60 * C.MILLIS_PER_SECOND;
|
||||||
|
|
||||||
private static final String TAG = "DefaultDrmSessionMgr";
|
private static final String TAG = "DefaultDrmSessionMgr";
|
||||||
|
|
||||||
|
|
@ -244,15 +275,19 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
private final boolean playClearSamplesWithoutKeys;
|
private final boolean playClearSamplesWithoutKeys;
|
||||||
private final ProvisioningManagerImpl provisioningManagerImpl;
|
private final ProvisioningManagerImpl provisioningManagerImpl;
|
||||||
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
private final LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||||
|
private final ReferenceCountListenerImpl referenceCountListener;
|
||||||
|
private final long sessionKeepaliveMs;
|
||||||
|
|
||||||
private final List<DefaultDrmSession> sessions;
|
private final List<DefaultDrmSession> sessions;
|
||||||
private final List<DefaultDrmSession> provisioningSessions;
|
private final List<DefaultDrmSession> provisioningSessions;
|
||||||
|
private final Set<DefaultDrmSession> keepaliveSessions;
|
||||||
|
|
||||||
private int prepareCallsCount;
|
private int prepareCallsCount;
|
||||||
@Nullable private ExoMediaDrm exoMediaDrm;
|
@Nullable private ExoMediaDrm exoMediaDrm;
|
||||||
@Nullable private DefaultDrmSession placeholderDrmSession;
|
@Nullable private DefaultDrmSession placeholderDrmSession;
|
||||||
@Nullable private DefaultDrmSession noMultiSessionDrmSession;
|
@Nullable private DefaultDrmSession noMultiSessionDrmSession;
|
||||||
@Nullable private Looper playbackLooper;
|
@Nullable private Looper playbackLooper;
|
||||||
|
private @MonotonicNonNull Handler sessionReleasingHandler;
|
||||||
private int mode;
|
private int mode;
|
||||||
@Nullable private byte[] offlineLicenseKeySetId;
|
@Nullable private byte[] offlineLicenseKeySetId;
|
||||||
|
|
||||||
|
|
@ -336,7 +371,8 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
multiSession,
|
multiSession,
|
||||||
/* useDrmSessionsForClearContentTrackTypes= */ new int[0],
|
/* useDrmSessionsForClearContentTrackTypes= */ new int[0],
|
||||||
/* playClearSamplesWithoutKeys= */ false,
|
/* playClearSamplesWithoutKeys= */ false,
|
||||||
new DefaultLoadErrorHandlingPolicy(initialDrmRequestRetryCount));
|
new DefaultLoadErrorHandlingPolicy(initialDrmRequestRetryCount),
|
||||||
|
DEFAULT_SESSION_KEEPALIVE_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private DefaultDrmSessionManager(
|
private DefaultDrmSessionManager(
|
||||||
|
|
@ -347,7 +383,8 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
boolean multiSession,
|
boolean multiSession,
|
||||||
int[] useDrmSessionsForClearContentTrackTypes,
|
int[] useDrmSessionsForClearContentTrackTypes,
|
||||||
boolean playClearSamplesWithoutKeys,
|
boolean playClearSamplesWithoutKeys,
|
||||||
LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
|
LoadErrorHandlingPolicy loadErrorHandlingPolicy,
|
||||||
|
long sessionKeepaliveMs) {
|
||||||
Assertions.checkNotNull(uuid);
|
Assertions.checkNotNull(uuid);
|
||||||
Assertions.checkArgument(!C.COMMON_PSSH_UUID.equals(uuid), "Use C.CLEARKEY_UUID instead");
|
Assertions.checkArgument(!C.COMMON_PSSH_UUID.equals(uuid), "Use C.CLEARKEY_UUID instead");
|
||||||
this.uuid = uuid;
|
this.uuid = uuid;
|
||||||
|
|
@ -359,9 +396,12 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
|
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
|
||||||
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
||||||
provisioningManagerImpl = new ProvisioningManagerImpl();
|
provisioningManagerImpl = new ProvisioningManagerImpl();
|
||||||
|
referenceCountListener = new ReferenceCountListenerImpl();
|
||||||
mode = MODE_PLAYBACK;
|
mode = MODE_PLAYBACK;
|
||||||
sessions = new ArrayList<>();
|
sessions = new ArrayList<>();
|
||||||
provisioningSessions = new ArrayList<>();
|
provisioningSessions = new ArrayList<>();
|
||||||
|
keepaliveSessions = Sets.newIdentityHashSet();
|
||||||
|
this.sessionKeepaliveMs = sessionKeepaliveMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -411,6 +451,13 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
@Override
|
@Override
|
||||||
public final void release() {
|
public final void release() {
|
||||||
if (--prepareCallsCount == 0) {
|
if (--prepareCallsCount == 0) {
|
||||||
|
// Make a local copy, because sessions are removed from this.sessions during release (via
|
||||||
|
// callback).
|
||||||
|
List<DefaultDrmSession> sessions = new ArrayList<>(this.sessions);
|
||||||
|
for (int i = 0; i < sessions.size(); i++) {
|
||||||
|
// Release all the keepalive acquisitions.
|
||||||
|
sessions.get(i).release(/* eventDispatcher= */ null);
|
||||||
|
}
|
||||||
Assertions.checkNotNull(exoMediaDrm).release();
|
Assertions.checkNotNull(exoMediaDrm).release();
|
||||||
exoMediaDrm = null;
|
exoMediaDrm = null;
|
||||||
}
|
}
|
||||||
|
|
@ -451,7 +498,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public DrmSession acquirePlaceholderSession(Looper playbackLooper, int trackType) {
|
public DrmSession acquirePlaceholderSession(Looper playbackLooper, int trackType) {
|
||||||
assertExpectedPlaybackLooper(playbackLooper);
|
initPlaybackLooper(playbackLooper);
|
||||||
ExoMediaDrm exoMediaDrm = Assertions.checkNotNull(this.exoMediaDrm);
|
ExoMediaDrm exoMediaDrm = Assertions.checkNotNull(this.exoMediaDrm);
|
||||||
boolean avoidPlaceholderDrmSessions =
|
boolean avoidPlaceholderDrmSessions =
|
||||||
FrameworkMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType())
|
FrameworkMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType())
|
||||||
|
|
@ -465,12 +512,15 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
maybeCreateMediaDrmHandler(playbackLooper);
|
maybeCreateMediaDrmHandler(playbackLooper);
|
||||||
if (placeholderDrmSession == null) {
|
if (placeholderDrmSession == null) {
|
||||||
DefaultDrmSession placeholderDrmSession =
|
DefaultDrmSession placeholderDrmSession =
|
||||||
createNewDefaultSession(
|
createAndAcquireSessionWithRetry(
|
||||||
/* schemeDatas= */ Collections.emptyList(), /* isPlaceholderSession= */ true);
|
/* schemeDatas= */ ImmutableList.of(),
|
||||||
|
/* isPlaceholderSession= */ true,
|
||||||
|
/* eventDispatcher= */ null);
|
||||||
sessions.add(placeholderDrmSession);
|
sessions.add(placeholderDrmSession);
|
||||||
this.placeholderDrmSession = placeholderDrmSession;
|
this.placeholderDrmSession = placeholderDrmSession;
|
||||||
}
|
} else {
|
||||||
placeholderDrmSession.acquire(/* eventDispatcher= */ null);
|
placeholderDrmSession.acquire(/* eventDispatcher= */ null);
|
||||||
|
}
|
||||||
return placeholderDrmSession;
|
return placeholderDrmSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -479,7 +529,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
Looper playbackLooper,
|
Looper playbackLooper,
|
||||||
@Nullable MediaSourceEventDispatcher eventDispatcher,
|
@Nullable MediaSourceEventDispatcher eventDispatcher,
|
||||||
DrmInitData drmInitData) {
|
DrmInitData drmInitData) {
|
||||||
assertExpectedPlaybackLooper(playbackLooper);
|
initPlaybackLooper(playbackLooper);
|
||||||
maybeCreateMediaDrmHandler(playbackLooper);
|
maybeCreateMediaDrmHandler(playbackLooper);
|
||||||
|
|
||||||
@Nullable List<SchemeData> schemeDatas = null;
|
@Nullable List<SchemeData> schemeDatas = null;
|
||||||
|
|
@ -513,13 +563,17 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
|
|
||||||
if (session == null) {
|
if (session == null) {
|
||||||
// Create a new session.
|
// Create a new session.
|
||||||
session = createNewDefaultSession(schemeDatas, /* isPlaceholderSession= */ false);
|
session =
|
||||||
|
createAndAcquireSessionWithRetry(
|
||||||
|
schemeDatas, /* isPlaceholderSession= */ false, eventDispatcher);
|
||||||
if (!multiSession) {
|
if (!multiSession) {
|
||||||
noMultiSessionDrmSession = session;
|
noMultiSessionDrmSession = session;
|
||||||
}
|
}
|
||||||
sessions.add(session);
|
sessions.add(session);
|
||||||
}
|
} else {
|
||||||
session.acquire(eventDispatcher);
|
session.acquire(eventDispatcher);
|
||||||
|
}
|
||||||
|
|
||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -533,9 +587,13 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
private void assertExpectedPlaybackLooper(Looper playbackLooper) {
|
private void initPlaybackLooper(Looper playbackLooper) {
|
||||||
Assertions.checkState(this.playbackLooper == null || this.playbackLooper == playbackLooper);
|
if (this.playbackLooper == null) {
|
||||||
this.playbackLooper = playbackLooper;
|
this.playbackLooper = playbackLooper;
|
||||||
|
this.sessionReleasingHandler = new Handler(playbackLooper);
|
||||||
|
} else {
|
||||||
|
Assertions.checkState(this.playbackLooper == playbackLooper);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeCreateMediaDrmHandler(Looper playbackLooper) {
|
private void maybeCreateMediaDrmHandler(Looper playbackLooper) {
|
||||||
|
|
@ -544,16 +602,58 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DefaultDrmSession createNewDefaultSession(
|
private DefaultDrmSession createAndAcquireSessionWithRetry(
|
||||||
@Nullable List<SchemeData> schemeDatas, boolean isPlaceholderSession) {
|
@Nullable List<SchemeData> schemeDatas,
|
||||||
|
boolean isPlaceholderSession,
|
||||||
|
@Nullable MediaSourceEventDispatcher eventDispatcher) {
|
||||||
|
DefaultDrmSession session =
|
||||||
|
createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher);
|
||||||
|
if (session.getState() == DrmSession.STATE_ERROR
|
||||||
|
&& (Util.SDK_INT < 19
|
||||||
|
|| Assertions.checkNotNull(session.getError()).getCause()
|
||||||
|
instanceof ResourceBusyException)) {
|
||||||
|
// We're short on DRM session resources, so eagerly release all our keepalive sessions.
|
||||||
|
// ResourceBusyException is only available at API 19, so on earlier versions we always
|
||||||
|
// eagerly release regardless of the underlying error.
|
||||||
|
if (!keepaliveSessions.isEmpty()) {
|
||||||
|
// Make a local copy, because sessions are removed from this.timingOutSessions during
|
||||||
|
// release (via callback).
|
||||||
|
ImmutableList<DefaultDrmSession> timingOutSessions =
|
||||||
|
ImmutableList.copyOf(this.keepaliveSessions);
|
||||||
|
for (DrmSession timingOutSession : timingOutSessions) {
|
||||||
|
timingOutSession.release(/* eventDispatcher= */ null);
|
||||||
|
}
|
||||||
|
// Undo the acquisitions from createAndAcquireSession().
|
||||||
|
session.release(eventDispatcher);
|
||||||
|
if (sessionKeepaliveMs != C.TIME_UNSET) {
|
||||||
|
session.release(/* eventDispatcher= */ null);
|
||||||
|
}
|
||||||
|
session = createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link DefaultDrmSession} and acquires it on behalf of the caller (passing in
|
||||||
|
* {@code eventDispatcher}).
|
||||||
|
*
|
||||||
|
* <p>If {@link #sessionKeepaliveMs} != {@link C#TIME_UNSET} then acquires it again to allow the
|
||||||
|
* manager to keep it alive (passing in {@code eventDispatcher=null}.
|
||||||
|
*/
|
||||||
|
private DefaultDrmSession createAndAcquireSession(
|
||||||
|
@Nullable List<SchemeData> schemeDatas,
|
||||||
|
boolean isPlaceholderSession,
|
||||||
|
@Nullable MediaSourceEventDispatcher eventDispatcher) {
|
||||||
Assertions.checkNotNull(exoMediaDrm);
|
Assertions.checkNotNull(exoMediaDrm);
|
||||||
// Placeholder sessions should always play clear samples without keys.
|
// Placeholder sessions should always play clear samples without keys.
|
||||||
boolean playClearSamplesWithoutKeys = this.playClearSamplesWithoutKeys | isPlaceholderSession;
|
boolean playClearSamplesWithoutKeys = this.playClearSamplesWithoutKeys | isPlaceholderSession;
|
||||||
return new DefaultDrmSession(
|
DefaultDrmSession session =
|
||||||
|
new DefaultDrmSession(
|
||||||
uuid,
|
uuid,
|
||||||
exoMediaDrm,
|
exoMediaDrm,
|
||||||
/* provisioningManager= */ provisioningManagerImpl,
|
/* provisioningManager= */ provisioningManagerImpl,
|
||||||
/* releaseCallback= */ this::onSessionReleased,
|
referenceCountListener,
|
||||||
schemeDatas,
|
schemeDatas,
|
||||||
mode,
|
mode,
|
||||||
playClearSamplesWithoutKeys,
|
playClearSamplesWithoutKeys,
|
||||||
|
|
@ -563,22 +663,16 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
callback,
|
callback,
|
||||||
Assertions.checkNotNull(playbackLooper),
|
Assertions.checkNotNull(playbackLooper),
|
||||||
loadErrorHandlingPolicy);
|
loadErrorHandlingPolicy);
|
||||||
|
// Acquire the session once on behalf of the caller to DrmSessionManager - this is the
|
||||||
|
// reference 'assigned' to the caller which they're responsible for releasing. Do this first,
|
||||||
|
// to ensure that eventDispatcher receives all events related to the initial
|
||||||
|
// acquisition/opening.
|
||||||
|
session.acquire(eventDispatcher);
|
||||||
|
if (sessionKeepaliveMs != C.TIME_UNSET) {
|
||||||
|
// Acquire the session once more so the Manager can keep it alive.
|
||||||
|
session.acquire(/* eventDispatcher= */ null);
|
||||||
}
|
}
|
||||||
|
return session;
|
||||||
private void onSessionReleased(DefaultDrmSession drmSession) {
|
|
||||||
sessions.remove(drmSession);
|
|
||||||
if (placeholderDrmSession == drmSession) {
|
|
||||||
placeholderDrmSession = null;
|
|
||||||
}
|
|
||||||
if (noMultiSessionDrmSession == drmSession) {
|
|
||||||
noMultiSessionDrmSession = null;
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -661,6 +755,52 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ReferenceCountListenerImpl implements DefaultDrmSession.ReferenceCountListener {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReferenceCountIncremented(DefaultDrmSession session, int newReferenceCount) {
|
||||||
|
if (sessionKeepaliveMs != C.TIME_UNSET) {
|
||||||
|
// The session has been acquired elsewhere so we want to cancel our timeout.
|
||||||
|
keepaliveSessions.remove(session);
|
||||||
|
Assertions.checkNotNull(sessionReleasingHandler).removeCallbacksAndMessages(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onReferenceCountDecremented(DefaultDrmSession session, int newReferenceCount) {
|
||||||
|
if (newReferenceCount == 1 && sessionKeepaliveMs != C.TIME_UNSET) {
|
||||||
|
// Only the internal keep-alive reference remains, so we can start the timeout.
|
||||||
|
keepaliveSessions.add(session);
|
||||||
|
Assertions.checkNotNull(sessionReleasingHandler)
|
||||||
|
.postAtTime(
|
||||||
|
() -> {
|
||||||
|
session.release(/* eventDispatcher= */ null);
|
||||||
|
},
|
||||||
|
session,
|
||||||
|
/* uptimeMillis= */ SystemClock.uptimeMillis() + sessionKeepaliveMs);
|
||||||
|
} else if (newReferenceCount == 0) {
|
||||||
|
// This session is fully released.
|
||||||
|
sessions.remove(session);
|
||||||
|
if (placeholderDrmSession == session) {
|
||||||
|
placeholderDrmSession = null;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
if (sessionKeepaliveMs != C.TIME_UNSET) {
|
||||||
|
Assertions.checkNotNull(sessionReleasingHandler).removeCallbacksAndMessages(session);
|
||||||
|
keepaliveSessions.remove(session);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class MediaDrmEventListener implements OnEventListener {
|
private class MediaDrmEventListener implements OnEventListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -16,9 +16,11 @@
|
||||||
package com.google.android.exoplayer2.drm;
|
package com.google.android.exoplayer2.drm;
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||||
|
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.testutil.FakeExoMediaDrm;
|
import com.google.android.exoplayer2.testutil.FakeExoMediaDrm;
|
||||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
@ -47,7 +49,7 @@ public class DefaultDrmSessionManagerTest {
|
||||||
private static final DrmInitData DRM_INIT_DATA = new DrmInitData(DRM_SCHEME_DATAS);
|
private static final DrmInitData DRM_INIT_DATA = new DrmInitData(DRM_SCHEME_DATAS);
|
||||||
|
|
||||||
@Test(timeout = 10_000)
|
@Test(timeout = 10_000)
|
||||||
public void acquireSessionTriggersKeyLoadAndSessionIsOpened() throws Exception {
|
public void acquireSession_triggersKeyLoadAndSessionIsOpened() throws Exception {
|
||||||
FakeExoMediaDrm.LicenseServer licenseServer =
|
FakeExoMediaDrm.LicenseServer licenseServer =
|
||||||
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
|
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
|
||||||
|
|
||||||
|
|
@ -68,6 +70,151 @@ public class DefaultDrmSessionManagerTest {
|
||||||
.containsExactly(FakeExoMediaDrm.KEY_STATUS_KEY, FakeExoMediaDrm.KEY_STATUS_AVAILABLE);
|
.containsExactly(FakeExoMediaDrm.KEY_STATUS_KEY, FakeExoMediaDrm.KEY_STATUS_AVAILABLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 10_000)
|
||||||
|
public void keepaliveEnabled_sessionsKeptForRequestedTime() throws Exception {
|
||||||
|
FakeExoMediaDrm.LicenseServer licenseServer =
|
||||||
|
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
|
||||||
|
DrmSessionManager drmSessionManager =
|
||||||
|
new DefaultDrmSessionManager.Builder()
|
||||||
|
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm())
|
||||||
|
.setSessionKeepaliveMs(10_000)
|
||||||
|
.build(/* mediaDrmCallback= */ licenseServer);
|
||||||
|
|
||||||
|
drmSessionManager.prepare();
|
||||||
|
DrmSession drmSession =
|
||||||
|
drmSessionManager.acquireSession(
|
||||||
|
/* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()),
|
||||||
|
/* eventDispatcher= */ null,
|
||||||
|
DRM_INIT_DATA);
|
||||||
|
waitForOpenedWithKeys(drmSession);
|
||||||
|
|
||||||
|
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||||
|
drmSession.release(/* eventDispatcher= */ null);
|
||||||
|
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||||
|
ShadowLooper.idleMainLooper(10, SECONDS);
|
||||||
|
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_RELEASED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 10_000)
|
||||||
|
public void keepaliveDisabled_sessionsReleasedImmediately() throws Exception {
|
||||||
|
FakeExoMediaDrm.LicenseServer licenseServer =
|
||||||
|
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
|
||||||
|
DrmSessionManager drmSessionManager =
|
||||||
|
new DefaultDrmSessionManager.Builder()
|
||||||
|
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm())
|
||||||
|
.setSessionKeepaliveMs(C.TIME_UNSET)
|
||||||
|
.build(/* mediaDrmCallback= */ licenseServer);
|
||||||
|
|
||||||
|
drmSessionManager.prepare();
|
||||||
|
DrmSession drmSession =
|
||||||
|
drmSessionManager.acquireSession(
|
||||||
|
/* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()),
|
||||||
|
/* eventDispatcher= */ null,
|
||||||
|
DRM_INIT_DATA);
|
||||||
|
waitForOpenedWithKeys(drmSession);
|
||||||
|
drmSession.release(/* eventDispatcher= */ null);
|
||||||
|
|
||||||
|
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_RELEASED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 10_000)
|
||||||
|
public void managerRelease_allKeepaliveSessionsImmediatelyReleased() throws Exception {
|
||||||
|
FakeExoMediaDrm.LicenseServer licenseServer =
|
||||||
|
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
|
||||||
|
DrmSessionManager drmSessionManager =
|
||||||
|
new DefaultDrmSessionManager.Builder()
|
||||||
|
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm())
|
||||||
|
.setSessionKeepaliveMs(10_000)
|
||||||
|
.build(/* mediaDrmCallback= */ licenseServer);
|
||||||
|
|
||||||
|
drmSessionManager.prepare();
|
||||||
|
DrmSession drmSession =
|
||||||
|
drmSessionManager.acquireSession(
|
||||||
|
/* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()),
|
||||||
|
/* eventDispatcher= */ null,
|
||||||
|
DRM_INIT_DATA);
|
||||||
|
waitForOpenedWithKeys(drmSession);
|
||||||
|
drmSession.release(/* eventDispatcher= */ null);
|
||||||
|
|
||||||
|
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||||
|
drmSessionManager.release();
|
||||||
|
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_RELEASED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 10_000)
|
||||||
|
public void maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased() throws Exception {
|
||||||
|
ImmutableList<DrmInitData.SchemeData> secondSchemeDatas =
|
||||||
|
ImmutableList.of(DRM_SCHEME_DATAS.get(0).copyWithData(TestUtil.createByteArray(4, 5, 6)));
|
||||||
|
FakeExoMediaDrm.LicenseServer licenseServer =
|
||||||
|
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS, secondSchemeDatas);
|
||||||
|
DrmInitData secondInitData = new DrmInitData(secondSchemeDatas);
|
||||||
|
DrmSessionManager drmSessionManager =
|
||||||
|
new DefaultDrmSessionManager.Builder()
|
||||||
|
.setUuidAndExoMediaDrmProvider(
|
||||||
|
DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm(/* maxConcurrentSessions= */ 1))
|
||||||
|
.setSessionKeepaliveMs(10_000)
|
||||||
|
.setMultiSession(true)
|
||||||
|
.build(/* mediaDrmCallback= */ licenseServer);
|
||||||
|
|
||||||
|
drmSessionManager.prepare();
|
||||||
|
DrmSession firstDrmSession =
|
||||||
|
drmSessionManager.acquireSession(
|
||||||
|
/* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()),
|
||||||
|
/* eventDispatcher= */ null,
|
||||||
|
DRM_INIT_DATA);
|
||||||
|
waitForOpenedWithKeys(firstDrmSession);
|
||||||
|
firstDrmSession.release(/* eventDispatcher= */ null);
|
||||||
|
|
||||||
|
// All external references to firstDrmSession have been released, it's being kept alive by
|
||||||
|
// drmSessionManager's internal reference.
|
||||||
|
assertThat(firstDrmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||||
|
DrmSession secondDrmSession =
|
||||||
|
drmSessionManager.acquireSession(
|
||||||
|
/* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()),
|
||||||
|
/* eventDispatcher= */ null,
|
||||||
|
secondInitData);
|
||||||
|
// The drmSessionManager had to release firstDrmSession in order to acquire secondDrmSession.
|
||||||
|
assertThat(firstDrmSession.getState()).isEqualTo(DrmSession.STATE_RELEASED);
|
||||||
|
|
||||||
|
waitForOpenedWithKeys(secondDrmSession);
|
||||||
|
assertThat(secondDrmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(timeout = 10_000)
|
||||||
|
public void sessionReacquired_keepaliveTimeOutCancelled() throws Exception {
|
||||||
|
FakeExoMediaDrm.LicenseServer licenseServer =
|
||||||
|
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
|
||||||
|
DrmSessionManager drmSessionManager =
|
||||||
|
new DefaultDrmSessionManager.Builder()
|
||||||
|
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm())
|
||||||
|
.setSessionKeepaliveMs(10_000)
|
||||||
|
.build(/* mediaDrmCallback= */ licenseServer);
|
||||||
|
|
||||||
|
drmSessionManager.prepare();
|
||||||
|
DrmSession firstDrmSession =
|
||||||
|
drmSessionManager.acquireSession(
|
||||||
|
/* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()),
|
||||||
|
/* eventDispatcher= */ null,
|
||||||
|
DRM_INIT_DATA);
|
||||||
|
waitForOpenedWithKeys(firstDrmSession);
|
||||||
|
firstDrmSession.release(/* eventDispatcher= */ null);
|
||||||
|
|
||||||
|
ShadowLooper.idleMainLooper(5, SECONDS);
|
||||||
|
|
||||||
|
// Acquire a session for the same init data 5s in to the 10s timeout (so expect the same
|
||||||
|
// instance).
|
||||||
|
DrmSession secondDrmSession =
|
||||||
|
drmSessionManager.acquireSession(
|
||||||
|
/* playbackLooper= */ Assertions.checkNotNull(Looper.myLooper()),
|
||||||
|
/* eventDispatcher= */ null,
|
||||||
|
DRM_INIT_DATA);
|
||||||
|
assertThat(secondDrmSession).isSameInstanceAs(firstDrmSession);
|
||||||
|
|
||||||
|
// Let the timeout definitely expire, and check the session didn't get released.
|
||||||
|
ShadowLooper.idleMainLooper(10, SECONDS);
|
||||||
|
assertThat(secondDrmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||||
|
}
|
||||||
|
|
||||||
private static void waitForOpenedWithKeys(DrmSession drmSession) {
|
private static void waitForOpenedWithKeys(DrmSession drmSession) {
|
||||||
// Check the error first, so we get a meaningful failure if there's been an error.
|
// Check the error first, so we get a meaningful failure if there's been an error.
|
||||||
assertThat(drmSession.getError()).isNull();
|
assertThat(drmSession.getError()).isNull();
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,7 @@ import android.media.DeniedByServerException;
|
||||||
import android.media.MediaCryptoException;
|
import android.media.MediaCryptoException;
|
||||||
import android.media.MediaDrmException;
|
import android.media.MediaDrmException;
|
||||||
import android.media.NotProvisionedException;
|
import android.media.NotProvisionedException;
|
||||||
|
import android.media.ResourceBusyException;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import android.os.PersistableBundle;
|
import android.os.PersistableBundle;
|
||||||
|
|
@ -57,7 +58,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||||
// TODO: Consider replacing this with a Robolectric ShadowMediaDrm so we can use a real
|
// TODO: Consider replacing this with a Robolectric ShadowMediaDrm so we can use a real
|
||||||
// FrameworkMediaDrm.
|
// FrameworkMediaDrm.
|
||||||
@RequiresApi(29)
|
@RequiresApi(29)
|
||||||
public class FakeExoMediaDrm implements ExoMediaDrm {
|
public final class FakeExoMediaDrm implements ExoMediaDrm {
|
||||||
|
|
||||||
public static final ProvisionRequest DUMMY_PROVISION_REQUEST =
|
public static final ProvisionRequest DUMMY_PROVISION_REQUEST =
|
||||||
new ProvisionRequest(TestUtil.createByteArray(7, 8, 9), "bar.test");
|
new ProvisionRequest(TestUtil.createByteArray(7, 8, 9), "bar.test");
|
||||||
|
|
@ -72,6 +73,7 @@ public class FakeExoMediaDrm implements ExoMediaDrm {
|
||||||
private static final ImmutableList<Byte> VALID_KEY_RESPONSE = TestUtil.createByteList(1, 2, 3);
|
private static final ImmutableList<Byte> VALID_KEY_RESPONSE = TestUtil.createByteList(1, 2, 3);
|
||||||
private static final ImmutableList<Byte> KEY_DENIED_RESPONSE = TestUtil.createByteList(9, 8, 7);
|
private static final ImmutableList<Byte> KEY_DENIED_RESPONSE = TestUtil.createByteList(9, 8, 7);
|
||||||
|
|
||||||
|
private final int maxConcurrentSessions;
|
||||||
private final Map<String, byte[]> byteProperties;
|
private final Map<String, byte[]> byteProperties;
|
||||||
private final Map<String, String> stringProperties;
|
private final Map<String, String> stringProperties;
|
||||||
private final Set<List<Byte>> openSessionIds;
|
private final Set<List<Byte>> openSessionIds;
|
||||||
|
|
@ -82,9 +84,20 @@ public class FakeExoMediaDrm implements ExoMediaDrm {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance that returns random and unique {@code sessionIds} for subsequent calls
|
* Constructs an instance that returns random and unique {@code sessionIds} for subsequent calls
|
||||||
* to {@link #openSession()}.
|
* to {@link #openSession()} with no limit on the number of concurrent open sessions.
|
||||||
*/
|
*/
|
||||||
public FakeExoMediaDrm() {
|
public FakeExoMediaDrm() {
|
||||||
|
this(/* maxConcurrentSessions= */ Integer.MAX_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance that returns random and unique {@code sessionIds} for subsequent calls
|
||||||
|
* to {@link #openSession()} with a limit on the number of concurrent open sessions.
|
||||||
|
*
|
||||||
|
* @param maxConcurrentSessions The max number of sessions allowed to be open simultaneously.
|
||||||
|
*/
|
||||||
|
public FakeExoMediaDrm(int maxConcurrentSessions) {
|
||||||
|
this.maxConcurrentSessions = maxConcurrentSessions;
|
||||||
byteProperties = new HashMap<>();
|
byteProperties = new HashMap<>();
|
||||||
stringProperties = new HashMap<>();
|
stringProperties = new HashMap<>();
|
||||||
openSessionIds = new HashSet<>();
|
openSessionIds = new HashSet<>();
|
||||||
|
|
@ -114,6 +127,9 @@ public class FakeExoMediaDrm implements ExoMediaDrm {
|
||||||
@Override
|
@Override
|
||||||
public byte[] openSession() throws MediaDrmException {
|
public byte[] openSession() throws MediaDrmException {
|
||||||
Assertions.checkState(referenceCount > 0);
|
Assertions.checkState(referenceCount > 0);
|
||||||
|
if (openSessionIds.size() >= maxConcurrentSessions) {
|
||||||
|
throw new ResourceBusyException("Too many sessions open. max=" + maxConcurrentSessions);
|
||||||
|
}
|
||||||
byte[] sessionId =
|
byte[] sessionId =
|
||||||
TestUtil.buildTestData(/* length= */ 10, sessionIdGenerator.incrementAndGet());
|
TestUtil.buildTestData(/* length= */ 10, sessionIdGenerator.incrementAndGet());
|
||||||
if (!openSessionIds.add(toByteList(sessionId))) {
|
if (!openSessionIds.add(toByteList(sessionId))) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue