add clearTrackTypes and playClearContentWithoutKey to DrmConfiguration

With these additional properties, we can declare the behaviour for clear tracks and clear content on a media item level.

PiperOrigin-RevId: 304351716
This commit is contained in:
bachinger 2020-04-02 09:38:48 +01:00 committed by Oliver Woodman
parent dbea614cca
commit dc4118da7b
6 changed files with 139 additions and 48 deletions

View file

@ -454,7 +454,6 @@ public class PlayerActivity extends AppCompatActivity
private MediaSource createLeafMediaSource(UriSample parameters) {
MediaItem.Builder builder = new MediaItem.Builder().setSourceUri(parameters.uri);
builder.setMimeType(Sample.inferAdaptiveStreamMimeType(parameters.uri, parameters.extension));
int[] drmSessionForClearTypes = new int[0];
HttpDataSource.Factory drmDataSourceFactory = null;
if (parameters.drmInfo != null) {
if (Util.SDK_INT < 18) {
@ -471,8 +470,8 @@ public class PlayerActivity extends AppCompatActivity
.setDrmLicenseRequestHeaders(
createLicenseHeaders(parameters.drmInfo.drmKeyRequestProperties))
.setDrmUuid(parameters.drmInfo.drmScheme)
.setDrmMultiSession(parameters.drmInfo.drmMultiSession);
drmSessionForClearTypes = parameters.drmInfo.drmSessionForClearTypes;
.setDrmMultiSession(parameters.drmInfo.drmMultiSession)
.setDrmSessionForClearTypes(Util.toList(parameters.drmInfo.drmSessionForClearTypes));
drmDataSourceFactory = ((DemoApplication) getApplication()).buildHttpDataSourceFactory();
}
if (parameters.subtitleInfo != null) {
@ -494,7 +493,6 @@ public class PlayerActivity extends AppCompatActivity
}
return mediaSourceFactory
.setDrmHttpDataSourceFactory(drmDataSourceFactory)
.setUseDrmSessionForClearContent(drmSessionForClearTypes)
.createMediaSource(builder.build());
}

View file

@ -60,6 +60,7 @@ import java.io.InputStream;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
@ -1199,6 +1200,23 @@ public final class Util {
return intArray;
}
/**
* Converts an array of primitive ints to a list of integers.
*
* @param ints The ints.
* @return The input array in list form.
*/
public static List<Integer> toList(int... ints) {
if (ints == null) {
return new ArrayList<>();
}
List<Integer> integers = new ArrayList<>();
for (int anInt : ints) {
integers.add(anInt);
}
return integers;
}
/**
* Returns the integer equal to the big-endian concatenation of the characters in {@code string}
* as bytes. The string must be no more than four characters long.

View file

@ -934,6 +934,21 @@ public class UtilTest {
assertThat(Util.normalizeLanguageCode("hsn")).isEqualTo("zh-hsn");
}
@Test
public void toList() {
assertThat(Util.toList(0, 3, 4)).containsExactly(0, 3, 4).inOrder();
}
@Test
public void toList_nullPassed_returnsEmptyList() {
assertThat(Util.toList(null)).isEmpty();
}
@Test
public void toList_emptyArrayPassed_returnsEmptyList() {
assertThat(Util.toList(new int[0])).isEmpty();
}
private static void assertEscapeUnescapeFileName(String fileName, String escapedFileName) {
assertThat(escapeFileName(fileName)).isEqualTo(escapedFileName);
assertThat(unescapeFileName(escapedFileName)).isEqualTo(fileName);

View file

@ -21,6 +21,7 @@ import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Util;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@ -60,6 +61,8 @@ public final class MediaItem {
private Map<String, String> drmLicenseRequestHeaders;
@Nullable private UUID drmUuid;
private boolean drmMultiSession;
private boolean drmPlayClearContentWithoutKey;
private List<Integer> drmSessionForClearTypes;
private List<StreamKey> streamKeys;
private List<Subtitle> subtitles;
@Nullable private Object tag;
@ -69,6 +72,7 @@ public final class MediaItem {
public Builder() {
streamKeys = Collections.emptyList();
subtitles = Collections.emptyList();
drmSessionForClearTypes = Collections.emptyList();
drmLicenseRequestHeaders = Collections.emptyMap();
}
@ -145,10 +149,10 @@ public final class MediaItem {
* <p>If no valid drm configuration is specified, the drm license request headers are ignored.
*/
public Builder setDrmLicenseRequestHeaders(
@Nullable Map<String, String> drmLicenseRequestHeaders) {
@Nullable Map<String, String> licenseRequestHeaders) {
this.drmLicenseRequestHeaders =
drmLicenseRequestHeaders != null && !drmLicenseRequestHeaders.isEmpty()
? drmLicenseRequestHeaders
licenseRequestHeaders != null && !licenseRequestHeaders.isEmpty()
? licenseRequestHeaders
: Collections.emptyMap();
return this;
}
@ -176,6 +180,50 @@ public final class MediaItem {
return this;
}
/**
* Sets whether clear samples within protected content should be played when keys for the
* encrypted part of the content have yet to be loaded.
*/
public Builder setDrmPlayClearContentWithoutKey(boolean playClearContentWithoutKey) {
this.drmPlayClearContentWithoutKey = playClearContentWithoutKey;
return this;
}
/**
* Sets whether a drm session should be used for clear tracks of type {@link C#TRACK_TYPE_VIDEO}
* and {@link C#TRACK_TYPE_AUDIO}.
*
* <p>This method overrides what has been set by previously calling {@link
* #setDrmSessionForClearTypes(List)}.
*/
public Builder setDrmSessionForClearPeriods(boolean sessionForClearPeriods) {
this.setDrmSessionForClearTypes(
sessionForClearPeriods
? Arrays.asList(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO)
: Collections.emptyList());
return this;
}
/**
* Sets a list of {@link C}{@code .TRACK_TYPE_*} constants for which to use a drm session even
* when the tracks are in the clear.
*
* <p>For the common case of using a drm session for {@link C#TRACK_TYPE_VIDEO} and {@link
* C#TRACK_TYPE_AUDIO} the {@link #setDrmSessionForClearPeriods(boolean)} can be used.
*
* <p>This method overrides what has been set by previously calling {@link
* #setDrmSessionForClearPeriods(boolean)}.
*
* <p>{@code null} or an empty {@link List} can be used for a reset.
*/
public Builder setDrmSessionForClearTypes(@Nullable List<Integer> sessionForClearTypes) {
this.drmSessionForClearTypes =
sessionForClearTypes != null && !sessionForClearTypes.isEmpty()
? Collections.unmodifiableList(new ArrayList<>(sessionForClearTypes))
: Collections.emptyList();
return this;
}
/**
* Sets the optional stream keys by which the manifest is filtered (only used for adaptive
* streams).
@ -241,7 +289,12 @@ public final class MediaItem {
mimeType,
drmUuid != null
? new DrmConfiguration(
drmUuid, drmLicenseUri, drmLicenseRequestHeaders, drmMultiSession)
drmUuid,
drmLicenseUri,
drmLicenseRequestHeaders,
drmMultiSession,
drmPlayClearContentWithoutKey,
drmSessionForClearTypes)
: null,
streamKeys,
subtitles,
@ -273,15 +326,28 @@ public final class MediaItem {
/** Whether the drm configuration is multi session enabled. */
public final boolean multiSession;
/**
* Whether clear samples within protected content should be played when keys for the encrypted
* part of the content have yet to be loaded.
*/
public final boolean playClearContentWithoutKey;
/** The types of clear tracks for which to use a drm session. */
public final List<Integer> sessionForClearTypes;
private DrmConfiguration(
UUID uuid,
@Nullable Uri licenseUri,
Map<String, String> requestHeaders,
boolean multiSession) {
boolean multiSession,
boolean playClearContentWithoutKey,
List<Integer> drmSessionForClearTypes) {
this.uuid = uuid;
this.licenseUri = licenseUri;
this.requestHeaders = Collections.unmodifiableMap(new HashMap<>(requestHeaders));
this.multiSession = multiSession;
this.playClearContentWithoutKey = playClearContentWithoutKey;
this.sessionForClearTypes = drmSessionForClearTypes;
}
@Override
@ -297,7 +363,9 @@ public final class MediaItem {
return uuid.equals(other.uuid)
&& Util.areEqual(licenseUri, other.licenseUri)
&& Util.areEqual(requestHeaders, other.requestHeaders)
&& multiSession == other.multiSession;
&& multiSession == other.multiSession
&& playClearContentWithoutKey == other.playClearContentWithoutKey
&& sessionForClearTypes.equals(other.sessionForClearTypes);
}
@Override
@ -306,6 +374,8 @@ public final class MediaItem {
result = 31 * result + (licenseUri != null ? licenseUri.hashCode() : 0);
result = 31 * result + requestHeaders.hashCode();
result = 31 * result + (multiSession ? 1 : 0);
result = 31 * result + (playClearContentWithoutKey ? 1 : 0);
result = 31 * result + sessionForClearTypes.hashCode();
return result;
}
}

