From dc4118da7b9f9ca5967e8a28e7a6e1a4b5793584 Mon Sep 17 00:00:00 2001 From: bachinger Date: Thu, 2 Apr 2020 09:38:48 +0100 Subject: [PATCH] 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 --- .../exoplayer2/demo/PlayerActivity.java | 6 +- .../google/android/exoplayer2/util/Util.java | 18 ++++ .../android/exoplayer2/util/UtilTest.java | 15 ++++ .../google/android/exoplayer2/MediaItem.java | 82 +++++++++++++++++-- .../source/DefaultMediaSourceFactory.java | 44 ++-------- .../android/exoplayer2/MediaItemTest.java | 22 +++++ 6 files changed, 139 insertions(+), 48 deletions(-) diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java index b70b562bb0..173ffa02b5 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/PlayerActivity.java @@ -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()); } diff --git a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java index 0247d551c7..038f7d8418 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/util/Util.java @@ -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 toList(int... ints) { + if (ints == null) { + return new ArrayList<>(); + } + List 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. diff --git a/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java b/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java index 7c67b82b61..825988cf48 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/util/UtilTest.java @@ -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); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/MediaItem.java b/library/core/src/main/java/com/google/android/exoplayer2/MediaItem.java index 0d3b785715..650e439f20 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/MediaItem.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/MediaItem.java @@ -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 drmLicenseRequestHeaders; @Nullable private UUID drmUuid; private boolean drmMultiSession; + private boolean drmPlayClearContentWithoutKey; + private List drmSessionForClearTypes; private List streamKeys; private List 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 { *

If no valid drm configuration is specified, the drm license request headers are ignored. */ public Builder setDrmLicenseRequestHeaders( - @Nullable Map drmLicenseRequestHeaders) { + @Nullable Map 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}. + * + *

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. + * + *

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. + * + *

This method overrides what has been set by previously calling {@link + * #setDrmSessionForClearPeriods(boolean)}. + * + *

{@code null} or an empty {@link List} can be used for a reset. + */ + public Builder setDrmSessionForClearTypes(@Nullable List 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 sessionForClearTypes; + private DrmConfiguration( UUID uuid, @Nullable Uri licenseUri, Map requestHeaders, - boolean multiSession) { + boolean multiSession, + boolean playClearContentWithoutKey, + List 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; } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java index 0351f2f791..4bdb46b484 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/source/DefaultMediaSourceFactory.java @@ -77,14 +77,9 @@ import java.util.Map; * *

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: * *

    - *
  • {@link #setPlayClearContentWithoutKey(boolean)}: See {@link - * DefaultDrmSessionManager.Builder#setPlayClearSamplesWithoutKeys(boolean)} (default: {@code - * false}). - *
  • {@link #setUseDrmSessionForClearContent(int...)}: See {@link - * DefaultDrmSessionManager.Builder#setUseDrmSessionsForClearContent(int...)} (default: none). *
  • {@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 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)); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/MediaItemTest.java b/library/core/src/test/java/com/google/android/exoplayer2/MediaItemTest.java index 289e56d422..d6505b9138 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/MediaItemTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/MediaItemTest.java @@ -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