Skip to content

Commit

Permalink
Ensure DefaultDrmSessions keep working if their manager is released
Browse files Browse the repository at this point in the history
This change introduces a third 'state' for `DefaultDrmSessionManager`:
It's been fully released (prepareCount == 0) but at least one of its
sessions is still active.

In this state new acquisitions are rejected (`(pre)acquireSession()`
calls will fail) but the machinery to support the existing sessions
(ExoMediaDrm and MediaDrmHandler) is kept until they're all released.

This change will allow us to remove the TODO in MediaCodecRenderer
that resolves Issue: #8842.

PiperOrigin-RevId: 376193952
  • Loading branch information
icbaker authored and ojw28 committed May 27, 2021
1 parent 4cca8b6 commit 1bf5a27
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 5 deletions.
2 changes: 2 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@
* DRM:
* Don't restore offline keys before releasing them. In OEMCrypto v16+ keys
must be released without restoring them first.
* Ensure `DefaultDrmSession` instances keep working even after their
`DefaultDrmSessionManager` instance is released.
* UI:
* Keep subtitle language features embedded (e.g. rubies & tate-chu-yoko)
in `Cue.text` even when `SubtitleView#setApplyEmbeddedStyles()` is
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -457,9 +457,15 @@ public final void prepare() {
if (prepareCallsCount++ != 0) {
return;
}
checkState(exoMediaDrm == null);
exoMediaDrm = exoMediaDrmProvider.acquireExoMediaDrm(uuid);
exoMediaDrm.setOnEventListener(new MediaDrmEventListener());
if (exoMediaDrm == null) {
exoMediaDrm = exoMediaDrmProvider.acquireExoMediaDrm(uuid);
exoMediaDrm.setOnEventListener(new MediaDrmEventListener());
} else if (sessionKeepaliveMs != C.TIME_UNSET) {
// Re-acquire the keepalive references for any sessions that are still active.
for (int i = 0; i < sessions.size(); i++) {
sessions.get(i).acquire(/* eventDispatcher= */ null);
}
}
}

@Override
Expand All @@ -478,8 +484,7 @@ public final void release() {
}
releaseAllPreacquiredSessions();

checkNotNull(exoMediaDrm).release();
exoMediaDrm = null;
maybeReleaseMediaDrm();
}

@Override
Expand Down Expand Up @@ -776,6 +781,17 @@ private DefaultDrmSession createAndAcquireSession(
return session;
}

private void maybeReleaseMediaDrm() {
if (exoMediaDrm != null
&& prepareCallsCount == 0
&& sessions.isEmpty()
&& preacquiredSessionReferences.isEmpty()) {
// This manager and all its sessions are fully released so we can release exoMediaDrm.
checkNotNull(exoMediaDrm).release();
exoMediaDrm = null;
}
}

/**
* Extracts {@link SchemeData} instances suitable for the given DRM scheme {@link UUID}.
*
Expand Down Expand Up @@ -897,6 +913,7 @@ public void onReferenceCountDecremented(DefaultDrmSession session, int newRefere
keepaliveSessions.remove(session);
}
}
maybeReleaseMediaDrm();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,49 @@ public void managerRelease_keepaliveDisabled_doesntReleaseAnySessions() throws E
assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
}

@Test(timeout = 10_000)
public void managerRelease_mediaDrmNotReleasedUntilLastSessionReleased() throws Exception {
FakeExoMediaDrm.LicenseServer licenseServer =
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
FakeExoMediaDrm exoMediaDrm = new FakeExoMediaDrm();
DrmSessionManager drmSessionManager =
new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, new AppManagedProvider(exoMediaDrm))
.setSessionKeepaliveMs(10_000)
.build(/* mediaDrmCallback= */ licenseServer);

drmSessionManager.prepare();
DrmSession drmSession =
checkNotNull(
drmSessionManager.acquireSession(
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
/* eventDispatcher= */ null,
FORMAT_WITH_DRM_INIT_DATA));
drmSessionManager.release();

// The manager is now in a 'releasing' state because the session is still active - so the
// ExoMediaDrm instance should still be active (with 1 reference held by this test, and 1 held
// by the manager).
assertThat(exoMediaDrm.getReferenceCount()).isEqualTo(2);

// And re-preparing the session shouldn't acquire another reference.
drmSessionManager.prepare();
assertThat(exoMediaDrm.getReferenceCount()).isEqualTo(2);
drmSessionManager.release();

