diff --git a/RELEASENOTES.md b/RELEASENOTES.md index d2322fa157..9aae0bcceb 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -98,6 +98,8 @@ using a `ForwardingPlayer` that overrides `getSeekBackIncrement`, `seekBack`, `getSeekForwardIncrement` and `seekForward`. The rewind and fast forward buttons can be disabled by using a `ForwardingPlayer` that + `seekBack`, `getSeekForwardIncrement` and `seekForward`. The + corresponding buttons can be disabled by using a `ForwardingPlayer` that removes `COMMAND_SEEK_BACK` and `COMMAND_SEEK_FORWARD` from the available commands. * Update `DefaultControlDispatcher` `getRewindIncrementMs` and @@ -147,6 +149,10 @@ ([#9106](https://github.com/google/ExoPlayer/issues/9106). * DRM: * Allow repeated provisioning in `DefaultDrmSession(Manager)`. + * Fix a crash due to `DefaultDrmSessionManager.release()` incorrectly + releasing too many keep-alive `DefaultDrmSession` references, resulting + in `DefaultDrmSession.release()` throwing an `IllegalStateException` + ([#9193](https://github.com/google/ExoPlayer/issues/9193)). * PlayerNotificationManager: * Add `PendingIntent.FLAG_IMMUTABLE` flag to BroadcastReceiver to support Android 12. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java index 3f9ab764c6..674e265739 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManager.java @@ -914,8 +914,10 @@ public class DefaultDrmSessionManager implements DrmSessionManager { @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. + if (newReferenceCount == 1 && prepareCallsCount > 0 && sessionKeepaliveMs != C.TIME_UNSET) { + // Only the internal keep-alive reference remains, so we can start the timeout. We only + // do this if the manager isn't released, because a released manager has already released + // all its internal session keep-alive references. keepaliveSessions.add(session); checkNotNull(playbackHandler) .postAtTime( diff --git a/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java index 0b9f560546..4dac826ce2 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/drm/DefaultDrmSessionManagerTest.java @@ -222,6 +222,44 @@ public class DefaultDrmSessionManagerTest { exoMediaDrm.release(); } + // https://github.com/google/ExoPlayer/issues/9193 + @Test(timeout = 10_000) + public void + managerReleasedBeforeSession_keepaliveEnabled_managerOnlyReleasesOneKeepaliveReference() + throws Exception { + FakeExoMediaDrm.LicenseServer licenseServer = + FakeExoMediaDrm.LicenseServer.allowingSchemeDatas(DRM_SCHEME_DATAS); + FakeExoMediaDrm exoMediaDrm = new FakeExoMediaDrm.Builder().build(); + 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)); + waitForOpenedWithKeys(drmSession); + + // Release the manager (there's still an explicit reference to the session from acquireSession). + // This should immediately release the manager's internal keepalive session reference. + drmSessionManager.release(); + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS); + + // Ensure the manager doesn't release a *second* keepalive session reference after the timer + // expires. + ShadowLooper.idleMainLooper(10, SECONDS); + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_OPENED_WITH_KEYS); + + // Release the explicit session reference. + drmSession.release(/* eventDispatcher= */ null); + assertThat(drmSession.getState()).isEqualTo(DrmSession.STATE_RELEASED); + } + @Test(timeout = 10_000) public void maxConcurrentSessionsExceeded_allKeepAliveSessionsEagerlyReleased() throws Exception { ImmutableList secondSchemeDatas =