View file

@ -77,14 +77,9 @@ import java.util.Map;
*
* <p>For a media item with a valid {@link
* com.google.android.exoplayer2.MediaItem.DrmConfiguration}, a {@link DefaultDrmSessionManager} is
* created. The following setters can be used to optionally configure the creation:
* created. The following setter can be used to optionally configure the creation:
*
* <ul>
* <li>{@link #setPlayClearContentWithoutKey(boolean)}: See {@link
* DefaultDrmSessionManager.Builder#setPlayClearSamplesWithoutKeys(boolean)} (default: {@code
* false}).
* <li>{@link #setUseDrmSessionForClearContent(int...)}: See {@link
* DefaultDrmSessionManager.Builder#setUseDrmSessionsForClearContent(int...)} (default: none).
* <li>{@link #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}: Sets the data source factory
* to be used by the {@link HttpMediaDrmCallback} for network requests (default: {@link
* DefaultHttpDataSourceFactory}).
@ -130,8 +125,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
private DrmSessionManager drmSessionManager;
private HttpDataSource.Factory drmHttpDataSourceFactory;
private boolean playClearContentWithoutKey;
private int[] useDrmSessionsForClearContentTrackTypes;
@Nullable private List<StreamKey> streamKeys;
private DefaultMediaSourceFactory(Context context, DataSource.Factory dataSourceFactory) {
@ -139,7 +132,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
userAgent = Util.getUserAgent(context, ExoPlayerLibraryInfo.VERSION_SLASHY);
drmHttpDataSourceFactory = new DefaultHttpDataSourceFactory(userAgent);
useDrmSessionsForClearContentTrackTypes = new int[0];
mediaSourceFactories = loadDelegates(dataSourceFactory);
supportedTypes = new int[mediaSourceFactories.size()];
for (int i = 0; i < mediaSourceFactories.size(); i++) {
@ -165,33 +157,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
return this;
}
/**
* Used to create {@link DrmSessionManager DrmSessionManagers}. See {@link
* DefaultDrmSessionManager.Builder#setPlayClearSamplesWithoutKeys(boolean)}.
*
* @return This factory, for convenience.
*/
public DefaultMediaSourceFactory setPlayClearContentWithoutKey(
boolean playClearContentWithoutKey) {
this.playClearContentWithoutKey = playClearContentWithoutKey;
return this;
}
/**
* Used to create {@link DrmSessionManager DrmSessionManagers}. See {@link
* DefaultDrmSessionManager.Builder#setUseDrmSessionsForClearContent(int...)}.
*
* @return This factory, for convenience.
*/
public DefaultMediaSourceFactory setUseDrmSessionForClearContent(
int... useDrmSessionsForClearContentTrackTypes) {
for (int trackType : useDrmSessionsForClearContentTrackTypes) {
Assertions.checkArgument(trackType == C.TRACK_TYPE_VIDEO || trackType == C.TRACK_TYPE_AUDIO);
}
this.useDrmSessionsForClearContentTrackTypes = useDrmSessionsForClearContentTrackTypes.clone();
return this;
}
@Override
public DefaultMediaSourceFactory setDrmSessionManager(
@Nullable DrmSessionManager drmSessionManager) {
@ -202,6 +167,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
return this;
}
@Override
public DefaultMediaSourceFactory setLoadErrorHandlingPolicy(
@Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
LoadErrorHandlingPolicy newLoadErrorHandlingPolicy =
@ -286,8 +252,10 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
.setUuidAndExoMediaDrmProvider(
mediaItem.playbackProperties.drmConfiguration.uuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
.setMultiSession(mediaItem.playbackProperties.drmConfiguration.multiSession)
.setPlayClearSamplesWithoutKeys(playClearContentWithoutKey)
.setUseDrmSessionsForClearContent(useDrmSessionsForClearContentTrackTypes)
.setPlayClearSamplesWithoutKeys(
mediaItem.playbackProperties.drmConfiguration.playClearContentWithoutKey)
.setUseDrmSessionsForClearContent(
Util.toArray(mediaItem.playbackProperties.drmConfiguration.sessionForClearTypes))
.build(createHttpMediaDrmCallback(mediaItem.playbackProperties.drmConfiguration));
}

View file

@ -24,6 +24,7 @@ import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -97,6 +98,8 @@ public class MediaItemTest {
.setDrmLicenseUri(licenseUri)
.setDrmLicenseRequestHeaders(requestHeaders)
.setDrmMultiSession(/* multiSession= */ true)
.setDrmPlayClearContentWithoutKey(true)
.setDrmSessionForClearTypes(Collections.singletonList(C.TRACK_TYPE_AUDIO))
.build();
assertThat(mediaItem.playbackProperties.drmConfiguration).isNotNull();
@ -105,6 +108,25 @@ public class MediaItemTest {
assertThat(mediaItem.playbackProperties.drmConfiguration.requestHeaders)
.isEqualTo(requestHeaders);
assertThat(mediaItem.playbackProperties.drmConfiguration.multiSession).isTrue();
assertThat(mediaItem.playbackProperties.drmConfiguration.playClearContentWithoutKey).isTrue();
assertThat(mediaItem.playbackProperties.drmConfiguration.sessionForClearTypes)
.containsExactly(C.TRACK_TYPE_AUDIO);
}
@Test
public void builderSetDrmSessionForClearPeriods_setsAudioAndVideoTracks() {
Uri licenseUri = Uri.parse(URI_STRING);
MediaItem mediaItem =
new MediaItem.Builder()
.setSourceUri(URI_STRING)
.setDrmUuid(C.WIDEVINE_UUID)
.setDrmLicenseUri(licenseUri)
.setDrmSessionForClearTypes(Arrays.asList(C.TRACK_TYPE_AUDIO))
.setDrmSessionForClearPeriods(true)
.build();
assertThat(mediaItem.playbackProperties.drmConfiguration.sessionForClearTypes)
.containsExactly(C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_VIDEO);
}
@Test