drmSession.release(/* eventDispatcher= */ null);

// The final session has been released, so now the ExoMediaDrm should be released too.
assertThat(exoMediaDrm.getReferenceCount()).isEqualTo(1);

// Re-preparing the fully released manager should now acquire another ExoMediaDrm reference.
drmSessionManager.prepare();
assertThat(exoMediaDrm.getReferenceCount()).isEqualTo(2);
drmSessionManager.release();

exoMediaDrm.release();
}

@Test(timeout = 10_000)
public void maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased() throws Exception {
ImmutableList<DrmInitData.SchemeData> secondSchemeDatas =
Expand Down Expand Up @@ -452,6 +495,49 @@ public void keyRefreshEvent_triggersKeyRefresh() throws Exception {
exoMediaDrm.release();
}

@Test(timeout = 10_000)
public void keyRefreshEvent_whileManagerIsReleasing_triggersKeyRefresh() throws Exception {
FakeExoMediaDrm exoMediaDrm = new FakeExoMediaDrm();
FakeExoMediaDrm.LicenseServer licenseServer =
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
DrmSessionManager drmSessionManager =
new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, new AppManagedProvider(exoMediaDrm))
.build(/* mediaDrmCallback= */ licenseServer);

drmSessionManager.prepare();

DefaultDrmSession drmSession =
(DefaultDrmSession)
checkNotNull(
drmSessionManager.acquireSession(
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
/* eventDispatcher= */ null,
FORMAT_WITH_DRM_INIT_DATA));
waitForOpenedWithKeys(drmSession);

assertThat(licenseServer.getReceivedSchemeDatas()).hasSize(1);

drmSessionManager.release();

exoMediaDrm.triggerEvent(
drmSession::hasSessionId,
ExoMediaDrm.EVENT_KEY_REQUIRED,
/* extra= */ 0,
/* data= */ Util.EMPTY_BYTE_ARRAY);

while (licenseServer.getReceivedSchemeDatas().size() == 1) {
// Allow the key refresh event to be handled.
ShadowLooper.idleMainLooper();
}

assertThat(licenseServer.getReceivedSchemeDatas()).hasSize(2);
assertThat(ImmutableSet.copyOf(licenseServer.getReceivedSchemeDatas())).hasSize(1);

drmSession.release(/* eventDispatcher= */ null);
exoMediaDrm.release();
}

@Test
public void managerNotPrepared_acquireSessionAndPreacquireSessionFail() throws Exception {
FakeExoMediaDrm.LicenseServer licenseServer =
Expand All @@ -477,6 +563,44 @@ public void managerNotPrepared_acquireSessionAndPreacquireSessionFail() throws E
FORMAT_WITH_DRM_INIT_DATA));
}

@Test
public void managerReleasing_acquireSessionAndPreacquireSessionFail() throws Exception {
FakeExoMediaDrm.LicenseServer licenseServer =
FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS);
DefaultDrmSessionManager drmSessionManager =
new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(DRM_SCHEME_UUID, uuid -> new FakeExoMediaDrm())
.build(/* mediaDrmCallback= */ licenseServer);

drmSessionManager.prepare();
DrmSession drmSession =
checkNotNull(
drmSessionManager.acquireSession(
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
/* eventDispatcher= */ null,
FORMAT_WITH_DRM_INIT_DATA));
drmSessionManager.release();

// The manager's prepareCount is now zero, but the drmSession is keeping it in a 'releasing'
// state. acquireSession and preacquireSession should still fail.
assertThrows(
Exception.class,
() ->
drmSessionManager.acquireSession(
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
/* eventDispatcher= */ null,
FORMAT_WITH_DRM_INIT_DATA));
assertThrows(
Exception.class,
() ->
drmSessionManager.preacquireSession(
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
/* eventDispatcher= */ null,
FORMAT_WITH_DRM_INIT_DATA));

drmSession.release(/* eventDispatcher= */ null);
}

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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ public Class<FakeExoMediaCrypto> getExoMediaCryptoType() {

// Methods to facilitate testing

public int getReferenceCount() {
return referenceCount;
}

/**
* Calls {@link OnEventListener#onEvent(ExoMediaDrm, byte[], int, int, byte[])} on the attached
* listener (if present) once for each open session ID which passes {@code sessionIdPredicate},
Expand Down

0 comments on commit 1bf5a27

Please sign in to comment.