diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManager.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManager.java index 63c69282a79..b76ac032437 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManager.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManager.java @@ -85,6 +85,7 @@ public static final class Builder { private boolean playClearSamplesWithoutKeys; private LoadErrorHandlingPolicy loadErrorHandlingPolicy; private long sessionKeepaliveMs; + private boolean freeKeepAliveSessionsOnRelease; /** * Creates a builder with default values. The default values are: @@ -111,6 +112,7 @@ public Builder() { playClearSamplesWithoutKeys = true; loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy(); sessionKeepaliveMs = DEFAULT_SESSION_KEEPALIVE_MS; + freeKeepAliveSessionsOnRelease = true; } /** @@ -236,6 +238,16 @@ public Builder setSessionKeepaliveMs(long sessionKeepaliveMs) { return this; } + /** + * Sets the flag to enable {@link DrmSession DrmSessions} caching. + * + *

It is useful to keep sessions around during quick channel changes. + */ + public Builder setFreeKeepAliveSessionsOnRelease(boolean enable) { + this.freeKeepAliveSessionsOnRelease = enable; + return this; + } + /** Builds a {@link DefaultDrmSessionManager} instance. */ public DefaultDrmSessionManager build(MediaDrmCallback mediaDrmCallback) { return new DefaultDrmSessionManager( @@ -247,7 +259,8 @@ public DefaultDrmSessionManager build(MediaDrmCallback mediaDrmCallback) { useDrmSessionsForClearContentTrackTypes, playClearSamplesWithoutKeys, loadErrorHandlingPolicy, - sessionKeepaliveMs); + sessionKeepaliveMs, + freeKeepAliveSessionsOnRelease); } } @@ -312,6 +325,8 @@ private MissingSchemeDataException(UUID uuid) { private final LoadErrorHandlingPolicy loadErrorHandlingPolicy; private final ReferenceCountListenerImpl referenceCountListener; private final long sessionKeepaliveMs; + private boolean freeKeepAliveSessionsOnRelease; + private boolean isFinalRelease; private final List sessions; private final Set preacquiredSessionReferences; @@ -339,6 +354,31 @@ private DefaultDrmSessionManager( boolean playClearSamplesWithoutKeys, LoadErrorHandlingPolicy loadErrorHandlingPolicy, long sessionKeepaliveMs) { + + this( + uuid, + exoMediaDrmProvider, + callback, + keyRequestParameters, + multiSession, + useDrmSessionsForClearContentTrackTypes, + playClearSamplesWithoutKeys, + loadErrorHandlingPolicy, + sessionKeepaliveMs, + true); + } + + private DefaultDrmSessionManager( + UUID uuid, + ExoMediaDrm.Provider exoMediaDrmProvider, + MediaDrmCallback callback, + HashMap keyRequestParameters, + boolean multiSession, + int[] useDrmSessionsForClearContentTrackTypes, + boolean playClearSamplesWithoutKeys, + LoadErrorHandlingPolicy loadErrorHandlingPolicy, + long sessionKeepaliveMs, + boolean freeKeepAliveSessionsOnRelease) { checkNotNull(uuid); checkArgument(!C.COMMON_PSSH_UUID.equals(uuid), "Use C.CLEARKEY_UUID instead"); this.uuid = uuid; @@ -356,6 +396,7 @@ private DefaultDrmSessionManager( preacquiredSessionReferences = Sets.newIdentityHashSet(); keepaliveSessions = Sets.newIdentityHashSet(); this.sessionKeepaliveMs = sessionKeepaliveMs; + this.freeKeepAliveSessionsOnRelease = freeKeepAliveSessionsOnRelease; } /** @@ -413,9 +454,29 @@ public final void prepare() { @Override public final void release() { verifyPlaybackThread(/* allowBeforeSetPlayer= */ true); - if (--prepareCallsCount != 0) { - return; + if (freeKeepAliveSessionsOnRelease) { + if (--prepareCallsCount != 0) { + return; + } + releaseKeepAliveSessionsIfEnabled(); + } else if (isFinalRelease) { + + while (!sessions.isEmpty() || !keepaliveSessions.isEmpty()) { + // Release all keepalive acquisitions if keepalive is enabled. + releaseKeepAliveSessionsIfEnabled(); + } + prepareCallsCount = 0; } + releaseAllPreacquiredSessions(); + + maybeReleaseMediaDrm(); + + if (isFinalRelease) { + assert exoMediaDrm == null; + } + } + + private void releaseKeepAliveSessionsIfEnabled() { // Release all keepalive acquisitions if keepalive is enabled. if (sessionKeepaliveMs != C.TIME_UNSET) { // Make a local copy, because sessions are removed from this.sessions during release (via @@ -425,9 +486,11 @@ public final void release() { sessions.get(i).release(/* eventDispatcher= */ null); } } - releaseAllPreacquiredSessions(); + } - maybeReleaseMediaDrm(); + /** Releases all the sessions. This is called from onStop() */ + public void releaseAllSessions() { + isFinalRelease = true; } @Override @@ -631,6 +694,7 @@ private DefaultDrmSession createAndAcquireSessionWithRetry( // 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()) { + Log.w(TAG, "aquire resource shortage and keepaliveSession size: " + keepaliveSessions.size()); releaseAllKeepaliveSessions(); undoAcquisition(session, eventDispatcher); session = createAndAcquireSession(schemeDatas, isPlaceholderSession, eventDispatcher); @@ -642,6 +706,11 @@ private DefaultDrmSession createAndAcquireSessionWithRetry( if (acquisitionFailedIndicatingResourceShortage(session) && shouldReleasePreacquiredSessionsBeforeRetrying && !preacquiredSessionReferences.isEmpty()) { + Log.w( + TAG, + "aquire resource shortage and preacquiredSessionReferences size: " + + preacquiredSessionReferences.size()); + releaseAllPreacquiredSessions(); if (!keepaliveSessions.isEmpty()) { // Some preacquired sessions released above are now in their keepalive timeout phase. We @@ -877,7 +946,12 @@ private class ReferenceCountListenerImpl implements DefaultDrmSession.ReferenceC public void onReferenceCountIncremented(DefaultDrmSession session, int newReferenceCount) { if (sessionKeepaliveMs != C.TIME_UNSET) { // The session has been acquired elsewhere so we want to cancel our timeout. - keepaliveSessions.remove(session); + boolean removed = keepaliveSessions.remove(session); + if (removed) { + Log.d( + TAG, + "Using cached session, ref count: " + newReferenceCount + ", session: " + session); + } checkNotNull(playbackHandler).removeCallbacksAndMessages(session); } } @@ -891,7 +965,10 @@ public void onReferenceCountDecremented(DefaultDrmSession session, int newRefere keepaliveSessions.add(session); checkNotNull(playbackHandler) .postAtTime( - () -> session.release(/* eventDispatcher= */ null), + () -> { + Log.d(TAG, "keepAlive expired for session: " + session); + session.release(/* eventDispatcher= */ null); + }, session, /* uptimeMillis= */ SystemClock.uptimeMillis() + sessionKeepaliveMs); } else if (newReferenceCount == 0) { @@ -903,6 +980,14 @@ public void onReferenceCountDecremented(DefaultDrmSession session, int newRefere if (noMultiSessionDrmSession == session) { noMultiSessionDrmSession = null; } + ImmutableSet references = + ImmutableSet.copyOf(preacquiredSessionReferences); + for (PreacquiredSessionReference reference : references) { + if (reference.session == session) { + reference.isReleased = true; + preacquiredSessionReferences.remove(reference); + } + } provisioningManagerImpl.onSessionFullyReleased(session); if (sessionKeepaliveMs != C.TIME_UNSET) { checkNotNull(playbackHandler).removeCallbacksAndMessages(session); diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManagerProvider.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManagerProvider.java index ad6ad520b3b..e2cbd1c297f 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManagerProvider.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DefaultDrmSessionManagerProvider.java @@ -45,6 +45,7 @@ public final class DefaultDrmSessionManagerProvider implements DrmSessionManager @Nullable private DataSource.Factory drmHttpDataSourceFactory; @Nullable private String userAgent; @Nullable private LoadErrorHandlingPolicy drmLoadErrorHandlingPolicy; + private boolean freeKeepAliveSessionsOnRelease; public DefaultDrmSessionManagerProvider() { lock = new Object(); @@ -83,6 +84,22 @@ public void setDrmLoadErrorHandlingPolicy(LoadErrorHandlingPolicy drmLoadErrorHa this.drmLoadErrorHandlingPolicy = drmLoadErrorHandlingPolicy; } + /** + * Set the flag to indicate {@link DefaultDrmSessionManager} to cache the DrmSessions + * + * @param enable - true means caching ie enabled, false is the default behaviour + */ + public void setFreeKeepAliveSessionsOnRelease(boolean enable) { + this.freeKeepAliveSessionsOnRelease = enable; + } + + /** Releases all the cached sessions in {@link DrmSessionManager} */ + public void releaseDrmSession() { + if (this.manager != null) { + manager.releaseAllSessions(); + } + } + @Override public DrmSessionManager get(MediaItem mediaItem) { checkNotNull(mediaItem.localConfiguration); @@ -95,6 +112,9 @@ public DrmSessionManager get(MediaItem mediaItem) { synchronized (lock) { if (!Util.areEqual(drmConfiguration, this.drmConfiguration)) { this.drmConfiguration = drmConfiguration; + if (manager != null) { + manager.releaseAllSessions(); + } this.manager = createManager(drmConfiguration); } return checkNotNull(this.manager); diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmSessionManager.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmSessionManager.java index 6880f97d47d..1fd85d9431b 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmSessionManager.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/drm/DrmSessionManager.java @@ -94,6 +94,14 @@ default void release() { */ void setPlayer(Looper playbackLooper, PlayerId playerId); + /** + * Releases all the aquired resources if default is set to preserve keepAlive on {@link + * #release()}. This is called from the Activity's onStop() + */ + default void releaseAllSessions() { + // Do nothing. + } + /** * Pre-acquires a DRM session for the specified {@link Format}. *