Fix how preacquired DRM sessions are released under resource contention

Previously the released preacquired sessions would start their keepalive
timeout, and so no additional resources would be freed in time for the
manager to retry the session acquisition.

This change adds an additional purge of keepalive sessions *after* the
preacquired sessions are released, which fixes the problem.

PiperOrigin-RevId: 396613352
This commit is contained in:
ibaker 2021-09-14 17:26:48 +01:00 committed by Christos Tsilopoulos
parent e6cb52a471
commit b4aa8688df
3 changed files with 80 additions and 10 deletions

View File

@ -30,6 +30,9 @@
thrown from `Requirements.isInternetConnectivityValidated` on devices thrown from `Requirements.isInternetConnectivityValidated` on devices
running Android 11 running Android 11
([#9002](https://github.com/google/ExoPlayer/issues/9002)). ([#9002](https://github.com/google/ExoPlayer/issues/9002)).
* DRM:
* Fix `DefaultDrmSessionManager` to correctly eagerly release preacquired
DRM sessions when there's a shortage of DRM resources on the device.
* DASH: * DASH:
* Use identical cache keys for downloading and playing DASH segments * Use identical cache keys for downloading and playing DASH segments
([#9370](https://github.com/google/ExoPlayer/issues/9370)). ([#9370](https://github.com/google/ExoPlayer/issues/9370)).

View File

@ -687,13 +687,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
// If we're short on DRM session resources, first try eagerly releasing all our keepalive // If we're short on DRM session resources, first try eagerly releasing all our keepalive
// sessions and then retry the acquisition. // sessions and then retry the acquisition.
if (acquisitionFailedIndicatingResourceShortage(session) && !keepaliveSessions.isEmpty()) { if (acquisitionFailedIndicatingResourceShortage(session) && !keepaliveSessions.isEmpty()) {
// Make a local copy, because sessions are removed from this.keepaliveSessions during releaseAllKeepaliveSessions();
// release (via callback).
ImmutableSet<DefaultDrmSession> keepaliveSessions =
ImmutableSet.copyOf(this.keepaliveSessions);
for (DrmSession keepaliveSession : keepaliveSessions) {
keepaliveSession.release(/* eventDispatcher= */ null);
}
undoAcquisition(session, eventDispatcher); undoAcquisition(session, eventDispatcher);
session = createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher); session = createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher);
} }
@ -705,6 +699,11 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
&& shouldReleasePreacquiredSessionsBeforeRetrying && shouldReleasePreacquiredSessionsBeforeRetrying
&& !preacquiredSessionReferences.isEmpty()) { && !preacquiredSessionReferences.isEmpty()) {
releaseAllPreacquiredSessions(); releaseAllPreacquiredSessions();
if (!keepaliveSessions.isEmpty()) {
// Some preacquired sessions released above are now in their keepalive timeout phase. We
// release the keepalive references immediately.
releaseAllKeepaliveSessions();
}
undoAcquisition(session, eventDispatcher); undoAcquisition(session, eventDispatcher);
session = createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher); session = createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher);
} }
@ -731,6 +730,15 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
} }
} }
private void releaseAllKeepaliveSessions() {
// 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);
}
}
private void releaseAllPreacquiredSessions() { private void releaseAllPreacquiredSessions() {
// Make a local copy, because sessions are removed from this.preacquiredSessionReferences // Make a local copy, because sessions are removed from this.preacquiredSessionReferences
// during release (via callback). // during release (via callback).

View File

@ -25,6 +25,7 @@ import androidx.annotation.Nullable;
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.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmSessionManager.DrmSessionReference;
import com.google.android.exoplayer2.drm.ExoMediaDrm.AppManagedProvider; import com.google.android.exoplayer2.drm.ExoMediaDrm.AppManagedProvider;
import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.testutil.FakeExoMediaDrm; import com.google.android.exoplayer2.testutil.FakeExoMediaDrm;
@ -302,6 +303,64 @@ public class DefaultDrmSessionManagerTest {
assertThat(secondDrmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS); assertThat(secondDrmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
} }
@Test(timeout = 10_000)
public void maxConcurrentSessionsExceeded_allPreacquiredAndKeepaliveSessionsEagerlyReleased()
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);
Format secondFormatWithDrmInitData =
new Format.Builder().setDrmInitData(new DrmInitData(secondSchemeDatas)).build();
DrmSessionManager drmSessionManager =
new DefaultDrmSessionManager.Builder()
.setUuidAndExoMediaDrmProvider(
DRM_SCHEME_UUID,
uuid -> new FakeExoMediaDrm.Builder().setMaxConcurrentSessions(1).build())
.setSessionKeepaliveMs(10_000)
.setMultiSession(true)
.build(/* mediaDrmCallback= */ licenseServer);
drmSessionManager.prepare();
DrmSessionReference firstDrmSessionReference =
checkNotNull(
drmSessionManager.preacquireSession(
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
/* eventDispatcher= */ null,
FORMAT_WITH_DRM_INIT_DATA));
DrmSession firstDrmSession =
checkNotNull(
drmSessionManager.acquireSession(
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
/* eventDispatcher= */ null,
FORMAT_WITH_DRM_INIT_DATA));
waitForOpenedWithKeys(firstDrmSession);
firstDrmSession.release(/* eventDispatcher= */ null);
// The direct reference to firstDrmSession has been released, it's being kept alive by both
// firstDrmSessionReference and drmSessionManager's internal reference.
assertThat(firstDrmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
DrmSession secondDrmSession =
checkNotNull(
drmSessionManager.acquireSession(
/* playbackLooper= */ checkNotNull(Looper.myLooper()),
/* eventDispatcher= */ null,
secondFormatWithDrmInitData));
// The drmSessionManager had to release both it's internal keep-alive reference and the
// reference represented by firstDrmSessionReference in order to acquire secondDrmSession.
assertThat(firstDrmSession.getState()).isEqualTo(DrmSession.STATE_RELEASED);
waitForOpenedWithKeys(secondDrmSession);
assertThat(secondDrmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS);
// Not needed (because the manager has already released this reference) but we call it anyway
// for completeness.
firstDrmSessionReference.release();
// Clean-up
secondDrmSession.release(/* eventDispatcher= */ null);
drmSessionManager.release();
}
@Test(timeout = 10_000) @Test(timeout = 10_000)
public void sessionReacquired_keepaliveTimeOutCancelled() throws Exception { public void sessionReacquired_keepaliveTimeOutCancelled() throws Exception {
FakeExoMediaDrm.LicenseServer licenseServer = FakeExoMediaDrm.LicenseServer licenseServer =
@ -364,7 +423,7 @@ public class DefaultDrmSessionManagerTest {
drmSessionManager.prepare(); drmSessionManager.prepare();
DrmSessionManager.DrmSessionReference sessionReference = DrmSessionReference sessionReference =
drmSessionManager.preacquireSession( drmSessionManager.preacquireSession(
/* playbackLooper= */ checkNotNull(Looper.myLooper()), /* playbackLooper= */ checkNotNull(Looper.myLooper()),
eventDispatcher, eventDispatcher,
@ -413,7 +472,7 @@ public class DefaultDrmSessionManagerTest {
drmSessionManager.prepare(); drmSessionManager.prepare();
DrmSessionManager.DrmSessionReference sessionReference = DrmSessionReference sessionReference =
drmSessionManager.preacquireSession( drmSessionManager.preacquireSession(
/* playbackLooper= */ checkNotNull(Looper.myLooper()), /* playbackLooper= */ checkNotNull(Looper.myLooper()),
/* eventDispatcher= */ null, /* eventDispatcher= */ null,
@ -453,7 +512,7 @@ public class DefaultDrmSessionManagerTest {
drmSessionManager.prepare(); drmSessionManager.prepare();
DrmSessionManager.DrmSessionReference sessionReference = DrmSessionReference sessionReference =
drmSessionManager.preacquireSession( drmSessionManager.preacquireSession(
/* playbackLooper= */ checkNotNull(Looper.myLooper()), /* playbackLooper= */ checkNotNull(Looper.myLooper()),
/* eventDispatcher= */ null, /* eventDispatcher= */ null,