mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Ensure DefaultDrmSessions keep working if their manager is released
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
This commit is contained in:
parent
4cca8b6d4a
commit
1bf5a273ff
@ -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
|
||||
|
@ -457,9 +457,15 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
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
|
||||
@ -478,8 +484,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
}
|
||||
releaseAllPreacquiredSessions();
|
||||
|
||||
checkNotNull(exoMediaDrm).release();
|
||||
exoMediaDrm = null;
|
||||
maybeReleaseMediaDrm();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -776,6 +781,17 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
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}.
|
||||
*
|
||||
@ -897,6 +913,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
keepaliveSessions.remove(session);
|
||||
}
|
||||
}
|
||||
maybeReleaseMediaDrm();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,6 +181,49 @@ public class DefaultDrmSessionManagerTest {
|
||||
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 =
|
||||
@ -452,6 +495,49 @@ public class DefaultDrmSessionManagerTest {
|
||||
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 =
|
||||
@ -477,6 +563,44 @@ public class DefaultDrmSessionManagerTest {
|
||||
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();
|
||||
|
@ -283,6 +283,10 @@ public final class FakeExoMediaDrm implements ExoMediaDrm {
|
||||
|
||||
// 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},
|
||||
|
Loading…
x
Reference in New Issue
Block a user