diff --git a/libraries/common/src/main/java/androidx/media3/common/MediaItem.java b/libraries/common/src/main/java/androidx/media3/common/MediaItem.java index 7dae880d9c..757752da2f 100644 --- a/libraries/common/src/main/java/androidx/media3/common/MediaItem.java +++ b/libraries/common/src/main/java/androidx/media3/common/MediaItem.java @@ -1853,6 +1853,7 @@ public final class MediaItem { private boolean relativeToLiveWindow; private boolean relativeToDefaultPosition; private boolean startsAtKeyFrame; + private boolean allowUnseekableMedia; /** Creates a new instance with default values. */ public Builder() { @@ -1865,6 +1866,7 @@ public final class MediaItem { relativeToLiveWindow = clippingConfiguration.relativeToLiveWindow; relativeToDefaultPosition = clippingConfiguration.relativeToDefaultPosition; startsAtKeyFrame = clippingConfiguration.startsAtKeyFrame; + allowUnseekableMedia = clippingConfiguration.allowUnseekableMedia; } /** @@ -1942,6 +1944,21 @@ public final class MediaItem { return this; } + /** + * Sets whether clipping to a non-zero start position in unseekable media is allowed (Default: + * {@code false}). + * + *

Note that this could be inefficient because a player needs to read and decode all + * samples from the beginning of the file and it should only be used if the clip start + * position is small and the entire data before the start position fits into memory. + */ + @UnstableApi + @CanIgnoreReturnValue + public Builder setAllowUnseekableMedia(boolean allowUnseekableMedia) { + this.allowUnseekableMedia = allowUnseekableMedia; + return this; + } + /** * Returns a {@link ClippingConfiguration} instance initialized with the values of this * builder. @@ -1994,9 +2011,12 @@ public final class MediaItem { */ public final boolean relativeToDefaultPosition; - /** Sets whether the start point is guaranteed to be a key frame. */ + /** Whether the start point is guaranteed to be a key frame. */ public final boolean startsAtKeyFrame; + /** Whether clipping to a non-zero start position in unseekable media is allowed. */ + @UnstableApi public final boolean allowUnseekableMedia; + private ClippingConfiguration(Builder builder) { this.startPositionMs = usToMs(builder.startPositionUs); this.endPositionMs = usToMs(builder.endPositionUs); @@ -2005,6 +2025,7 @@ public final class MediaItem { this.relativeToLiveWindow = builder.relativeToLiveWindow; this.relativeToDefaultPosition = builder.relativeToDefaultPosition; this.startsAtKeyFrame = builder.startsAtKeyFrame; + this.allowUnseekableMedia = builder.allowUnseekableMedia; } /** Returns a {@link Builder} initialized with the values of this instance. */ @@ -2027,7 +2048,8 @@ public final class MediaItem { && endPositionUs == other.endPositionUs && relativeToLiveWindow == other.relativeToLiveWindow && relativeToDefaultPosition == other.relativeToDefaultPosition - && startsAtKeyFrame == other.startsAtKeyFrame; + && startsAtKeyFrame == other.startsAtKeyFrame + && allowUnseekableMedia == other.allowUnseekableMedia; } @Override @@ -2037,6 +2059,7 @@ public final class MediaItem { result = 31 * result + (relativeToLiveWindow ? 1 : 0); result = 31 * result + (relativeToDefaultPosition ? 1 : 0); result = 31 * result + (startsAtKeyFrame ? 1 : 0); + result = 31 * result + (allowUnseekableMedia ? 1 : 0); return result; } @@ -2045,8 +2068,9 @@ public final class MediaItem { private static final String FIELD_RELATIVE_TO_LIVE_WINDOW = Util.intToStringMaxRadix(2); private static final String FIELD_RELATIVE_TO_DEFAULT_POSITION = Util.intToStringMaxRadix(3); private static final String FIELD_STARTS_AT_KEY_FRAME = Util.intToStringMaxRadix(4); - static final String FIELD_START_POSITION_US = Util.intToStringMaxRadix(5); - static final String FIELD_END_POSITION_US = Util.intToStringMaxRadix(6); + @VisibleForTesting static final String FIELD_START_POSITION_US = Util.intToStringMaxRadix(5); + @VisibleForTesting static final String FIELD_END_POSITION_US = Util.intToStringMaxRadix(6); + private static final String FIELD_ALLOW_UNSEEKABLE_MEDIA = Util.intToStringMaxRadix(7); @UnstableApi public Bundle toBundle() { @@ -2072,6 +2096,9 @@ public final class MediaItem { if (startsAtKeyFrame != UNSET.startsAtKeyFrame) { bundle.putBoolean(FIELD_STARTS_AT_KEY_FRAME, startsAtKeyFrame); } + if (allowUnseekableMedia != UNSET.allowUnseekableMedia) { + bundle.putBoolean(FIELD_ALLOW_UNSEEKABLE_MEDIA, allowUnseekableMedia); + } return bundle; } @@ -2096,7 +2123,11 @@ public final class MediaItem { /* defaultValue= */ UNSET.relativeToDefaultPosition)) .setStartsAtKeyFrame( bundle.getBoolean( - FIELD_STARTS_AT_KEY_FRAME, /* defaultValue= */ UNSET.startsAtKeyFrame)); + FIELD_STARTS_AT_KEY_FRAME, /* defaultValue= */ UNSET.startsAtKeyFrame)) + .setAllowUnseekableMedia( + bundle.getBoolean( + FIELD_ALLOW_UNSEEKABLE_MEDIA, + /* defaultValue= */ UNSET.allowUnseekableMedia)); long startPositionUs = bundle.getLong(FIELD_START_POSITION_US, /* defaultValue= */ UNSET.startPositionUs); if (startPositionUs != UNSET.startPositionUs) { diff --git a/libraries/common/src/test/java/androidx/media3/common/MediaItemTest.java b/libraries/common/src/test/java/androidx/media3/common/MediaItemTest.java index c3400594c5..7fd5b97bb2 100644 --- a/libraries/common/src/test/java/androidx/media3/common/MediaItemTest.java +++ b/libraries/common/src/test/java/androidx/media3/common/MediaItemTest.java @@ -450,6 +450,7 @@ public class MediaItemTest { .setRelativeToLiveWindow(true) .setRelativeToDefaultPosition(true) .setStartsAtKeyFrame(true) + .setAllowUnseekableMedia(true) .build()) .build(); @@ -458,6 +459,7 @@ public class MediaItemTest { assertThat(mediaItem.clippingConfiguration.relativeToLiveWindow).isTrue(); assertThat(mediaItem.clippingConfiguration.relativeToDefaultPosition).isTrue(); assertThat(mediaItem.clippingConfiguration.startsAtKeyFrame).isTrue(); + assertThat(mediaItem.clippingConfiguration.allowUnseekableMedia).isTrue(); assertThat(mediaItem.clippingConfiguration).isEqualTo(mediaItem.clippingProperties); } @@ -475,6 +477,7 @@ public class MediaItemTest { assertThat(clippingConfiguration.relativeToLiveWindow).isFalse(); assertThat(clippingConfiguration.relativeToDefaultPosition).isFalse(); assertThat(clippingConfiguration.startsAtKeyFrame).isFalse(); + assertThat(clippingConfiguration.allowUnseekableMedia).isFalse(); assertThat(clippingConfiguration).isEqualTo(MediaItem.ClippingConfiguration.UNSET); } @@ -503,6 +506,7 @@ public class MediaItemTest { .setStartPositionMs(1000L) .setEndPositionUs(2000_031L) .setStartsAtKeyFrame(true) + .setAllowUnseekableMedia(true) .build(); MediaItem.ClippingConfiguration clippingConfigurationFromBundle = diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ClippingMediaSource.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ClippingMediaSource.java index 1fcd3debe3..a3f20e69df 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ClippingMediaSource.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/ClippingMediaSource.java @@ -200,7 +200,7 @@ public final class ClippingMediaSource extends WrappingMediaSource { * Sets whether clipping to a non-zero start position in unseekable media is allowed. * *

Note that this is inefficient because the player needs to read and decode all samples from - * the beginning of the file and it should only be used if the seek start position is small and + * the beginning of the file and it should only be used if the clip start position is small and * the entire data before the start position fits into memory. * *

The default value is {@code false}. diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactory.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactory.java index cf65fee2cb..399108629a 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactory.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/DefaultMediaSourceFactory.java @@ -589,6 +589,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory { .setEnableInitialDiscontinuity(!mediaItem.clippingConfiguration.startsAtKeyFrame) .setAllowDynamicClippingUpdates(mediaItem.clippingConfiguration.relativeToLiveWindow) .setRelativeToDefaultPosition(mediaItem.clippingConfiguration.relativeToDefaultPosition) + .setAllowUnseekableMedia(mediaItem.clippingConfiguration.allowUnseekableMedia) .build(); } diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ClippingMediaSourceTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ClippingMediaSourceTest.java index 5c985cdc86..bf4b290bf1 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ClippingMediaSourceTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/source/ClippingMediaSourceTest.java @@ -611,6 +611,7 @@ public final class ClippingMediaSourceTest { .setEnableInitialDiscontinuity(!mediaItem.clippingConfiguration.startsAtKeyFrame) .setAllowDynamicClippingUpdates(mediaItem.clippingConfiguration.relativeToLiveWindow) .setRelativeToDefaultPosition(mediaItem.clippingConfiguration.relativeToDefaultPosition) + .setAllowUnseekableMedia(mediaItem.clippingConfiguration.allowUnseekableMedia) .build(); }