From 59cd783dd49357b163f901c60e783581cda80cf4 Mon Sep 17 00:00:00 2001 From: ibaker Date: Wed, 22 Sep 2021 15:12:09 +0100 Subject: [PATCH] Add MediaItem.ClippingProperties.Builder PiperOrigin-RevId: 398232186 --- .../google/android/exoplayer2/MediaItem.java | 201 ++++++++++++------ .../android/exoplayer2/MediaItemTest.java | 84 ++++++-- 2 files changed, 205 insertions(+), 80 deletions(-) diff --git a/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java b/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java index ef58571f08..3621404e4c 100644 --- a/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java +++ b/library/common/src/main/java/com/google/android/exoplayer2/MediaItem.java @@ -67,11 +67,9 @@ public final class MediaItem implements Bundleable { @Nullable private String mediaId; @Nullable private Uri uri; @Nullable private String mimeType; - private long clipStartPositionMs; - private long clipEndPositionMs; - private boolean clipRelativeToLiveWindow; - private boolean clipRelativeToDefaultPosition; - private boolean clipStartsAtKeyFrame; + // TODO: Change this to ClippingProperties once all the deprecated individual setters are + // removed. + private ClippingProperties.Builder clippingProperties; // TODO: Change this to @Nullable DrmConfiguration once all the deprecated individual setters // are removed. private DrmConfiguration.Builder drmConfiguration; @@ -88,7 +86,7 @@ public final class MediaItem implements Bundleable { /** Creates a builder. */ @SuppressWarnings("deprecation") // Temporarily uses DrmConfiguration.Builder() constructor. public Builder() { - clipEndPositionMs = C.TIME_END_OF_SOURCE; + clippingProperties = new ClippingProperties.Builder(); drmConfiguration = new DrmConfiguration.Builder(); streamKeys = Collections.emptyList(); subtitles = Collections.emptyList(); @@ -97,11 +95,7 @@ public final class MediaItem implements Bundleable { private Builder(MediaItem mediaItem) { this(); - clipEndPositionMs = mediaItem.clippingProperties.endPositionMs; - clipRelativeToLiveWindow = mediaItem.clippingProperties.relativeToLiveWindow; - clipRelativeToDefaultPosition = mediaItem.clippingProperties.relativeToDefaultPosition; - clipStartPositionMs = mediaItem.clippingProperties.startPositionMs; - clipStartsAtKeyFrame = mediaItem.clippingProperties.startsAtKeyFrame; + clippingProperties = mediaItem.clippingProperties.buildUpon(); mediaId = mediaItem.mediaId; mediaMetadata = mediaItem.mediaMetadata; liveConfiguration = mediaItem.liveConfiguration.buildUpon(); @@ -168,52 +162,59 @@ public final class MediaItem implements Bundleable { return this; } + /** Sets the {@link ClippingProperties}, defaults to {@link ClippingProperties#UNSET}. */ + public Builder setClippingProperties(ClippingProperties clippingProperties) { + this.clippingProperties = clippingProperties.buildUpon(); + return this; + } + /** - * Sets the optional start position in milliseconds which must be a value larger than or equal - * to zero (Default: 0). + * @deprecated Use {@link #setClippingProperties(ClippingProperties)} and {@link + * ClippingProperties.Builder#setStartPositionMs(long)} instead. */ + @Deprecated public Builder setClipStartPositionMs(@IntRange(from = 0) long startPositionMs) { - Assertions.checkArgument(startPositionMs >= 0); - this.clipStartPositionMs = startPositionMs; + clippingProperties.setStartPositionMs(startPositionMs); return this; } /** - * Sets the optional end position in milliseconds which must be a value larger than or equal to - * zero, or {@link C#TIME_END_OF_SOURCE} to end when playback reaches the end of media (Default: - * {@link C#TIME_END_OF_SOURCE}). + * @deprecated Use {@link #setClippingProperties(ClippingProperties)} and {@link + * ClippingProperties.Builder#setEndPositionMs(long)} instead. */ + @Deprecated public Builder setClipEndPositionMs(long endPositionMs) { - Assertions.checkArgument(endPositionMs == C.TIME_END_OF_SOURCE || endPositionMs >= 0); - this.clipEndPositionMs = endPositionMs; + clippingProperties.setEndPositionMs(endPositionMs); return this; } /** - * Sets whether the start/end positions should move with the live window for live streams. If - * {@code false}, live streams end when playback reaches the end position in live window seen - * when the media is first loaded (Default: {@code false}). + * @deprecated Use {@link #setClippingProperties(ClippingProperties)} and {@link + * ClippingProperties.Builder#setRelativeToLiveWindow(boolean)} instead. */ + @Deprecated public Builder setClipRelativeToLiveWindow(boolean relativeToLiveWindow) { - this.clipRelativeToLiveWindow = relativeToLiveWindow; + clippingProperties.setRelativeToLiveWindow(relativeToLiveWindow); return this; } /** - * Sets whether the start position and the end position are relative to the default position in - * the window (Default: {@code false}). + * @deprecated Use {@link #setClippingProperties(ClippingProperties)} and {@link + * ClippingProperties.Builder#setRelativeToDefaultPosition(boolean)} instead. */ + @Deprecated public Builder setClipRelativeToDefaultPosition(boolean relativeToDefaultPosition) { - this.clipRelativeToDefaultPosition = relativeToDefaultPosition; + clippingProperties.setRelativeToDefaultPosition(relativeToDefaultPosition); return this; } /** - * Sets whether the start point is guaranteed to be a key frame. If {@code false}, the playback - * transition into the clip may not be seamless (Default: {@code false}). + * @deprecated Use {@link #setClippingProperties(ClippingProperties)} and {@link + * ClippingProperties.Builder#setStartsAtKeyFrame(boolean)} instead. */ + @Deprecated public Builder setClipStartsAtKeyFrame(boolean startsAtKeyFrame) { - this.clipStartsAtKeyFrame = startsAtKeyFrame; + clippingProperties.setStartsAtKeyFrame(startsAtKeyFrame); return this; } @@ -503,12 +504,7 @@ public final class MediaItem implements Bundleable { } return new MediaItem( mediaId != null ? mediaId : DEFAULT_MEDIA_ID, - new ClippingProperties( - clipStartPositionMs, - clipEndPositionMs, - clipRelativeToLiveWindow, - clipRelativeToDefaultPosition, - clipStartsAtKeyFrame), + clippingProperties.build(), playbackProperties, liveConfiguration.build(), mediaMetadata != null ? mediaMetadata : MediaMetadata.EMPTY); @@ -1347,7 +1343,89 @@ public final class MediaItem implements Bundleable { /** Optionally clips the media item to a custom start and end position. */ public static final class ClippingProperties implements Bundleable { + /** A clipping properties configuration with default values. */ + public static final ClippingProperties UNSET = new ClippingProperties.Builder().build(); + + /** Builder for {@link ClippingProperties} instances. */ + public static final class Builder { + private long startPositionMs; + private long endPositionMs; + private boolean relativeToLiveWindow; + private boolean relativeToDefaultPosition; + private boolean startsAtKeyFrame; + + /** Constructs an instance. */ + public Builder() { + endPositionMs = C.TIME_END_OF_SOURCE; + } + + private Builder(ClippingProperties clippingProperties) { + startPositionMs = clippingProperties.startPositionMs; + endPositionMs = clippingProperties.endPositionMs; + relativeToLiveWindow = clippingProperties.relativeToLiveWindow; + relativeToDefaultPosition = clippingProperties.relativeToDefaultPosition; + startsAtKeyFrame = clippingProperties.startsAtKeyFrame; + } + + /** + * Sets the optional start position in milliseconds which must be a value larger than or equal + * to zero (Default: 0). + */ + public Builder setStartPositionMs(@IntRange(from = 0) long startPositionMs) { + Assertions.checkArgument(startPositionMs >= 0); + this.startPositionMs = startPositionMs; + return this; + } + + /** + * Sets the optional end position in milliseconds which must be a value larger than or equal + * to zero, or {@link C#TIME_END_OF_SOURCE} to end when playback reaches the end of media + * (Default: {@link C#TIME_END_OF_SOURCE}). + */ + public Builder setEndPositionMs(long endPositionMs) { + Assertions.checkArgument(endPositionMs == C.TIME_END_OF_SOURCE || endPositionMs >= 0); + this.endPositionMs = endPositionMs; + return this; + } + + /** + * Sets whether the start/end positions should move with the live window for live streams. If + * {@code false}, live streams end when playback reaches the end position in live window seen + * when the media is first loaded (Default: {@code false}). + */ + public Builder setRelativeToLiveWindow(boolean relativeToLiveWindow) { + this.relativeToLiveWindow = relativeToLiveWindow; + return this; + } + + /** + * Sets whether the start position and the end position are relative to the default position + * in the window (Default: {@code false}). + */ + public Builder setRelativeToDefaultPosition(boolean relativeToDefaultPosition) { + this.relativeToDefaultPosition = relativeToDefaultPosition; + return this; + } + + /** + * Sets whether the start point is guaranteed to be a key frame. If {@code false}, the + * playback transition into the clip may not be seamless (Default: {@code false}). + */ + public Builder setStartsAtKeyFrame(boolean startsAtKeyFrame) { + this.startsAtKeyFrame = startsAtKeyFrame; + return this; + } + + /** + * Returns a {@link ClippingProperties} instance initialized with the values of this builder. + */ + public ClippingProperties build() { + return new ClippingProperties(this); + } + } + /** The start position in milliseconds. This is a value larger than or equal to zero. */ + @IntRange(from = 0) public final long startPositionMs; /** @@ -1371,17 +1449,17 @@ public final class MediaItem implements Bundleable { /** Sets whether the start point is guaranteed to be a key frame. */ public final boolean startsAtKeyFrame; - private ClippingProperties( - long startPositionMs, - long endPositionMs, - boolean relativeToLiveWindow, - boolean relativeToDefaultPosition, - boolean startsAtKeyFrame) { - this.startPositionMs = startPositionMs; - this.endPositionMs = endPositionMs; - this.relativeToLiveWindow = relativeToLiveWindow; - this.relativeToDefaultPosition = relativeToDefaultPosition; - this.startsAtKeyFrame = startsAtKeyFrame; + private ClippingProperties(Builder builder) { + this.startPositionMs = builder.startPositionMs; + this.endPositionMs = builder.endPositionMs; + this.relativeToLiveWindow = builder.relativeToLiveWindow; + this.relativeToDefaultPosition = builder.relativeToDefaultPosition; + this.startsAtKeyFrame = builder.startsAtKeyFrame; + } + + /** Returns a {@link Builder} initialized with the values of this instance. */ + public Builder buildUpon() { + return new Builder(this); } @Override @@ -1445,13 +1523,20 @@ public final class MediaItem implements Bundleable { /** Object that can restore {@link ClippingProperties} from a {@link Bundle}. */ public static final Creator CREATOR = bundle -> - new ClippingProperties( - bundle.getLong(keyForField(FIELD_START_POSITION_MS), /* defaultValue= */ 0), - bundle.getLong( - keyForField(FIELD_END_POSITION_MS), /* defaultValue= */ C.TIME_END_OF_SOURCE), - bundle.getBoolean(keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), false), - bundle.getBoolean(keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), false), - bundle.getBoolean(keyForField(FIELD_STARTS_AT_KEY_FRAME), false)); + new ClippingProperties.Builder() + .setStartPositionMs( + bundle.getLong(keyForField(FIELD_START_POSITION_MS), /* defaultValue= */ 0)) + .setEndPositionMs( + bundle.getLong( + keyForField(FIELD_END_POSITION_MS), + /* defaultValue= */ C.TIME_END_OF_SOURCE)) + .setRelativeToLiveWindow( + bundle.getBoolean(keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), false)) + .setRelativeToDefaultPosition( + bundle.getBoolean(keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), false)) + .setStartsAtKeyFrame( + bundle.getBoolean(keyForField(FIELD_STARTS_AT_KEY_FRAME), false)) + .build(); private static String keyForField(@ClippingProperties.FieldNumber int field) { return Integer.toString(field, Character.MAX_RADIX); @@ -1589,13 +1674,7 @@ public final class MediaItem implements Bundleable { Bundle clippingPropertiesBundle = bundle.getBundle(keyForField(FIELD_CLIPPING_PROPERTIES)); ClippingProperties clippingProperties; if (clippingPropertiesBundle == null) { - clippingProperties = - new ClippingProperties( - /* startPositionMs= */ 0, - /* endPositionMs= */ C.TIME_END_OF_SOURCE, - /* relativeToLiveWindow= */ false, - /* relativeToDefaultPosition= */ false, - /* startsAtKeyFrame= */ false); + clippingProperties = ClippingProperties.UNSET; } else { clippingProperties = ClippingProperties.CREATOR.fromBundle(clippingPropertiesBundle); } diff --git a/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java b/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java index e503dfa4e4..b7760bf56a 100644 --- a/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java +++ b/library/common/src/test/java/com/google/android/exoplayer2/MediaItemTest.java @@ -307,6 +307,58 @@ public class MediaItemTest { } @Test + public void builderSetClippingProperties() { + MediaItem mediaItem = + new MediaItem.Builder() + .setUri(URI_STRING) + .setClippingProperties( + new MediaItem.ClippingProperties.Builder() + .setStartPositionMs(1000L) + .setEndPositionMs(2000L) + .setRelativeToLiveWindow(true) + .setRelativeToDefaultPosition(true) + .setStartsAtKeyFrame(true) + .build()) + .build(); + + assertThat(mediaItem.clippingProperties.startPositionMs).isEqualTo(1000L); + assertThat(mediaItem.clippingProperties.endPositionMs).isEqualTo(2000L); + assertThat(mediaItem.clippingProperties.relativeToLiveWindow).isTrue(); + assertThat(mediaItem.clippingProperties.relativeToDefaultPosition).isTrue(); + assertThat(mediaItem.clippingProperties.startsAtKeyFrame).isTrue(); + } + + @Test + public void clippingPropertiesDefaults() { + MediaItem.ClippingProperties clippingProperties = + new MediaItem.ClippingProperties.Builder().build(); + + assertThat(clippingProperties.startPositionMs).isEqualTo(0L); + assertThat(clippingProperties.endPositionMs).isEqualTo(C.TIME_END_OF_SOURCE); + assertThat(clippingProperties.relativeToLiveWindow).isFalse(); + assertThat(clippingProperties.relativeToDefaultPosition).isFalse(); + assertThat(clippingProperties.startsAtKeyFrame).isFalse(); + assertThat(clippingProperties).isEqualTo(MediaItem.ClippingProperties.UNSET); + } + + @Test + public void clippingPropertiesBuilder_throwsOnInvalidValues() { + MediaItem.ClippingProperties.Builder clippingPropertiesBuilder = + new MediaItem.ClippingProperties.Builder(); + assertThrows( + IllegalArgumentException.class, () -> clippingPropertiesBuilder.setStartPositionMs(-1)); + assertThrows( + IllegalArgumentException.class, () -> clippingPropertiesBuilder.setEndPositionMs(-1)); + + MediaItem.ClippingProperties clippingProperties = clippingPropertiesBuilder.build(); + + // Check neither of the setters succeeded in mutating the builder. + assertThat(clippingProperties.startPositionMs).isEqualTo(0L); + assertThat(clippingProperties.endPositionMs).isEqualTo(C.TIME_END_OF_SOURCE); + } + + @Test + @SuppressWarnings("deprecation") // Testing deprecated setter. public void builderSetStartPositionMs_setsStartPositionMs() { MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).setClipStartPositionMs(1000L).build(); @@ -315,13 +367,7 @@ public class MediaItemTest { } @Test - public void builderSetStartPositionMs_zeroByDefault() { - MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build(); - - assertThat(mediaItem.clippingProperties.startPositionMs).isEqualTo(0); - } - - @Test + @SuppressWarnings("deprecation") // Testing deprecated setter. public void builderSetStartPositionMs_negativeValue_throws() { MediaItem.Builder builder = new MediaItem.Builder(); @@ -329,6 +375,7 @@ public class MediaItemTest { } @Test + @SuppressWarnings("deprecation") // Testing deprecated setter. public void builderSetEndPositionMs_setsEndPositionMs() { MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).setClipEndPositionMs(1000L).build(); @@ -337,13 +384,7 @@ public class MediaItemTest { } @Test - public void builderSetEndPositionMs_timeEndOfSourceByDefault() { - MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build(); - - assertThat(mediaItem.clippingProperties.endPositionMs).isEqualTo(C.TIME_END_OF_SOURCE); - } - - @Test + @SuppressWarnings("deprecation") // Testing deprecated setter. public void builderSetEndPositionMs_timeEndOfSource_setsEndPositionMs() { MediaItem mediaItem = new MediaItem.Builder() @@ -356,6 +397,7 @@ public class MediaItemTest { } @Test + @SuppressWarnings("deprecation") // Testing deprecated setter. public void builderSetEndPositionMs_negativeValue_throws() { MediaItem.Builder builder = new MediaItem.Builder(); @@ -363,6 +405,7 @@ public class MediaItemTest { } @Test + @SuppressWarnings("deprecation") // Testing deprecated setter. public void builderSetClippingFlags_setsClippingFlags() { MediaItem mediaItem = new MediaItem.Builder() @@ -561,11 +604,14 @@ public class MediaItemTest { new MediaItem.Builder() .setAdsConfiguration( new MediaItem.AdsConfiguration.Builder(Uri.parse(URI_STRING)).build()) - .setClipEndPositionMs(1000) - .setClipRelativeToDefaultPosition(true) - .setClipRelativeToLiveWindow(true) - .setClipStartPositionMs(100) - .setClipStartsAtKeyFrame(true) + .setClippingProperties( + new MediaItem.ClippingProperties.Builder() + .setEndPositionMs(1000) + .setRelativeToDefaultPosition(true) + .setRelativeToLiveWindow(true) + .setStartPositionMs(100) + .setStartsAtKeyFrame(true) + .build()) .setCustomCacheKey("key") .setDrmConfiguration( new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)