Add support for preparing DRM sessions before they're needed
This adds an optional DrmSessionManager#preacquireSession() method and implements it on DefaultDrmSessionManager. The manager doesn't promise to keep the preacquired sessions alive, and will proactively release them if a ResourceBusyException suggests the device is running out of available sessions in the underlying framework. In a future change, SampleQueue will preacquire sessions on the loading thread and keep track of preacquired 'references', releasing them when seeking or clearing the queue. Issue: #4133 PiperOrigin-RevId: 358381616
This commit is contained in:
parent
ecb109dad8
commit
74ad0949fd
@ -47,9 +47,16 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
|
||||
/** A {@link DrmSessionManager} that supports playbacks using {@link ExoMediaDrm}. */
|
||||
/**
|
||||
* A {@link DrmSessionManager} that supports playbacks using {@link ExoMediaDrm}.
|
||||
*
|
||||
* <p>This implementation supports pre-acquisition of sessions using {@link
|
||||
* #preacquireSession(Looper, DrmSessionEventListener.EventDispatcher, Format)}.
|
||||
*/
|
||||
@RequiresApi(18)
|
||||
public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
|
||||
@ -282,14 +289,15 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
|
||||
private final List<DefaultDrmSession> sessions;
|
||||
private final List<DefaultDrmSession> provisioningSessions;
|
||||
private final Set<PreacquiredSessionReference> preacquiredSessionReferences;
|
||||
private final Set<DefaultDrmSession> keepaliveSessions;
|
||||
|
||||
private int prepareCallsCount;
|
||||
@Nullable private ExoMediaDrm exoMediaDrm;
|
||||
@Nullable private DefaultDrmSession placeholderDrmSession;
|
||||
@Nullable private DefaultDrmSession noMultiSessionDrmSession;
|
||||
@Nullable private Looper playbackLooper;
|
||||
private @MonotonicNonNull Handler sessionReleasingHandler;
|
||||
private @MonotonicNonNull Looper playbackLooper;
|
||||
private @MonotonicNonNull Handler playbackHandler;
|
||||
private int mode;
|
||||
@Nullable private byte[] offlineLicenseKeySetId;
|
||||
|
||||
@ -403,6 +411,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
mode = MODE_PLAYBACK;
|
||||
sessions = new ArrayList<>();
|
||||
provisioningSessions = new ArrayList<>();
|
||||
preacquiredSessionReferences = Sets.newIdentityHashSet();
|
||||
keepaliveSessions = Sets.newIdentityHashSet();
|
||||
this.sessionKeepaliveMs = sessionKeepaliveMs;
|
||||
}
|
||||
@ -466,10 +475,24 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
sessions.get(i).release(/* eventDispatcher= */ null);
|
||||
}
|
||||
}
|
||||
releaseAllPreacquiredSessions();
|
||||
|
||||
Assertions.checkNotNull(exoMediaDrm).release();
|
||||
exoMediaDrm = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DrmSessionReference preacquireSession(
|
||||
Looper playbackLooper,
|
||||
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
|
||||
Format format) {
|
||||
initPlaybackLooper(playbackLooper);
|
||||
PreacquiredSessionReference preacquiredSessionReference =
|
||||
new PreacquiredSessionReference(eventDispatcher);
|
||||
preacquiredSessionReference.acquire(format);
|
||||
return preacquiredSessionReference;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public DrmSession acquireSession(
|
||||
@ -477,11 +500,27 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
|
||||
Format format) {
|
||||
initPlaybackLooper(playbackLooper);
|
||||
return acquireSession(
|
||||
playbackLooper,
|
||||
eventDispatcher,
|
||||
format,
|
||||
/* shouldReleasePreacquiredSessionsBeforeRetrying= */ true);
|
||||
}
|
||||
|
||||
// Must be called on the playback thread.
|
||||
@Nullable
|
||||
private DrmSession acquireSession(
|
||||
Looper playbackLooper,
|
||||
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
|
||||
Format format,
|
||||
boolean shouldReleasePreacquiredSessionsBeforeRetrying) {
|
||||
maybeCreateMediaDrmHandler(playbackLooper);
|
||||
|
||||
if (format.drmInitData == null) {
|
||||
// Content is not encrypted.
|
||||
return maybeAcquirePlaceholderSession(MimeTypes.getTrackType(format.sampleMimeType));
|
||||
return maybeAcquirePlaceholderSession(
|
||||
MimeTypes.getTrackType(format.sampleMimeType),
|
||||
shouldReleasePreacquiredSessionsBeforeRetrying);
|
||||
}
|
||||
|
||||
@Nullable List<SchemeData> schemeDatas = null;
|
||||
@ -515,7 +554,10 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
// Create a new session.
|
||||
session =
|
||||
createAndAcquireSessionWithRetry(
|
||||
schemeDatas, /* isPlaceholderSession= */ false, eventDispatcher);
|
||||
schemeDatas,
|
||||
/* isPlaceholderSession= */ false,
|
||||
eventDispatcher,
|
||||
shouldReleasePreacquiredSessionsBeforeRetrying);
|
||||
if (!multiSession) {
|
||||
noMultiSessionDrmSession = session;
|
||||
}
|
||||
@ -547,7 +589,8 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
// Internal methods.
|
||||
|
||||
@Nullable
|
||||
private DrmSession maybeAcquirePlaceholderSession(int trackType) {
|
||||
private DrmSession maybeAcquirePlaceholderSession(
|
||||
int trackType, boolean shouldReleasePreacquiredSessionsBeforeRetrying) {
|
||||
ExoMediaDrm exoMediaDrm = Assertions.checkNotNull(this.exoMediaDrm);
|
||||
boolean avoidPlaceholderDrmSessions =
|
||||
FrameworkMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType())
|
||||
@ -563,7 +606,8 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
createAndAcquireSessionWithRetry(
|
||||
/* schemeDatas= */ ImmutableList.of(),
|
||||
/* isPlaceholderSession= */ true,
|
||||
/* eventDispatcher= */ null);
|
||||
/* eventDispatcher= */ null,
|
||||
shouldReleasePreacquiredSessionsBeforeRetrying);
|
||||
sessions.add(placeholderDrmSession);
|
||||
this.placeholderDrmSession = placeholderDrmSession;
|
||||
} else {
|
||||
@ -607,12 +651,14 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void initPlaybackLooper(Looper playbackLooper) {
|
||||
@EnsuresNonNull({"this.playbackLooper", "this.playbackHandler"})
|
||||
private synchronized void initPlaybackLooper(Looper playbackLooper) {
|
||||
if (this.playbackLooper == null) {
|
||||
this.playbackLooper = playbackLooper;
|
||||
this.sessionReleasingHandler = new Handler(playbackLooper);
|
||||
this.playbackHandler = new Handler(playbackLooper);
|
||||
} else {
|
||||
Assertions.checkState(this.playbackLooper == playbackLooper);
|
||||
Assertions.checkNotNull(playbackHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@ -625,35 +671,68 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
private DefaultDrmSession createAndAcquireSessionWithRetry(
|
||||
@Nullable List<SchemeData> schemeDatas,
|
||||
boolean isPlaceholderSession,
|
||||
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher) {
|
||||
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
|
||||
boolean shouldReleasePreacquiredSessionsBeforeRetrying) {
|
||||
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.keepaliveSessions during
|
||||
// release (via callback).
|
||||
ImmutableSet<DefaultDrmSession> keepaliveSessions =
|
||||
ImmutableSet.copyOf(this.keepaliveSessions);
|
||||
for (DrmSession keepaliveSession : keepaliveSessions) {
|
||||
keepaliveSession.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);
|
||||
// If we're short on DRM session resources, first try eagerly releasing all our keepalive
|
||||
// sessions and then retry the acquisition.
|
||||
if (acquisitionFailedIndicatingResourceShortage(session) && !keepaliveSessions.isEmpty()) {
|
||||
// Make a local copy, because sessions are removed from this.keepaliveSessions during
|
||||
// release (via callback).
|
||||
ImmutableSet<DefaultDrmSession> keepaliveSessions =
|
||||
ImmutableSet.copyOf(this.keepaliveSessions);
|
||||
for (DrmSession keepaliveSession : keepaliveSessions) {
|
||||
keepaliveSession.release(/* eventDispatcher= */ null);
|
||||
}
|
||||
undoAcquisition(session, eventDispatcher);
|
||||
session = createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher);
|
||||
}
|
||||
|
||||
// If the acquisition failed again due to continued resource shortage, and
|
||||
// shouldReleasePreacquiredSessionsBeforeRetrying is true, try releasing all pre-acquired
|
||||
// sessions and then retry the acquisition.
|
||||
if (acquisitionFailedIndicatingResourceShortage(session)
|
||||
&& shouldReleasePreacquiredSessionsBeforeRetrying
|
||||
&& !preacquiredSessionReferences.isEmpty()) {
|
||||
releaseAllPreacquiredSessions();
|
||||
undoAcquisition(session, eventDispatcher);
|
||||
session = createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher);
|
||||
}
|
||||
return session;
|
||||
}
|
||||
|
||||
private static boolean acquisitionFailedIndicatingResourceShortage(DrmSession session) {
|
||||
// ResourceBusyException is only available at API 19, so on earlier versions we
|
||||
// assume any error indicates resource shortage (ensuring we retry).
|
||||
return session.getState() == DrmSession.STATE_ERROR
|
||||
&& (Util.SDK_INT < 19
|
||||
|| Assertions.checkNotNull(session.getError()).getCause()
|
||||
instanceof ResourceBusyException);
|
||||
}
|
||||
|
||||
/**
|
||||
* Undoes the acquisitions from {@link #createAndAcquireSession(List, boolean,
|
||||
* DrmSessionEventListener.EventDispatcher)}.
|
||||
*/
|
||||
private void undoAcquisition(
|
||||
DrmSession session, @Nullable DrmSessionEventListener.EventDispatcher eventDispatcher) {
|
||||
session.release(eventDispatcher);
|
||||
if (sessionKeepaliveMs != C.TIME_UNSET) {
|
||||
session.release(/* eventDispatcher= */ null);
|
||||
}
|
||||
}
|
||||
|
||||
private void releaseAllPreacquiredSessions() {
|
||||
// Make a local copy, because sessions are removed from this.preacquiredSessionReferences
|
||||
// during release (via callback).
|
||||
ImmutableSet<PreacquiredSessionReference> preacquiredSessionReferences =
|
||||
ImmutableSet.copyOf(this.preacquiredSessionReferences);
|
||||
for (PreacquiredSessionReference preacquiredSessionReference : preacquiredSessionReferences) {
|
||||
preacquiredSessionReference.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultDrmSession} and acquires it on behalf of the caller (passing in
|
||||
* {@code eventDispatcher}).
|
||||
@ -782,7 +861,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
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);
|
||||
Assertions.checkNotNull(playbackHandler).removeCallbacksAndMessages(session);
|
||||
}
|
||||
}
|
||||
|
||||
@ -791,7 +870,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
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)
|
||||
Assertions.checkNotNull(playbackHandler)
|
||||
.postAtTime(
|
||||
() -> session.release(/* eventDispatcher= */ null),
|
||||
session,
|
||||
@ -812,7 +891,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
}
|
||||
provisioningSessions.remove(session);
|
||||
if (sessionKeepaliveMs != C.TIME_UNSET) {
|
||||
Assertions.checkNotNull(sessionReleasingHandler).removeCallbacksAndMessages(session);
|
||||
Assertions.checkNotNull(playbackHandler).removeCallbacksAndMessages(session);
|
||||
keepaliveSessions.remove(session);
|
||||
}
|
||||
}
|
||||
@ -827,4 +906,75 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
Assertions.checkNotNull(mediaDrmHandler).obtainMessage(event, sessionId).sendToTarget();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An implementation of {@link DrmSessionReference} that lazily acquires the underlying {@link
|
||||
* DrmSession}.
|
||||
*
|
||||
* <p>A new instance is needed for each reference (compared to maintaining exactly one instance
|
||||
* for each {@link DrmSession}) because each associated {@link
|
||||
* DrmSessionEventListener.EventDispatcher} might be different. The {@link
|
||||
* DrmSessionEventListener.EventDispatcher} is required to implement the zero-arg {@link
|
||||
* DrmSessionReference#release()} method.
|
||||
*/
|
||||
private class PreacquiredSessionReference implements DrmSessionReference {
|
||||
|
||||
@Nullable private final DrmSessionEventListener.EventDispatcher eventDispatcher;
|
||||
|
||||
@Nullable private DrmSession session;
|
||||
private boolean isReleased;
|
||||
|
||||
/**
|
||||
* Constructs an instance.
|
||||
*
|
||||
* @param eventDispatcher The {@link DrmSessionEventListener.EventDispatcher} passed to {@link
|
||||
* #acquireSession(Looper, DrmSessionEventListener.EventDispatcher, Format)}.
|
||||
*/
|
||||
public PreacquiredSessionReference(
|
||||
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher) {
|
||||
this.eventDispatcher = eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires the underlying session.
|
||||
*
|
||||
* <p>Must be called at most once. Can be called from any thread.
|
||||
*/
|
||||
@RequiresNonNull("playbackHandler")
|
||||
public void acquire(Format format) {
|
||||
playbackHandler.post(
|
||||
() -> {
|
||||
if (prepareCallsCount == 0 || isReleased) {
|
||||
// The manager has been fully released or this reference has already been released.
|
||||
// Abort the acquisition attempt.
|
||||
return;
|
||||
}
|
||||
this.session =
|
||||
acquireSession(
|
||||
Assertions.checkNotNull(playbackLooper),
|
||||
eventDispatcher,
|
||||
format,
|
||||
/* shouldReleasePreacquiredSessionsBeforeRetrying= */ false);
|
||||
preacquiredSessionReferences.add(this);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
// Ensure the underlying session is released immediately if we're already on the playback
|
||||
// thread, to allow a failed session opening to be immediately retried.
|
||||
Util.postOrRun(
|
||||
Assertions.checkNotNull(playbackHandler),
|
||||
() -> {
|
||||
if (isReleased) {
|
||||
return;
|
||||
}
|
||||
if (session != null) {
|
||||
session.release(eventDispatcher);
|
||||
}
|
||||
preacquiredSessionReferences.remove(this);
|
||||
isReleased = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,23 @@ import com.google.android.exoplayer2.Format;
|
||||
/** Manages a DRM session. */
|
||||
public interface DrmSessionManager {
|
||||
|
||||
/**
|
||||
* Represents a single reference count of a {@link DrmSession}, while deliberately not giving
|
||||
* access to the underlying session.
|
||||
*/
|
||||
interface DrmSessionReference {
|
||||
/** A reference that is never populated with an underlying {@link DrmSession}. */
|
||||
DrmSessionReference EMPTY = () -> {};
|
||||
|
||||
/**
|
||||
* Releases the underlying session at most once.
|
||||
*
|
||||
* <p>Can be called from any thread. Calling this method more than once will only release the
|
||||
* underlying session once.
|
||||
*/
|
||||
void release();
|
||||
}
|
||||
|
||||
/** An instance that supports no DRM schemes. */
|
||||
DrmSessionManager DRM_UNSUPPORTED =
|
||||
new DrmSessionManager() {
|
||||
@ -81,6 +98,51 @@ public interface DrmSessionManager {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-acquires a DRM session for the specified {@link Format}.
|
||||
*
|
||||
* <p>This notifies the manager that a subsequent call to {@link #acquireSession(Looper,
|
||||
* DrmSessionEventListener.EventDispatcher, Format)} with the same {@link Format} is likely,
|
||||
* allowing a manager that supports pre-acquisition to get the required {@link DrmSession} ready
|
||||
* in the background.
|
||||
*
|
||||
* <p>The caller must call {@link DrmSessionReference#release()} on the returned instance when
|
||||
* they no longer require the pre-acquisition (i.e. they know they won't be making a matching call
|
||||
* to {@link #acquireSession(Looper, DrmSessionEventListener.EventDispatcher, Format)} in the near
|
||||
* future).
|
||||
*
|
||||
* <p>This manager may silently release the underlying session in order to allow another operation
|
||||
* to complete. This will result in a subsequent call to {@link #acquireSession(Looper,
|
||||
* DrmSessionEventListener.EventDispatcher, Format)} re-initializing a new session, including
|
||||
* repeating key loads and other async initialization steps.
|
||||
*
|
||||
* <p>The caller must separately call {@link #acquireSession(Looper,
|
||||
* DrmSessionEventListener.EventDispatcher, Format)} in order to obtain a session suitable for
|
||||
* playback. The pre-acquired {@link DrmSessionReference} and full {@link DrmSession} instances
|
||||
* are distinct. The caller must release both, and can release the {@link DrmSessionReference}
|
||||
* before the {@link DrmSession} without affecting playback.
|
||||
*
|
||||
* <p>This can be called from any thread.
|
||||
*
|
||||
* <p>Implementations that do not support pre-acquisition always return an empty {@link
|
||||
* DrmSessionReference} instance.
|
||||
*
|
||||
* @param playbackLooper The looper associated with the media playback thread.
|
||||
* @param eventDispatcher The {@link DrmSessionEventListener.EventDispatcher} used to distribute
|
||||
* events, and passed on to {@link
|
||||
* DrmSession#acquire(DrmSessionEventListener.EventDispatcher)}.
|
||||
* @param format The {@link Format} for which to pre-acquire a {@link DrmSession}.
|
||||
* @return A releaser for the pre-acquired session. Guaranteed to be non-null even if the matching
|
||||
* {@link #acquireSession(Looper, DrmSessionEventListener.EventDispatcher, Format)} would
|
||||
* return null.
|
||||
*/
|
||||
default DrmSessionReference preacquireSession(
|
||||
Looper playbackLooper,
|
||||
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
|
||||
Format format) {
|
||||
return DrmSessionReference.EMPTY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link DrmSession} for the specified {@link Format}, with an incremented reference
|
||||
* count. May return null if the {@link Format#drmInitData} is null and the DRM session manager is
|
||||
|
@ -20,14 +20,18 @@ import static com.google.common.truth.Truth.assertThat;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import android.os.Looper;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.source.MediaSource;
|
||||
import com.google.android.exoplayer2.testutil.FakeExoMediaDrm;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.shadows.ShadowLooper;
|
||||
@ -38,6 +42,7 @@ import org.robolectric.shadows.ShadowLooper;
|
||||
// - Multiple acquisitions & releases for same keys -> multiple requests.
|
||||
// - Provisioning.
|
||||
// - Key denial.
|
||||
// - Handling of ResourceBusyException (indicating session scarcity).
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class DefaultDrmSessionManagerTest {
|
||||
|
||||
@ -252,6 +257,156 @@ public class DefaultDrmSessionManagerTest {
|
||||
assertThat(secondDrmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||
}
|
||||
|
||||
@Test(timeout = 10_000)
|
||||
public void preacquireSession_loadsKeysBeforeFullAcquisition() throws Exception {
|
||||
AtomicInteger keyLoadCount = new AtomicInteger(0);
|
||||
DrmSessionEventListener.EventDispatcher eventDispatcher =
|
||||
new DrmSessionEventListener.EventDispatcher();
|
||||
eventDispatcher.addEventListener(
|
||||
Util.createHandlerForCurrentLooper(),
|
||||
new DrmSessionEventListener() {
|
||||
@Override
|
||||
public void onDrmKeysLoaded(
|
||||
int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) {
|
||||
keyLoadCount.incrementAndGet();
|
||||
}
|
||||
});
|
||||
FakeExoMediaDrm.LicenseServer licenseServer =
|
||||
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
|
||||
DrmSessionManager drmSessionManager =
|
||||
new DefaultDrmSessionManager.Builder()
|
||||
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm())
|
||||
// Disable keepalive
|
||||
.setSessionKeepaliveMs(C.TIME_UNSET)
|
||||
.build(/* mediaDrmCallback= */ licenseServer);
|
||||
|
||||
drmSessionManager.prepare();
|
||||
|
||||
DrmSessionManager.DrmSessionReference sessionReference =
|
||||
drmSessionManager.preacquireSession(
|
||||
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
|
||||
eventDispatcher,
|
||||
FORMAT_WITH_DRM_INIT_DATA);
|
||||
|
||||
// Wait for the key load event to propagate, indicating the pre-acquired session is in
|
||||
// STATE_OPENED_WITH_KEYS.
|
||||
while (keyLoadCount.get() == 0) {
|
||||
// Allow the key response to be handled.
|
||||
ShadowLooper.idleMainLooper();
|
||||
}
|
||||
|
||||
DrmSession drmSession =
|
||||
checkNotNull(
|
||||
drmSessionManager.acquireSession(
|
||||
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
|
||||
/* eventDispatcher= */ null,
|
||||
FORMAT_WITH_DRM_INIT_DATA));
|
||||
|
||||
// Without idling the main/playback looper, we assert the session is already in OPENED_WITH_KEYS
|
||||
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||
assertThat(keyLoadCount.get()).isEqualTo(1);
|
||||
|
||||
// After releasing our concrete session reference, the session is held open by the pre-acquired
|
||||
// reference.
|
||||
drmSession.release(/* eventDispatcher= */ null);
|
||||
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||
|
||||
// Releasing the pre-acquired reference allows the session to be fully released.
|
||||
sessionReference.release();
|
||||
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_RELEASED);
|
||||
}
|
||||
|
||||
@Test(timeout = 10_000)
|
||||
public void
|
||||
preacquireSession_releaseBeforeUnderlyingAcquisitionCompletesReleasesSessionOnceAcquired()
|
||||
throws Exception {
|
||||
FakeExoMediaDrm.LicenseServer licenseServer =
|
||||
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
|
||||
DrmSessionManager drmSessionManager =
|
||||
new DefaultDrmSessionManager.Builder()
|
||||
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm())
|
||||
// Disable keepalive
|
||||
.setSessionKeepaliveMs(C.TIME_UNSET)
|
||||
.build(/* mediaDrmCallback= */ licenseServer);
|
||||
|
||||
drmSessionManager.prepare();
|
||||
|
||||
DrmSessionManager.DrmSessionReference sessionReference =
|
||||
drmSessionManager.preacquireSession(
|
||||
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
|
||||
/* eventDispatcher= */ null,
|
||||
FORMAT_WITH_DRM_INIT_DATA);
|
||||
|
||||
// Release the pre-acquired reference before the underlying session has had a chance to be
|
||||
// constructed.
|
||||
sessionReference.release();
|
||||
|
||||
// Acquiring the same session triggers a second key load (because the pre-acquired session was
|
||||
// fully released).
|
||||
DrmSession drmSession =
|
||||
checkNotNull(
|
||||
drmSessionManager.acquireSession(
|
||||
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
|
||||
/* eventDispatcher= */ null,
|
||||
FORMAT_WITH_DRM_INIT_DATA));
|
||||
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED);
|
||||
|
||||
waitForOpenedWithKeys(drmSession);
|
||||
|
||||
drmSession.release(/* eventDispatcher= */ null);
|
||||
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_RELEASED);
|
||||
}
|
||||
|
||||
@Test(timeout = 10_000)
|
||||
public void preacquireSession_releaseManagerBeforeAcquisition_acquisitionDoesntHappen()
|
||||
throws Exception {
|
||||
FakeExoMediaDrm.LicenseServer licenseServer =
|
||||
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
|
||||
DrmSessionManager drmSessionManager =
|
||||
new DefaultDrmSessionManager.Builder()
|
||||
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm())
|
||||
// Disable keepalive
|
||||
.setSessionKeepaliveMs(C.TIME_UNSET)
|
||||
.build(/* mediaDrmCallback= */ licenseServer);
|
||||
|
||||
drmSessionManager.prepare();
|
||||
|
||||
DrmSessionManager.DrmSessionReference sessionReference =
|
||||
drmSessionManager.preacquireSession(
|
||||
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
|
||||
/* eventDispatcher= */ null,
|
||||
FORMAT_WITH_DRM_INIT_DATA);
|
||||
|
||||
// Release the manager before the underlying session has had a chance to be constructed. This
|
||||
// will release all pre-acquired sessions.
|
||||
drmSessionManager.release();
|
||||
|
||||
// Allow the acquisition event to be handled on the main/playback thread.
|
||||
ShadowLooper.idleMainLooper();
|
||||
|
||||
// Re-prepare the manager so we can fully acquire the same session, and check the previous
|
||||
// pre-acquisition didn't do anything.
|
||||
drmSessionManager.prepare();
|
||||
DrmSession drmSession =
|
||||
checkNotNull(
|
||||
drmSessionManager.acquireSession(
|
||||
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
|
||||
/* eventDispatcher= */ null,
|
||||
FORMAT_WITH_DRM_INIT_DATA));
|
||||
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED);
|
||||
waitForOpenedWithKeys(drmSession);
|
||||
|
||||
drmSession.release(/* eventDispatcher= */ null);
|
||||
// If the (still unreleased) pre-acquired session above was linked to the same underlying
|
||||
// session then the state would still be OPENED_WITH_KEYS.
|
||||
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_RELEASED);
|
||||
|
||||
// Release the pre-acquired session from above (this is a no-op, but we do it anyway for
|
||||
// correctness).
|
||||
sessionReference.release();
|
||||
drmSessionManager.release();
|
||||
}
|
||||
|
||||
private static void waitForOpenedWithKeys(DrmSession drmSession) {
|
||||
// Check the error first, so we get a meaningful failure if there's been an error.
|
||||
assertThat(drmSession.getError()).isNull();
|
||||
|
Loading…
x
Reference in New Issue
Block a user