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
|
* Fix `ERROR_DRM_SESSION_NOT_OPENED` when switching from encrypted to
|
||||||
clear content without a surface attached to the player. The error was
|
clear content without a surface attached to the player. The error was
|
||||||
due to incorrectly using a secure decoder to play the clear content.
|
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:
|
* Effect:
|
||||||
* Muxers:
|
* Muxers:
|
||||||
* IMA extension:
|
* IMA extension:
|
||||||
|
@ -25,6 +25,7 @@ import android.net.Uri;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.IntRange;
|
import androidx.annotation.IntRange;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
import androidx.media3.common.util.BundleCollectionUtil;
|
import androidx.media3.common.util.BundleCollectionUtil;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
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.
|
* @param scheme The {@link UUID} of the protection scheme.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("deprecation") // Calling deprecated constructor to reduce code duplication.
|
||||||
public Builder(UUID scheme) {
|
public Builder(UUID scheme) {
|
||||||
|
this();
|
||||||
this.scheme = scheme;
|
this.scheme = scheme;
|
||||||
this.licenseRequestHeaders = ImmutableMap.of();
|
|
||||||
this.forcedSessionTrackTypes = ImmutableList.of();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -653,6 +654,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
private Builder() {
|
private Builder() {
|
||||||
this.licenseRequestHeaders = ImmutableMap.of();
|
this.licenseRequestHeaders = ImmutableMap.of();
|
||||||
|
this.playClearContentWithoutKey = true;
|
||||||
this.forcedSessionTrackTypes = ImmutableList.of();
|
this.forcedSessionTrackTypes = ImmutableList.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -706,7 +708,11 @@ public final class MediaItem implements Bundleable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets whether multi session is enabled. */
|
/**
|
||||||
|
* Sets whether multi session is enabled.
|
||||||
|
*
|
||||||
|
* <p>The default is {@code false} (multi session disabled).
|
||||||
|
*/
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setMultiSession(boolean multiSession) {
|
public Builder setMultiSession(boolean multiSession) {
|
||||||
this.multiSession = 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
|
* Sets whether to always use the default DRM license server URI even if the media specifies
|
||||||
* its own DRM license server URI.
|
* its own DRM license server URI.
|
||||||
|
*
|
||||||
|
* <p>The default is {@code false}.
|
||||||
*/
|
*/
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setForceDefaultLicenseUri(boolean forceDefaultLicenseUri) {
|
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
|
* Sets whether clear samples within protected content should be played when keys for the
|
||||||
* encrypted part of the content have yet to be loaded.
|
* encrypted part of the content have yet to be loaded.
|
||||||
|
*
|
||||||
|
* <p>The default is {@code true}.
|
||||||
*/
|
*/
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setPlayClearContentWithoutKey(boolean playClearContentWithoutKey) {
|
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
|
* <p>This method overrides what has been set by previously calling {@link
|
||||||
* #setForcedSessionTrackTypes(List)}.
|
* #setForcedSessionTrackTypes(List)}.
|
||||||
|
*
|
||||||
|
* <p>The default is {@code false}.
|
||||||
*/
|
*/
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setForceSessionsForAudioAndVideoTracks(
|
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
|
* <p>This method overrides what has been set by previously calling {@link
|
||||||
* #setForceSessionsForAudioAndVideoTracks(boolean)}.
|
* #setForceSessionsForAudioAndVideoTracks(boolean)}.
|
||||||
|
*
|
||||||
|
* <p>The default is an empty list (i.e. DRM sessions are not forced for any track type).
|
||||||
*/
|
*/
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setForcedSessionTrackTypes(
|
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_URI = Util.intToStringMaxRadix(1);
|
||||||
private static final String FIELD_LICENSE_REQUEST_HEADERS = Util.intToStringMaxRadix(2);
|
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_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_FORCE_DEFAULT_LICENSE_URI = Util.intToStringMaxRadix(5);
|
||||||
private static final String FIELD_FORCED_SESSION_TRACK_TYPES = Util.intToStringMaxRadix(6);
|
private static final String FIELD_FORCED_SESSION_TRACK_TYPES = Util.intToStringMaxRadix(6);
|
||||||
private static final String FIELD_KEY_SET_ID = Util.intToStringMaxRadix(7);
|
private static final String FIELD_KEY_SET_ID = Util.intToStringMaxRadix(7);
|
||||||
|
@ -101,7 +101,7 @@ public class MediaItemTest {
|
|||||||
.setDrmLicenseRequestHeaders(requestHeaders)
|
.setDrmLicenseRequestHeaders(requestHeaders)
|
||||||
.setDrmMultiSession(true)
|
.setDrmMultiSession(true)
|
||||||
.setDrmForceDefaultLicenseUri(true)
|
.setDrmForceDefaultLicenseUri(true)
|
||||||
.setDrmPlayClearContentWithoutKey(true)
|
.setDrmPlayClearContentWithoutKey(false)
|
||||||
.setDrmSessionForClearTypes(ImmutableList.of(C.TRACK_TYPE_AUDIO))
|
.setDrmSessionForClearTypes(ImmutableList.of(C.TRACK_TYPE_AUDIO))
|
||||||
.setDrmKeySetId(keySetId)
|
.setDrmKeySetId(keySetId)
|
||||||
.setDrmUuid(C.WIDEVINE_UUID)
|
.setDrmUuid(C.WIDEVINE_UUID)
|
||||||
@ -117,7 +117,7 @@ public class MediaItemTest {
|
|||||||
.isEqualTo(requestHeaders);
|
.isEqualTo(requestHeaders);
|
||||||
assertThat(mediaItem.localConfiguration.drmConfiguration.multiSession).isTrue();
|
assertThat(mediaItem.localConfiguration.drmConfiguration.multiSession).isTrue();
|
||||||
assertThat(mediaItem.localConfiguration.drmConfiguration.forceDefaultLicenseUri).isTrue();
|
assertThat(mediaItem.localConfiguration.drmConfiguration.forceDefaultLicenseUri).isTrue();
|
||||||
assertThat(mediaItem.localConfiguration.drmConfiguration.playClearContentWithoutKey).isTrue();
|
assertThat(mediaItem.localConfiguration.drmConfiguration.playClearContentWithoutKey).isFalse();
|
||||||
assertThat(mediaItem.localConfiguration.drmConfiguration.sessionForClearTypes)
|
assertThat(mediaItem.localConfiguration.drmConfiguration.sessionForClearTypes)
|
||||||
.containsExactly(C.TRACK_TYPE_AUDIO);
|
.containsExactly(C.TRACK_TYPE_AUDIO);
|
||||||
assertThat(mediaItem.localConfiguration.drmConfiguration.forcedSessionTrackTypes)
|
assertThat(mediaItem.localConfiguration.drmConfiguration.forcedSessionTrackTypes)
|
||||||
@ -139,7 +139,7 @@ public class MediaItemTest {
|
|||||||
.setDrmLicenseRequestHeaders(requestHeaders)
|
.setDrmLicenseRequestHeaders(requestHeaders)
|
||||||
.setDrmMultiSession(true)
|
.setDrmMultiSession(true)
|
||||||
.setDrmForceDefaultLicenseUri(true)
|
.setDrmForceDefaultLicenseUri(true)
|
||||||
.setDrmPlayClearContentWithoutKey(true)
|
.setDrmPlayClearContentWithoutKey(false)
|
||||||
.setDrmSessionForClearTypes(Collections.singletonList(C.TRACK_TYPE_AUDIO))
|
.setDrmSessionForClearTypes(Collections.singletonList(C.TRACK_TYPE_AUDIO))
|
||||||
.setDrmKeySetId(keySetId)
|
.setDrmKeySetId(keySetId)
|
||||||
.setDrmUuid(C.WIDEVINE_UUID)
|
.setDrmUuid(C.WIDEVINE_UUID)
|
||||||
@ -154,7 +154,7 @@ public class MediaItemTest {
|
|||||||
assertThat(mediaItem.localConfiguration.drmConfiguration.licenseRequestHeaders).isEmpty();
|
assertThat(mediaItem.localConfiguration.drmConfiguration.licenseRequestHeaders).isEmpty();
|
||||||
assertThat(mediaItem.localConfiguration.drmConfiguration.multiSession).isFalse();
|
assertThat(mediaItem.localConfiguration.drmConfiguration.multiSession).isFalse();
|
||||||
assertThat(mediaItem.localConfiguration.drmConfiguration.forceDefaultLicenseUri).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.sessionForClearTypes).isEmpty();
|
||||||
assertThat(mediaItem.localConfiguration.drmConfiguration.forcedSessionTrackTypes).isEmpty();
|
assertThat(mediaItem.localConfiguration.drmConfiguration.forcedSessionTrackTypes).isEmpty();
|
||||||
assertThat(mediaItem.localConfiguration.drmConfiguration.getKeySetId()).isNull();
|
assertThat(mediaItem.localConfiguration.drmConfiguration.getKeySetId()).isNull();
|
||||||
@ -249,6 +249,35 @@ public class MediaItemTest {
|
|||||||
.build());
|
.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
|
@Test
|
||||||
public void createDrmConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() {
|
public void createDrmConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() {
|
||||||
MediaItem.DrmConfiguration drmConfiguration =
|
MediaItem.DrmConfiguration drmConfiguration =
|
||||||
|
@ -98,17 +98,20 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
|||||||
* FrameworkMediaDrm#DEFAULT_PROVIDER}.
|
* FrameworkMediaDrm#DEFAULT_PROVIDER}.
|
||||||
* <li>{@link #setMultiSession multiSession}: {@code false}.
|
* <li>{@link #setMultiSession multiSession}: {@code false}.
|
||||||
* <li>{@link #setUseDrmSessionsForClearContent useDrmSessionsForClearContent}: No tracks.
|
* <li>{@link #setUseDrmSessionsForClearContent useDrmSessionsForClearContent}: No tracks.
|
||||||
* <li>{@link #setPlayClearSamplesWithoutKeys playClearSamplesWithoutKeys}: {@code false}.
|
* <li>{@link #setPlayClearSamplesWithoutKeys playClearSamplesWithoutKeys}: {@code true}.
|
||||||
* <li>{@link #setLoadErrorHandlingPolicy LoadErrorHandlingPolicy}: {@link
|
* <li>{@link #setLoadErrorHandlingPolicy LoadErrorHandlingPolicy}: {@link
|
||||||
* DefaultLoadErrorHandlingPolicy}.
|
* DefaultLoadErrorHandlingPolicy}.
|
||||||
|
* <li>{@link #setSessionKeepaliveMs sessionKeepaliveMs}: {@link
|
||||||
|
* #DEFAULT_SESSION_KEEPALIVE_MS}.
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public Builder() {
|
public Builder() {
|
||||||
keyRequestParameters = new HashMap<>();
|
keyRequestParameters = new HashMap<>();
|
||||||
uuid = C.WIDEVINE_UUID;
|
uuid = C.WIDEVINE_UUID;
|
||||||
exoMediaDrmProvider = FrameworkMediaDrm.DEFAULT_PROVIDER;
|
exoMediaDrmProvider = FrameworkMediaDrm.DEFAULT_PROVIDER;
|
||||||
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
|
||||||
useDrmSessionsForClearContentTrackTypes = new int[0];
|
useDrmSessionsForClearContentTrackTypes = new int[0];
|
||||||
|
playClearSamplesWithoutKeys = true;
|
||||||
|
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
||||||
sessionKeepaliveMs = DEFAULT_SESSION_KEEPALIVE_MS;
|
sessionKeepaliveMs = DEFAULT_SESSION_KEEPALIVE_MS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1461,6 +1461,9 @@ public final class DefaultAnalyticsCollectorTest {
|
|||||||
DRM_SCHEME_UUID,
|
DRM_SCHEME_UUID,
|
||||||
uuid -> new FakeExoMediaDrm.Builder().setEnforceValidKeyResponses(false).build())
|
uuid -> new FakeExoMediaDrm.Builder().setEnforceValidKeyResponses(false).build())
|
||||||
.setMultiSession(true)
|
.setMultiSession(true)
|
||||||
|
// The fake samples are not encrypted, so this forces the test to block playback until
|
||||||
|
// keys are ready.
|
||||||
|
.setPlayClearSamplesWithoutKeys(false)
|
||||||
.build(mediaDrmCallback);
|
.build(mediaDrmCallback);
|
||||||
MediaSource mediaSource =
|
MediaSource mediaSource =
|
||||||
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, failingDrmSessionManager, VIDEO_FORMAT_DRM_1);
|
new FakeMediaSource(SINGLE_PERIOD_TIMELINE, failingDrmSessionManager, VIDEO_FORMAT_DRM_1);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user