Remove DrmSessionManager.acquirePlaceholderSession

In order to acquire a placeholder session, clients can call acquireSession
with a format with a null drmInitData.

PiperOrigin-RevId: 327220249
This commit is contained in:
aquilescanta 2020-08-18 15:00:45 +01:00 committed by Oliver Woodman
parent be98509e03
commit 41e6577dca
6 changed files with 108 additions and 99 deletions

View File

@ -409,7 +409,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
/**
* Sets the mode, which determines the role of sessions acquired from the instance. This must be
* called before {@link #acquireSession(Looper, DrmSessionEventListener.EventDispatcher, Format)}
* or {@link #acquirePlaceholderSession} is called.
* is called.
*
* <p>By default, the mode is {@link #MODE_PLAYBACK} and a streaming license is requested when
* required.
@ -469,34 +469,6 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
@Override
@Nullable
public DrmSession acquirePlaceholderSession(Looper playbackLooper, int trackType) {
initPlaybackLooper(playbackLooper);
ExoMediaDrm exoMediaDrm = Assertions.checkNotNull(this.exoMediaDrm);
boolean avoidPlaceholderDrmSessions =
FrameworkMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType())
&& FrameworkMediaCrypto.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC;
// Avoid attaching a session to sparse formats.
if (avoidPlaceholderDrmSessions
|| Util.linearSearch(useDrmSessionsForClearContentTrackTypes, trackType) == C.INDEX_UNSET
|| exoMediaDrm.getExoMediaCryptoType() == null) {
return null;
}
maybeCreateMediaDrmHandler(playbackLooper);
if (placeholderDrmSession == null) {
DefaultDrmSession placeholderDrmSession =
createAndAcquireSessionWithRetry(
/* schemeDatas= */ ImmutableList.of(),
/* isPlaceholderSession= */ true,
/* eventDispatcher= */ null);
sessions.add(placeholderDrmSession);
this.placeholderDrmSession = placeholderDrmSession;
} else {
placeholderDrmSession.acquire(/* eventDispatcher= */ null);
}
return placeholderDrmSession;
}
@Override
public DrmSession acquireSession(
Looper playbackLooper,
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
@ -504,6 +476,11 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
initPlaybackLooper(playbackLooper);
maybeCreateMediaDrmHandler(playbackLooper);
if (format.drmInitData == null) {
// Content is not encrypted.
return maybeAcquirePlaceholderSession(MimeTypes.getTrackType(format.sampleMimeType));
}
@Nullable List<SchemeData> schemeDatas = null;
if (offlineLicenseKeySetId == null) {
schemeDatas = getSchemeDatas(Assertions.checkNotNull(format.drmInitData), uuid, false);
@ -565,6 +542,32 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
// Internal methods.
@Nullable
private DrmSession maybeAcquirePlaceholderSession(int trackType) {
ExoMediaDrm exoMediaDrm = Assertions.checkNotNull(this.exoMediaDrm);
boolean avoidPlaceholderDrmSessions =
FrameworkMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType())
&& FrameworkMediaCrypto.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC;
// Avoid attaching a session to sparse formats.
if (avoidPlaceholderDrmSessions
|| Util.linearSearch(useDrmSessionsForClearContentTrackTypes, trackType) == C.INDEX_UNSET
|| exoMediaDrm.getExoMediaCryptoType() == null) {
return null;
}
if (placeholderDrmSession == null) {
DefaultDrmSession placeholderDrmSession =
createAndAcquireSessionWithRetry(
/* schemeDatas= */ ImmutableList.of(),
/* isPlaceholderSession= */ true,
/* eventDispatcher= */ null);
sessions.add(placeholderDrmSession);
this.placeholderDrmSession = placeholderDrmSession;
} else {
placeholderDrmSession.acquire(/* eventDispatcher= */ null);
}
return placeholderDrmSession;
}
private boolean canAcquireSession(DrmInitData drmInitData) {
if (offlineLicenseKeySetId != null) {
// An offline license can be restored so a session can always be acquired.

View File

@ -17,7 +17,6 @@ package com.google.android.exoplayer2.drm;
import android.os.Looper;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
/** Manages a DRM session. */
@ -33,13 +32,19 @@ public interface DrmSessionManager {
new DrmSessionManager() {
@Override
@Nullable
public DrmSession acquireSession(
Looper playbackLooper,
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
Format format) {
return new ErrorStateDrmSession(
new DrmSession.DrmSessionException(
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME)));
if (format.drmInitData == null) {
return null;
} else {
return new ErrorStateDrmSession(
new DrmSession.DrmSessionException(
new UnsupportedDrmException(
UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME)));
}
}
@Override
@ -64,39 +69,27 @@ public interface DrmSessionManager {
// Do nothing.
}
/**
* Returns a {@link DrmSession} that does not execute key requests, with an incremented reference
* count. When the caller no longer needs to use the instance, it must call {@link
* DrmSession#release(DrmSessionEventListener.EventDispatcher)} to decrement the reference count.
*
* <p>Placeholder {@link DrmSession DrmSessions} may be used to configure secure decoders for
* playback of clear content periods. This can reduce the cost of transitioning between clear and
* encrypted content periods.
*
* @param playbackLooper The looper associated with the media playback thread.
* @param trackType The type of the track to acquire a placeholder session for. Must be one of the
* {@link C}{@code .TRACK_TYPE_*} constants.
* @return The placeholder DRM session, or null if this DRM session manager does not support
* placeholder sessions.
*/
@Nullable
default DrmSession acquirePlaceholderSession(Looper playbackLooper, int trackType) {
return null;
}
/**
* Returns a {@link DrmSession} for the specified {@link Format}, with an incremented reference
* count. When the caller no longer needs to use the instance, it must call {@link
* count. May return null if the {@link Format#drmInitData} is null and the DRM session manager is
* not configured to attach a {@link DrmSession} to clear content. When the caller no longer needs
* to use a returned {@link DrmSession}, it must call {@link
* DrmSession#release(DrmSessionEventListener.EventDispatcher)} to decrement the reference count.
*
* <p>If the provided {@link Format} contains a null {@link Format#drmInitData}, the returned
* {@link DrmSession} (if not null) will be a placeholder session which does not execute key
* requests, and cannot be used to handle encrypted content. However, a placeholder session may be
* used to configure secure decoders for playback of clear content periods, which can reduce the
* cost of transitioning between clear and encrypted content.
*
* @param playbackLooper The looper associated with the media playback thread.
* @param eventDispatcher The {@link DrmSessionEventListener.EventDispatcher} used to distribute
* events, and passed on to {@link
* DrmSession#acquire(DrmSessionEventListener.EventDispatcher)}.
* @param format The {@link Format} for which to acquire a {@link DrmSession}. Must contain a
* non-null {@link Format#drmInitData}.
* @return The DRM session.
* @param format The {@link Format} for which to acquire a {@link DrmSession}.
* @return The DRM session. May be null if the given {@link Format#drmInitData} is null.
*/
@Nullable
DrmSession acquireSession(
Looper playbackLooper,
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
@ -105,16 +98,16 @@ public interface DrmSessionManager {
/**
* Returns the {@link ExoMediaCrypto} type associated to sessions acquired for the given {@link
* Format}. Returns the {@link UnsupportedMediaCrypto} type if this DRM session manager does not
* support any of the DRM schemes defined in the given {@link Format}. If the {@link Format}
* describes unencrypted content, returns an {@link ExoMediaCrypto} type if this DRM session
* manager would associate a {@link #acquirePlaceholderSession placeholder session} to the given
* {@link Format}, or null otherwise.
* support any of the DRM schemes defined in the given {@link Format}. Returns null if {@link
* Format#drmInitData} is null and {@link #acquireSession} would return null for the given {@link
* Format}.
*
* @param format The {@link Format} for which to return the {@link ExoMediaCrypto} type.
* @return The {@link ExoMediaCrypto} type associated to sessions acquired using the given
* parameters, or the {@link UnsupportedMediaCrypto} type if the provided {@code drmInitData}
* is not supported, or {@code null} if {@code drmInitData} is null and no DRM session will be
* associated to the given {@code trackType}.
* @return The {@link ExoMediaCrypto} type associated to sessions acquired using the given {@link
* Format}, or {@link UnsupportedMediaCrypto} if this DRM session manager does not support any
* of the DRM schemes defined in the given {@link Format}. May be null if {@link
* Format#drmInitData} is null and {@link #acquireSession} would return null for the given
* {@link Format}.
*/
@Nullable
Class<? extends ExoMediaCrypto> getExoMediaCryptoType(Format format);

View File

@ -184,7 +184,8 @@ public final class OfflineLicenseHelper {
/**
* Downloads an offline license.
*
* @param format The {@link Format} of the content whose license is to be downloaded.
* @param format The {@link Format} of the content whose license is to be downloaded. Must contain
* a non-null {@link Format#drmInitData}.
* @return The key set id for the downloaded license.
* @throws DrmSessionException Thrown when a DRM session error occurs.
*/
@ -278,13 +279,14 @@ public final class OfflineLicenseHelper {
private DrmSession openBlockingKeyRequest(
@Mode int licenseMode, @Nullable byte[] offlineLicenseKeySetId, Format format) {
Assertions.checkNotNull(format.drmInitData);
drmSessionManager.setMode(licenseMode, offlineLicenseKeySetId);
conditionVariable.close();
DrmSession drmSession =
drmSessionManager.acquireSession(handlerThread.getLooper(), eventDispatcher, format);
// Block current thread until key loading is finished
conditionVariable.block();
return drmSession;
return Assertions.checkNotNull(drmSession);
}
}

View File

@ -327,13 +327,7 @@ public class SampleQueue implements TrackOutput {
* Attempts to read from the queue.
*
* <p>{@link Format Formats} read from this method may be associated to a {@link DrmSession}
* through {@link FormatHolder#drmSession}, which is populated in two scenarios:
*
* <ul>
* <li>The {@link Format} has a non-null {@link Format#drmInitData}.
* <li>The {@link DrmSessionManager} provides placeholder sessions for this queue's track type.
* See {@link DrmSessionManager#acquirePlaceholderSession(Looper, int)}.
* </ul>
* through {@link FormatHolder#drmSession}.
*
* @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
* @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
@ -842,10 +836,7 @@ public class SampleQueue implements TrackOutput {
// is being used for both DrmInitData.
@Nullable DrmSession previousSession = currentDrmSession;
currentDrmSession =
newDrmInitData != null
? drmSessionManager.acquireSession(playbackLooper, drmEventDispatcher, newFormat)
: drmSessionManager.acquirePlaceholderSession(
playbackLooper, MimeTypes.getTrackType(newFormat.sampleMimeType));
drmSessionManager.acquireSession(playbackLooper, drmEventDispatcher, newFormat);
outputFormatHolder.drmSession = currentDrmSession;
if (previousSession != null) {

View File

@ -38,6 +38,7 @@ import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.drm.DrmSession;
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.upstream.Allocator;
@ -54,7 +55,6 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
/** Test for {@link SampleQueue}. */
@ -69,6 +69,8 @@ public final class SampleQueueTest {
private static final Format FORMAT_SPLICED = buildFormat(/* id= */ "spliced");
private static final Format FORMAT_ENCRYPTED =
new Format.Builder().setId(/* id= */ "encrypted").setDrmInitData(new DrmInitData()).build();
private static final Format FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE =
FORMAT_ENCRYPTED.copyWithExoMediaCryptoType(MockExoMediaCrypto.class);
private static final byte[] DATA = TestUtil.buildTestData(ALLOCATION_SIZE * 10);
/*
@ -128,7 +130,7 @@ public final class SampleQueueTest {
new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, new byte[16], 0, 0);
private Allocator allocator;
private DrmSessionManager mockDrmSessionManager;
private MockDrmSessionManager mockDrmSessionManager;
private DrmSession mockDrmSession;
private DrmSessionEventListener.EventDispatcher eventDispatcher;
private SampleQueue sampleQueue;
@ -138,11 +140,8 @@ public final class SampleQueueTest {
@Before
public void setUp() {
allocator = new DefaultAllocator(false, ALLOCATION_SIZE);
mockDrmSessionManager = Mockito.mock(DrmSessionManager.class);
mockDrmSession = Mockito.mock(DrmSession.class);
when(mockDrmSessionManager.acquireSession(
ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any()))
.thenReturn(mockDrmSession);
mockDrmSessionManager = new MockDrmSessionManager(mockDrmSession);
eventDispatcher = new DrmSessionEventListener.EventDispatcher();
sampleQueue =
new SampleQueue(
@ -399,7 +398,7 @@ public final class SampleQueueTest {
@Test
public void isReadyReturnsTrueForValidDrmSession() {
writeTestDataWithEncryptedSections();
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE);
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isFalse();
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isTrue();
@ -424,7 +423,7 @@ public final class SampleQueueTest {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
writeTestDataWithEncryptedSections();
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE);
assertReadNothing(/* formatRequired= */ false);
assertThat(inputBuffer.waitingForKeys).isTrue();
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
@ -464,9 +463,7 @@ public final class SampleQueueTest {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
DrmSession mockPlaceholderDrmSession = Mockito.mock(DrmSession.class);
when(mockPlaceholderDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
when(mockDrmSessionManager.acquirePlaceholderSession(
ArgumentMatchers.any(), ArgumentMatchers.anyInt()))
.thenReturn(mockPlaceholderDrmSession);
mockDrmSessionManager.mockPlaceholderDrmSession = mockPlaceholderDrmSession;
writeTestDataWithEncryptedSections();
int result =
@ -497,9 +494,7 @@ public final class SampleQueueTest {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
DrmSession mockPlaceholderDrmSession = Mockito.mock(DrmSession.class);
when(mockPlaceholderDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
when(mockDrmSessionManager.acquirePlaceholderSession(
ArgumentMatchers.any(), ArgumentMatchers.anyInt()))
.thenReturn(mockPlaceholderDrmSession);
mockDrmSessionManager.mockPlaceholderDrmSession = mockPlaceholderDrmSession;
writeFormat(ENCRYPTED_SAMPLE_FORMATS[0]);
byte[] sampleData = new byte[] {0, 1, 2};
@ -540,7 +535,7 @@ public final class SampleQueueTest {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
writeTestDataWithEncryptedSections();
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE);
assertReadNothing(/* formatRequired= */ false);
sampleQueue.maybeThrowError();
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_ERROR);
@ -569,7 +564,7 @@ public final class SampleQueueTest {
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
writeTestDataWithEncryptedSections();
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE);
assertReadEncryptedSample(/* sampleIndex= */ 0);
}
@ -1497,4 +1492,33 @@ public final class SampleQueueTest {
private static Format copyWithLabel(Format format, String label) {
return format.buildUpon().setLabel(label).build();
}
private static final class MockExoMediaCrypto implements ExoMediaCrypto {}
private static final class MockDrmSessionManager implements DrmSessionManager {
private final DrmSession mockDrmSession;
@Nullable private DrmSession mockPlaceholderDrmSession;
private MockDrmSessionManager(DrmSession mockDrmSession) {
this.mockDrmSession = mockDrmSession;
}
@Nullable
@Override
public DrmSession acquireSession(
Looper playbackLooper,
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
Format format) {
return format.drmInitData != null ? mockDrmSession : mockPlaceholderDrmSession;
}
@Nullable
@Override
public Class<? extends ExoMediaCrypto> getExoMediaCryptoType(Format format) {
return mockPlaceholderDrmSession != null || format.drmInitData != null
? MockExoMediaCrypto.class
: null;
}
}
}

View File

@ -30,7 +30,6 @@ import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.Iterables;
import java.io.IOException;
@ -274,10 +273,7 @@ public class FakeSampleStream implements SampleStream {
@Nullable DrmSession previousSession = currentDrmSession;
Looper playbackLooper = Assertions.checkNotNull(Looper.myLooper());
currentDrmSession =
newDrmInitData != null
? drmSessionManager.acquireSession(playbackLooper, drmEventDispatcher, newFormat)
: drmSessionManager.acquirePlaceholderSession(
playbackLooper, MimeTypes.getTrackType(newFormat.sampleMimeType));
drmSessionManager.acquireSession(playbackLooper, drmEventDispatcher, newFormat);
outputFormatHolder.drmSession = currentDrmSession;
if (previousSession != null) {