Commit 47cc567d by aquilescanta Committed by Toni

Add reference counting to DrmSession

This CL should not introduce any functional changes.

PiperOrigin-RevId: 250277165
parent 6b68bc0c
...@@ -473,21 +473,13 @@ public class LibvpxVideoRenderer extends BaseRenderer { ...@@ -473,21 +473,13 @@ public class LibvpxVideoRenderer extends BaseRenderer {
} }
private void setSourceDrmSession(@Nullable DrmSession<ExoMediaCrypto> session) { private void setSourceDrmSession(@Nullable DrmSession<ExoMediaCrypto> session) {
DrmSession<ExoMediaCrypto> previous = sourceDrmSession; DrmSession.replaceSessionReferences(sourceDrmSession, session);
sourceDrmSession = session; sourceDrmSession = session;
releaseDrmSessionIfUnused(previous);
} }
private void setDecoderDrmSession(@Nullable DrmSession<ExoMediaCrypto> session) { private void setDecoderDrmSession(@Nullable DrmSession<ExoMediaCrypto> session) {
DrmSession<ExoMediaCrypto> previous = decoderDrmSession; DrmSession.replaceSessionReferences(decoderDrmSession, session);
decoderDrmSession = session; decoderDrmSession = session;
releaseDrmSessionIfUnused(previous);
}
private void releaseDrmSessionIfUnused(@Nullable DrmSession<ExoMediaCrypto> session) {
if (session != null && session != decoderDrmSession && session != sourceDrmSession) {
drmSessionManager.releaseSession(session);
}
} }
/** /**
...@@ -512,12 +504,10 @@ public class LibvpxVideoRenderer extends BaseRenderer { ...@@ -512,12 +504,10 @@ public class LibvpxVideoRenderer extends BaseRenderer {
} }
DrmSession<ExoMediaCrypto> session = DrmSession<ExoMediaCrypto> session =
drmSessionManager.acquireSession(Looper.myLooper(), newFormat.drmInitData); drmSessionManager.acquireSession(Looper.myLooper(), newFormat.drmInitData);
if (session == decoderDrmSession || session == sourceDrmSession) { if (sourceDrmSession != null) {
// We already had this session. The manager must be reference counting, so release it once sourceDrmSession.releaseReference();
// to get the count attributed to this renderer back down to 1.
drmSessionManager.releaseSession(session);
} }
setSourceDrmSession(session); sourceDrmSession = session;
} else { } else {
setSourceDrmSession(null); setSourceDrmSession(null);
} }
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.drm.DecryptionResource; import com.google.android.exoplayer2.drm.DrmSession;
/** /**
* Holds a {@link Format}. * Holds a {@link Format}.
...@@ -25,14 +25,14 @@ public final class FormatHolder { ...@@ -25,14 +25,14 @@ public final class FormatHolder {
/** /**
* Whether the object expected to populate {@link #format} is also expected to populate {@link * Whether the object expected to populate {@link #format} is also expected to populate {@link
* #decryptionResource}. * #drmSession}.
*/ */
// TODO: Remove once all Renderers and MediaSources have migrated to the new DRM model [Internal // TODO: Remove once all Renderers and MediaSources have migrated to the new DRM model [Internal
// ref: b/129764794]. // ref: b/129764794].
public boolean decryptionResourceIsProvided; public boolean decryptionResourceIsProvided;
/** An accompanying context for decrypting samples in the format. */ /** An accompanying context for decrypting samples in the format. */
@Nullable public DecryptionResource<?> decryptionResource; @Nullable public DrmSession<?> drmSession;
/** The held {@link Format}. */ /** The held {@link Format}. */
@Nullable public Format format; @Nullable public Format format;
......
...@@ -646,21 +646,13 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -646,21 +646,13 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
private void setSourceDrmSession(@Nullable DrmSession<ExoMediaCrypto> session) { private void setSourceDrmSession(@Nullable DrmSession<ExoMediaCrypto> session) {
DrmSession<ExoMediaCrypto> previous = sourceDrmSession; DrmSession.replaceSessionReferences(sourceDrmSession, session);
sourceDrmSession = session; sourceDrmSession = session;
releaseDrmSessionIfUnused(previous);
} }
private void setDecoderDrmSession(@Nullable DrmSession<ExoMediaCrypto> session) { private void setDecoderDrmSession(@Nullable DrmSession<ExoMediaCrypto> session) {
DrmSession<ExoMediaCrypto> previous = decoderDrmSession; DrmSession.replaceSessionReferences(decoderDrmSession, session);
decoderDrmSession = session; decoderDrmSession = session;
releaseDrmSessionIfUnused(previous);
}
private void releaseDrmSessionIfUnused(@Nullable DrmSession<ExoMediaCrypto> session) {
if (session != null && session != decoderDrmSession && session != sourceDrmSession) {
drmSessionManager.releaseSession(session);
}
} }
private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException { private void onInputFormatChanged(Format newFormat) throws ExoPlaybackException {
...@@ -677,12 +669,10 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements ...@@ -677,12 +669,10 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
} }
DrmSession<ExoMediaCrypto> session = DrmSession<ExoMediaCrypto> session =
drmSessionManager.acquireSession(Looper.myLooper(), newFormat.drmInitData); drmSessionManager.acquireSession(Looper.myLooper(), newFormat.drmInitData);
if (session == decoderDrmSession || session == sourceDrmSession) { if (sourceDrmSession != null) {
// We already had this session. The manager must be reference counting, so release it once sourceDrmSession.releaseReference();
// to get the count attributed to this renderer back down to 1.
drmSessionManager.releaseSession(session);
} }
setSourceDrmSession(session); sourceDrmSession = session;
} else { } else {
setSourceDrmSession(null); setSourceDrmSession(null);
} }
......
...@@ -38,6 +38,7 @@ import java.util.HashMap; ...@@ -38,6 +38,7 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf; import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull; import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...@@ -103,12 +104,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -103,12 +104,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/* package */ final PostResponseHandler postResponseHandler; /* package */ final PostResponseHandler postResponseHandler;
private @DrmSession.State int state; private @DrmSession.State int state;
private int openCount; private int referenceCount;
@Nullable private HandlerThread requestHandlerThread; @Nullable private HandlerThread requestHandlerThread;
@Nullable private PostRequestHandler postRequestHandler; @Nullable private PostRequestHandler postRequestHandler;
@Nullable private T mediaCrypto; @Nullable private T mediaCrypto;
@Nullable private DrmSessionException lastException; @Nullable private DrmSessionException lastException;
private byte @MonotonicNonNull [] sessionId; private byte @NullableType [] sessionId;
private byte @MonotonicNonNull [] offlineLicenseKeySetId; private byte @MonotonicNonNull [] offlineLicenseKeySetId;
@Nullable private KeyRequest currentKeyRequest; @Nullable private KeyRequest currentKeyRequest;
...@@ -169,42 +170,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -169,42 +170,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
postResponseHandler = new PostResponseHandler(playbackLooper); postResponseHandler = new PostResponseHandler(playbackLooper);
} }
// Life cycle.
public void acquire() {
if (++openCount == 1) {
requestHandlerThread = new HandlerThread("DrmRequestHandler");
requestHandlerThread.start();
postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper());
if (openInternal(true)) {
doLicense(true);
}
}
}
@SuppressWarnings("assignment.type.incompatible")
public void release() {
if (--openCount == 0) {
// Assigning null to various non-null variables for clean-up.
state = STATE_RELEASED;
postResponseHandler.removeCallbacksAndMessages(null);
Util.castNonNull(postRequestHandler).removeCallbacksAndMessages(null);
postRequestHandler = null;
Util.castNonNull(requestHandlerThread).quit();
requestHandlerThread = null;
mediaCrypto = null;
lastException = null;
currentKeyRequest = null;
currentProvisionRequest = null;
if (sessionId != null) {
mediaDrm.closeSession(sessionId);
sessionId = null;
eventDispatcher.dispatch(DefaultDrmSessionEventListener::onDrmSessionReleased);
}
releaseCallback.onSessionReleased(this);
}
}
public boolean hasSessionId(byte[] sessionId) { public boolean hasSessionId(byte[] sessionId) {
return Arrays.equals(this.sessionId, sessionId); return Arrays.equals(this.sessionId, sessionId);
} }
...@@ -270,6 +235,42 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -270,6 +235,42 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return offlineLicenseKeySetId; return offlineLicenseKeySetId;
} }
@Override
public void acquireReference() {
if (++referenceCount == 1) {
Assertions.checkState(state == STATE_OPENING);
requestHandlerThread = new HandlerThread("DrmRequestHandler");
requestHandlerThread.start();
postRequestHandler = new PostRequestHandler(requestHandlerThread.getLooper());
if (openInternal(true)) {
doLicense(true);
}
}
}
@Override
public void releaseReference() {
if (--referenceCount == 0) {
// Assigning null to various non-null variables for clean-up.
state = STATE_RELEASED;
Util.castNonNull(postResponseHandler).removeCallbacksAndMessages(null);
Util.castNonNull(postRequestHandler).removeCallbacksAndMessages(null);
postRequestHandler = null;
Util.castNonNull(requestHandlerThread).quit();
requestHandlerThread = null;
mediaCrypto = null;
lastException = null;
currentKeyRequest = null;
currentProvisionRequest = null;
if (sessionId != null) {
mediaDrm.closeSession(sessionId);
sessionId = null;
eventDispatcher.dispatch(DefaultDrmSessionEventListener::onDrmSessionReleased);
}
releaseCallback.onSessionReleased(this);
}
}
// Internal methods. // Internal methods.
/** /**
...@@ -288,9 +289,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -288,9 +289,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
try { try {
sessionId = mediaDrm.openSession(); sessionId = mediaDrm.openSession();
eventDispatcher.dispatch(DefaultDrmSessionEventListener::onDrmSessionAcquired);
mediaCrypto = mediaDrm.createMediaCrypto(sessionId); mediaCrypto = mediaDrm.createMediaCrypto(sessionId);
eventDispatcher.dispatch(DefaultDrmSessionEventListener::onDrmSessionAcquired);
state = STATE_OPENED; state = STATE_OPENED;
Assertions.checkNotNull(sessionId);
return true; return true;
} catch (NotProvisionedException e) { } catch (NotProvisionedException e) {
if (allowProvisioning) { if (allowProvisioning) {
...@@ -329,6 +331,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -329,6 +331,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@RequiresNonNull("sessionId") @RequiresNonNull("sessionId")
private void doLicense(boolean allowRetry) { private void doLicense(boolean allowRetry) {
byte[] sessionId = Util.castNonNull(this.sessionId);
switch (mode) { switch (mode) {
case DefaultDrmSessionManager.MODE_PLAYBACK: case DefaultDrmSessionManager.MODE_PLAYBACK:
case DefaultDrmSessionManager.MODE_QUERY: case DefaultDrmSessionManager.MODE_QUERY:
...@@ -364,6 +367,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -364,6 +367,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
break; break;
case DefaultDrmSessionManager.MODE_RELEASE: case DefaultDrmSessionManager.MODE_RELEASE:
Assertions.checkNotNull(offlineLicenseKeySetId); Assertions.checkNotNull(offlineLicenseKeySetId);
Assertions.checkNotNull(this.sessionId);
// It's not necessary to restore the key (and open a session to do that) before releasing it // It's not necessary to restore the key (and open a session to do that) before releasing it
// but this serves as a good sanity/fast-failure check. // but this serves as a good sanity/fast-failure check.
if (restoreKeys()) { if (restoreKeys()) {
......
...@@ -432,19 +432,10 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto> ...@@ -432,19 +432,10 @@ public class DefaultDrmSessionManager<T extends ExoMediaCrypto>
initialDrmRequestRetryCount); initialDrmRequestRetryCount);
sessions.add(session); sessions.add(session);
} }
session.acquire(); session.acquireReference();
return session; return session;
} }
@Override
public void releaseSession(DrmSession<T> session) {
if (session instanceof ErrorStateDrmSession) {
// Do nothing.
return;
}
((DefaultDrmSession<T>) session).release();
}
// ProvisioningManager implementation. // ProvisioningManager implementation.
@Override @Override
......
...@@ -29,6 +29,20 @@ import java.util.Map; ...@@ -29,6 +29,20 @@ import java.util.Map;
public interface DrmSession<T extends ExoMediaCrypto> { public interface DrmSession<T extends ExoMediaCrypto> {
/** /**
* Invokes {@code newSession's} {@link #acquireReference()} and {@code previousSession's} {@link
* #releaseReference()} in that order. Does nothing for passed null values.
*/
static <T extends ExoMediaCrypto> void replaceSessionReferences(
@Nullable DrmSession<T> previousSession, @Nullable DrmSession<T> newSession) {
if (newSession != null) {
newSession.acquireReference();
}
if (previousSession != null) {
previousSession.releaseReference();
}
}
/**
* Wraps the throwable which is the cause of the error state. * Wraps the throwable which is the cause of the error state.
*/ */
class DrmSessionException extends Exception { class DrmSessionException extends Exception {
...@@ -110,4 +124,18 @@ public interface DrmSession<T extends ExoMediaCrypto> { ...@@ -110,4 +124,18 @@ public interface DrmSession<T extends ExoMediaCrypto> {
*/ */
@Nullable @Nullable
byte[] getOfflineLicenseKeySetId(); byte[] getOfflineLicenseKeySetId();
/**
* Increments the reference count for this session. A non-zero reference count session will keep
* any acquired resources.
*/
void acquireReference();
/**
* Decreases by one the reference count for this session. A session that reaches a zero reference
* count will release any resources it holds.
*
* <p>The session must not be used after its reference count has been reduced to 0.
*/
void releaseReference();
} }
...@@ -34,8 +34,10 @@ public interface DrmSessionManager<T extends ExoMediaCrypto> { ...@@ -34,8 +34,10 @@ public interface DrmSessionManager<T extends ExoMediaCrypto> {
boolean canAcquireSession(DrmInitData drmInitData); boolean canAcquireSession(DrmInitData drmInitData);
/** /**
* Acquires a {@link DrmSession} for the specified {@link DrmInitData}. The {@link DrmSession} * Returns a {@link DrmSession} with an acquired reference for the specified {@link DrmInitData}.
* must be returned to {@link #releaseSession(DrmSession)} when it is no longer required. *
* <p>The caller must call {@link DrmSession#releaseReference} to decrement the session's
* reference count when the session is no longer required.
* *
* @param playbackLooper The looper associated with the media playback thread. * @param playbackLooper The looper associated with the media playback thread.
* @param drmInitData DRM initialization data. All contained {@link SchemeData}s must contain * @param drmInitData DRM initialization data. All contained {@link SchemeData}s must contain
...@@ -43,10 +45,4 @@ public interface DrmSessionManager<T extends ExoMediaCrypto> { ...@@ -43,10 +45,4 @@ public interface DrmSessionManager<T extends ExoMediaCrypto> {
* @return The DRM session. * @return The DRM session.
*/ */
DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitData); DrmSession<T> acquireSession(Looper playbackLooper, DrmInitData drmInitData);
/**
* Releases a {@link DrmSession}.
*/
void releaseSession(DrmSession<T> drmSession);
} }
...@@ -57,4 +57,13 @@ public final class ErrorStateDrmSession<T extends ExoMediaCrypto> implements Drm ...@@ -57,4 +57,13 @@ public final class ErrorStateDrmSession<T extends ExoMediaCrypto> implements Drm
return null; return null;
} }
@Override
public void acquireReference() {
// Do nothing.
}
@Override
public void releaseReference() {
// Do nothing.
}
} }
...@@ -235,7 +235,7 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> { ...@@ -235,7 +235,7 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
DrmSessionException error = drmSession.getError(); DrmSessionException error = drmSession.getError();
Pair<Long, Long> licenseDurationRemainingSec = Pair<Long, Long> licenseDurationRemainingSec =
WidevineUtil.getLicenseDurationRemainingSec(drmSession); WidevineUtil.getLicenseDurationRemainingSec(drmSession);
drmSessionManager.releaseSession(drmSession); drmSession.releaseReference();
if (error != null) { if (error != null) {
if (error.getCause() instanceof KeysExpiredException) { if (error.getCause() instanceof KeysExpiredException) {
return Pair.create(0L, 0L); return Pair.create(0L, 0L);
...@@ -259,7 +259,7 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> { ...@@ -259,7 +259,7 @@ public final class OfflineLicenseHelper<T extends ExoMediaCrypto> {
drmInitData); drmInitData);
DrmSessionException error = drmSession.getError(); DrmSessionException error = drmSession.getError();
byte[] keySetId = drmSession.getOfflineLicenseKeySetId(); byte[] keySetId = drmSession.getOfflineLicenseKeySetId();
drmSessionManager.releaseSession(drmSession); drmSession.releaseReference();
if (error != null) { if (error != null) {
throw error; throw error;
} }
......
...@@ -941,21 +941,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -941,21 +941,13 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
private void setSourceDrmSession(@Nullable DrmSession<FrameworkMediaCrypto> session) { private void setSourceDrmSession(@Nullable DrmSession<FrameworkMediaCrypto> session) {
DrmSession<FrameworkMediaCrypto> previous = sourceDrmSession; DrmSession.replaceSessionReferences(sourceDrmSession, session);
sourceDrmSession = session; sourceDrmSession = session;
releaseDrmSessionIfUnused(previous);
} }
private void setCodecDrmSession(@Nullable DrmSession<FrameworkMediaCrypto> session) { private void setCodecDrmSession(@Nullable DrmSession<FrameworkMediaCrypto> session) {
DrmSession<FrameworkMediaCrypto> previous = codecDrmSession; DrmSession.replaceSessionReferences(codecDrmSession, session);
codecDrmSession = session; codecDrmSession = session;
releaseDrmSessionIfUnused(previous);
}
private void releaseDrmSessionIfUnused(@Nullable DrmSession<FrameworkMediaCrypto> session) {
if (session != null && session != sourceDrmSession && session != codecDrmSession) {
drmSessionManager.releaseSession(session);
}
} }
/** /**
...@@ -1159,12 +1151,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer { ...@@ -1159,12 +1151,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
} }
DrmSession<FrameworkMediaCrypto> session = DrmSession<FrameworkMediaCrypto> session =
drmSessionManager.acquireSession(Looper.myLooper(), newFormat.drmInitData); drmSessionManager.acquireSession(Looper.myLooper(), newFormat.drmInitData);
if (session == sourceDrmSession || session == codecDrmSession) { if (sourceDrmSession != null) {
// We already had this session. The manager must be reference counting, so release it once sourceDrmSession.releaseReference();
// to get the count attributed to this renderer back down to 1.
drmSessionManager.releaseSession(session);
} }
setSourceDrmSession(session); sourceDrmSession = session;
} else { } else {
setSourceDrmSession(null); setSourceDrmSession(null);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment