Play clear samples in DRM content without keys by default
This behavior was previously available as opt-in via `MediaItem.DrmConfiguration.Builder.setPlayClearContentWithoutKey` and `DefaultDrmSessionManager.Builder.setPlayClearSamplesWithoutKeys`. This change flips the default of both these properties to true. This should speed up the time for playback to start when playing DRM content with a 'clear lead' of unencrypted samples at the start. Previously playback would wait until the keys for the later encrypted samples were ready. The new behaviour could result in mid-playback stalls/rebuffers if the keys are not ready yet by the transition from clear to encrypted samples, but this is not really a regression since previously playback wouldn't have started at all at this point. PiperOrigin-RevId: 595992727
This commit is contained in:
parent
62f6c64a91
commit
77f311917f
@ -107,6 +107,15 @@
|
||||
* Fix `ERROR_DRM_SESSION_NOT_OPENED` when switching from encrypted to
|
||||
clear content without a surface attached to the player. The error was
|
||||
due to incorrectly using a secure decoder to play the clear content.
|
||||
* Play 'clear lead' unencrypted samples in DRM content immediately by
|
||||
default, even if the keys for the later encrypted samples aren't ready
|
||||
yet. This may lead to mid-playback stalls if the keys still aren't ready
|
||||
when the playback position reaches the encrypted samples (but previously
|
||||
playback wouldn't have started at all by this point). This behavior can
|
||||
be disabled with
|
||||
[`MediaItem.DrmConfiguration.Builder.setPlayClearContentWithoutKey`](https://developer.android.com/reference/androidx/media3/common/MediaItem.DrmConfiguration.Builder#setPlayClearContentWithoutKey\(boolean\))
|
||||
or
|
||||
[`DefaultDrmSessionManager.Builder.setPlayClearSamplesWithoutKeys`](https://developer.android.com/reference/androidx/media3/exoplayer/drm/DefaultDrmSessionManager.Builder#setPlayClearSamplesWithoutKeys\(boolean\)).
|
||||
* Effect:
|
||||
* Muxers:
|
||||
* IMA extension:
|
||||
|
@ -25,6 +25,7 @@ import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IntRange;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.media3.common.util.Assertions;
|
||||
import androidx.media3.common.util.BundleCollectionUtil;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
@ -640,10 +641,10 @@ public final class MediaItem implements Bundleable {
|
||||
*
|
||||
* @param scheme The {@link UUID} of the protection scheme.
|
||||
*/
|
||||
@SuppressWarnings("deprecation") // Calling deprecated constructor to reduce code duplication.
|
||||
public Builder(UUID scheme) {
|
||||
this();
|
||||
this.scheme = scheme;
|
||||
this.licenseRequestHeaders = ImmutableMap.of();
|
||||
this.forcedSessionTrackTypes = ImmutableList.of();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -653,6 +654,7 @@ public final class MediaItem implements Bundleable {
|
||||
@Deprecated
|
||||
private Builder() {
|
||||
this.licenseRequestHeaders = ImmutableMap.of();
|
||||
this.playClearContentWithoutKey = true;
|
||||
this.forcedSessionTrackTypes = ImmutableList.of();
|
||||
}
|
||||
|
||||
@ -706,7 +708,11 @@ public final class MediaItem implements Bundleable {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Sets whether multi session is enabled. */
|
||||
/**
|
||||
* Sets whether multi session is enabled.
|
||||
*
|
||||
* <p>The default is {@code false} (multi session disabled).
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setMultiSession(boolean multiSession) {
|
||||
this.multiSession = multiSession;
|
||||
@ -716,6 +722,8 @@ public final class MediaItem implements Bundleable {
|
||||
/**
|
||||
* Sets whether to always use the default DRM license server URI even if the media specifies
|
||||
* its own DRM license server URI.
|
||||
*
|
||||
* <p>The default is {@code false}.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setForceDefaultLicenseUri(boolean forceDefaultLicenseUri) {
|
||||
@ -726,6 +734,8 @@ public final class MediaItem implements Bundleable {
|
||||
/**
|
||||
* Sets whether clear samples within protected content should be played when keys for the
|
||||
* encrypted part of the content have yet to be loaded.
|
||||
*
|
||||
* <p>The default is {@code true}.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setPlayClearContentWithoutKey(boolean playClearContentWithoutKey) {
|
||||
@ -753,6 +763,8 @@ public final class MediaItem implements Bundleable {
|
||||
*
|
||||
* <p>This method overrides what has been set by previously calling {@link
|
||||
* #setForcedSessionTrackTypes(List)}.
|
||||
*
|
||||
* <p>The default is {@code false}.
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setForceSessionsForAudioAndVideoTracks(
|
||||
@ -773,6 +785,8 @@ public final class MediaItem implements Bundleable {
|
||||
*
|
||||
* <p>This method overrides what has been set by previously calling {@link
|
||||
* #setForceSessionsForAudioAndVideoTracks(boolean)}.
|
||||
*
|
||||
* <p>The default is an empty list (i.e. DRM sessions are not forced for any track type).
|
||||
*/
|
||||
@CanIgnoreReturnValue
|
||||
public Builder setForcedSessionTrackTypes(
|
||||
@ -917,7 +931,10 @@ public final class MediaItem implements Bundleable {
|
||||
private static final String FIELD_LICENSE_URI = Util.intToStringMaxRadix(1);
|
||||
private static final String FIELD_LICENSE_REQUEST_HEADERS = Util.intToStringMaxRadix(2);
|
||||
private static final String FIELD_MULTI_SESSION = Util.intToStringMaxRadix(3);
|
||||
private static final String FIELD_PLAY_CLEAR_CONTENT_WITHOUT_KEY = Util.intToStringMaxRadix(4);
|
||||
|
||||
@VisibleForTesting
|
||||
static final String FIELD_PLAY_CLEAR_CONTENT_WITHOUT_KEY = Util.intToStringMaxRadix(4);
|
||||
|
||||
private static final String FIELD_FORCE_DEFAULT_LICENSE_URI = Util.intToStringMaxRadix(5);
|
||||
private static final String FIELD_FORCED_SESSION_TRACK_TYPES = Util.intToStringMaxRadix(6);
|
||||
private static final String FIELD_KEY_SET_ID = Util.intToStringMaxRadix(7);
|
||||
|
@ -101,7 +101,7 @@ public class MediaItemTest {
|
||||
.setDrmLicenseRequestHeaders(requestHeaders)
|
||||
.setDrmMultiSession(true)
|
||||
.setDrmForceDefaultLicenseUri(true)
|
||||
.setDrmPlayClearContentWithoutKey(true)
|
||||
.setDrmPlayClearContentWithoutKey(false)
|
||||
.setDrmSessionForClearTypes(ImmutableList.of(C.TRACK_TYPE_AUDIO))
|
||||
.setDrmKeySetId(keySetId)
|
||||
.setDrmUuid(C.WIDEVINE_UUID)
|
||||
@ -117,7 +117,7 @@ public class MediaItemTest {
|
||||
.isEqualTo(requestHeaders);
|
||||
assertThat(mediaItem.localConfiguration.drmConfiguration.multiSession).isTrue();
|
||||
assertThat(mediaItem.localConfiguration.drmConfiguration.forceDefaultLicenseUri).isTrue();
|
||||
assertThat(mediaItem.localConfiguration.drmConfiguration.playClearContentWithoutKey).isTrue();
|
||||
assertThat(mediaItem.localConfiguration.drmConfiguration.playClearContentWithoutKey).isFalse();
|
||||
assertThat(mediaItem.localConfiguration.drmConfiguration.sessionForClearTypes)
|
||||
.containsExactly(C.TRACK_TYPE_AUDIO);
|
||||
assertThat(mediaItem.localConfiguration.drmConfiguration.forcedSessionTrackTypes)
|
||||
@ -139,7 +139,7 @@ public class MediaItemTest {
|
||||
.setDrmLicenseRequestHeaders(requestHeaders)
|
||||
.setDrmMultiSession(true)
|
||||
.setDrmForceDefaultLicenseUri(true)
|
||||
.setDrmPlayClearContentWithoutKey(true)
|
||||
.setDrmPlayClearContentWithoutKey(false)
|
||||
.setDrmSessionForClearTypes(Collections.singletonList(C.TRACK_TYPE_AUDIO))
|
||||
.setDrmKeySetId(keySetId)
|
||||
.setDrmUuid(C.WIDEVINE_UUID)
|
||||
@ -154,7 +154,7 @@ public class MediaItemTest {
|
||||
assertThat(mediaItem.localConfiguration.drmConfiguration.licenseRequestHeaders).isEmpty();
|
||||
assertThat(mediaItem.localConfiguration.drmConfiguration.multiSession).isFalse();
|
||||
assertThat(mediaItem.localConfiguration.drmConfiguration.forceDefaultLicenseUri).isFalse();
|
||||
assertThat(mediaItem.localConfiguration.drmConfiguration.playClearContentWithoutKey).isFalse();
|
||||
assertThat(mediaItem.localConfiguration.drmConfiguration.playClearContentWithoutKey).isTrue();
|
||||
assertThat(mediaItem.localConfiguration.drmConfiguration.sessionForClearTypes).isEmpty();
|
||||
assertThat(mediaItem.localConfiguration.drmConfiguration.forcedSessionTrackTypes).isEmpty();
|
||||
assertThat(mediaItem.localConfiguration.drmConfiguration.getKeySetId()).isNull();
|
||||
@ -249,6 +249,35 @@ public class MediaItemTest {
|
||||
.build());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createDefaultDrmConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() {
|
||||
MediaItem.DrmConfiguration drmConfiguration =
|
||||
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID).build();
|
||||
|
||||
MediaItem.DrmConfiguration drmConfigurationFromBundle =
|
||||
MediaItem.DrmConfiguration.fromBundle(drmConfiguration.toBundle());
|
||||
|
||||
assertThat(drmConfigurationFromBundle).isEqualTo(drmConfiguration);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void drmConfigurationFromOldBundle_yieldsIntendedInstance() {
|
||||
MediaItem.DrmConfiguration drmConfiguration =
|
||||
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID).build();
|
||||
|
||||
Bundle bundle = drmConfiguration.toBundle();
|
||||
// Remove the playClearSamplesWithoutKey field, to simulate a 'default' bundle from an old
|
||||
// version of the library, and check the result is 'false' (as intended by the old library).
|
||||
bundle.remove(MediaItem.DrmConfiguration.FIELD_PLAY_CLEAR_CONTENT_WITHOUT_KEY);
|
||||
|
||||
MediaItem.DrmConfiguration drmConfigurationFromBundle =
|
||||
MediaItem.DrmConfiguration.fromBundle(bundle);
|
||||
|
||||
MediaItem.DrmConfiguration expectedDrmConfiguration =
|
||||
drmConfiguration.buildUpon().setPlayClearContentWithoutKey(false).build();
|
||||
assertThat(drmConfigurationFromBundle).isEqualTo(expectedDrmConfiguration);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createDrmConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() {
|
||||
MediaItem.DrmConfiguration drmConfiguration =
|
||||
|
@ -98,17 +98,20 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
* FrameworkMediaDrm#DEFAULT_PROVIDER}.
|
||||
* <li>{@link #setMultiSession multiSession}: {@code false}.
|
||||
* <li>{@link #setUseDrmSessionsForClearContent useDrmSessionsForClearContent}: No tracks.
|
||||
* <li>{@link #setPlayClearSamplesWithoutKeys playClearSamplesWithoutKeys}: {@code false}.
|
||||
* <li>{@link #setPlayClearSamplesWithoutKeys playClearSamplesWithoutKeys}: {@code true}.
|
||||
* <li>{@link #setLoadErrorHandlingPolicy LoadErrorHandlingPolicy}: {@link
|
||||
* DefaultLoadErrorHandlingPolicy}.
|
||||
* <li>{@link #setSessionKeepaliveMs sessionKeepaliveMs}: {@link
|
||||
* #DEFAULT_SESSION_KEEPALIVE_MS}.
|
||||
* </ul>
|
||||
*/
|
||||
public Builder() {
|
||||
keyRequestParameters = new HashMap<>();
|
||||
uuid = C.WIDEVINE_UUID;
|
||||
exoMediaDrmProvider = FrameworkMediaDrm.DEFAULT_PROVIDER;
|
||||
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
||||
useDrmSessionsForClearContentTrackTypes = new int[0];
|
||||
playClearSamplesWithoutKeys = true;
|
||||
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
||||
sessionKeepaliveMs = DEFAULT_SESSION_KEEPALIVE_MS;
|
||||
}
|
||||
|
||||
|
@ -1461,6 +1461,9 @@ public final class DefaultAnalyticsCollectorTest {
|
||||
DRM_SCHEME_UUID,
|
||||
uuid -> new FakeExoMediaDrm.Builder().setEnforceValidKeyResponses(false).build())
|
||||
.setMultiSession(true)
|
||||
// The fake samples are not encrypted, so this forces the test to block playback until
|
||||
// keys are ready.
|
||||
.setPlayClearSamplesWithoutKeys(false)
|
||||
.build(mediaDrmCallback);
|
||||
MediaSource mediaSource =
|
||||
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, failingDrmSessionManager, VIDEO_FORMAT_DRM_1);
|
||||
|
Loading…
x
Reference in New Issue
Block a user