Add MediaItem.ClippingProperties.Builder

PiperOrigin-RevId: 398232186
This commit is contained in:
ibaker 2021-09-22 15:12:09 +01:00 committed by bachinger
parent 1372300073
commit 59cd783dd4
2 changed files with 205 additions and 80 deletions

View File

@ -67,11 +67,9 @@ public final class MediaItem implements Bundleable {
@Nullable private String mediaId; @Nullable private String mediaId;
@Nullable private Uri uri; @Nullable private Uri uri;
@Nullable private String mimeType; @Nullable private String mimeType;
private long clipStartPositionMs; // TODO: Change this to ClippingProperties once all the deprecated individual setters are
private long clipEndPositionMs; // removed.
private boolean clipRelativeToLiveWindow; private ClippingProperties.Builder clippingProperties;
private boolean clipRelativeToDefaultPosition;
private boolean clipStartsAtKeyFrame;
// TODO: Change this to @Nullable DrmConfiguration once all the deprecated individual setters // TODO: Change this to @Nullable DrmConfiguration once all the deprecated individual setters
// are removed. // are removed.
private DrmConfiguration.Builder drmConfiguration; private DrmConfiguration.Builder drmConfiguration;
@ -88,7 +86,7 @@ public final class MediaItem implements Bundleable {
/** Creates a builder. */ /** Creates a builder. */
@SuppressWarnings("deprecation") // Temporarily uses DrmConfiguration.Builder() constructor. @SuppressWarnings("deprecation") // Temporarily uses DrmConfiguration.Builder() constructor.
public Builder() { public Builder() {
clipEndPositionMs = C.TIME_END_OF_SOURCE; clippingProperties = new ClippingProperties.Builder();
drmConfiguration = new DrmConfiguration.Builder(); drmConfiguration = new DrmConfiguration.Builder();
streamKeys = Collections.emptyList(); streamKeys = Collections.emptyList();
subtitles = Collections.emptyList(); subtitles = Collections.emptyList();
@ -97,11 +95,7 @@ public final class MediaItem implements Bundleable {
private Builder(MediaItem mediaItem) { private Builder(MediaItem mediaItem) {
this(); this();
clipEndPositionMs = mediaItem.clippingProperties.endPositionMs; clippingProperties = mediaItem.clippingProperties.buildUpon();
clipRelativeToLiveWindow = mediaItem.clippingProperties.relativeToLiveWindow;
clipRelativeToDefaultPosition = mediaItem.clippingProperties.relativeToDefaultPosition;
clipStartPositionMs = mediaItem.clippingProperties.startPositionMs;
clipStartsAtKeyFrame = mediaItem.clippingProperties.startsAtKeyFrame;
mediaId = mediaItem.mediaId; mediaId = mediaItem.mediaId;
mediaMetadata = mediaItem.mediaMetadata; mediaMetadata = mediaItem.mediaMetadata;
liveConfiguration = mediaItem.liveConfiguration.buildUpon(); liveConfiguration = mediaItem.liveConfiguration.buildUpon();
@ -168,52 +162,59 @@ public final class MediaItem implements Bundleable {
return this; 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 * @deprecated Use {@link #setClippingProperties(ClippingProperties)} and {@link
* to zero (Default: 0). * ClippingProperties.Builder#setStartPositionMs(long)} instead.
*/ */
@Deprecated
public Builder setClipStartPositionMs(@IntRange(from = 0) long startPositionMs) { public Builder setClipStartPositionMs(@IntRange(from = 0) long startPositionMs) {
Assertions.checkArgument(startPositionMs >= 0); clippingProperties.setStartPositionMs(startPositionMs);
this.clipStartPositionMs = startPositionMs;
return this; return this;
} }
/** /**
* Sets the optional end position in milliseconds which must be a value larger than or equal to * @deprecated Use {@link #setClippingProperties(ClippingProperties)} and {@link
* zero, or {@link C#TIME_END_OF_SOURCE} to end when playback reaches the end of media (Default: * ClippingProperties.Builder#setEndPositionMs(long)} instead.
* {@link C#TIME_END_OF_SOURCE}).
*/ */
@Deprecated
public Builder setClipEndPositionMs(long endPositionMs) { public Builder setClipEndPositionMs(long endPositionMs) {
Assertions.checkArgument(endPositionMs == C.TIME_END_OF_SOURCE || endPositionMs >= 0); clippingProperties.setEndPositionMs(endPositionMs);
this.clipEndPositionMs = endPositionMs;
return this; return this;
} }
/** /**
* Sets whether the start/end positions should move with the live window for live streams. If * @deprecated Use {@link #setClippingProperties(ClippingProperties)} and {@link
* {@code false}, live streams end when playback reaches the end position in live window seen * ClippingProperties.Builder#setRelativeToLiveWindow(boolean)} instead.
* when the media is first loaded (Default: {@code false}).
*/ */
@Deprecated
public Builder setClipRelativeToLiveWindow(boolean relativeToLiveWindow) { public Builder setClipRelativeToLiveWindow(boolean relativeToLiveWindow) {
this.clipRelativeToLiveWindow = relativeToLiveWindow; clippingProperties.setRelativeToLiveWindow(relativeToLiveWindow);
return this; return this;
} }
/** /**
* Sets whether the start position and the end position are relative to the default position in * @deprecated Use {@link #setClippingProperties(ClippingProperties)} and {@link
* the window (Default: {@code false}). * ClippingProperties.Builder#setRelativeToDefaultPosition(boolean)} instead.
*/ */
@Deprecated
public Builder setClipRelativeToDefaultPosition(boolean relativeToDefaultPosition) { public Builder setClipRelativeToDefaultPosition(boolean relativeToDefaultPosition) {
this.clipRelativeToDefaultPosition = relativeToDefaultPosition; clippingProperties.setRelativeToDefaultPosition(relativeToDefaultPosition);
return this; return this;
} }
/** /**
* Sets whether the start point is guaranteed to be a key frame. If {@code false}, the playback * @deprecated Use {@link #setClippingProperties(ClippingProperties)} and {@link
* transition into the clip may not be seamless (Default: {@code false}). * ClippingProperties.Builder#setStartsAtKeyFrame(boolean)} instead.
*/ */
@Deprecated
public Builder setClipStartsAtKeyFrame(boolean startsAtKeyFrame) { public Builder setClipStartsAtKeyFrame(boolean startsAtKeyFrame) {
this.clipStartsAtKeyFrame = startsAtKeyFrame; clippingProperties.setStartsAtKeyFrame(startsAtKeyFrame);
return this; return this;
} }
@ -503,12 +504,7 @@ public final class MediaItem implements Bundleable {
} }
return new MediaItem( return new MediaItem(
mediaId != null ? mediaId : DEFAULT_MEDIA_ID, mediaId != null ? mediaId : DEFAULT_MEDIA_ID,
new ClippingProperties( clippingProperties.build(),
clipStartPositionMs,
clipEndPositionMs,
clipRelativeToLiveWindow,
clipRelativeToDefaultPosition,
clipStartsAtKeyFrame),
playbackProperties, playbackProperties,
liveConfiguration.build(), liveConfiguration.build(),
mediaMetadata != null ? mediaMetadata : MediaMetadata.EMPTY); 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. */ /** Optionally clips the media item to a custom start and end position. */
public static final class ClippingProperties implements Bundleable { 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. */ /** The start position in milliseconds. This is a value larger than or equal to zero. */
@IntRange(from = 0)
public final long startPositionMs; 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. */ /** Sets whether the start point is guaranteed to be a key frame. */
public final boolean startsAtKeyFrame; public final boolean startsAtKeyFrame;
private ClippingProperties( private ClippingProperties(Builder builder) {
long startPositionMs, this.startPositionMs = builder.startPositionMs;
long endPositionMs, this.endPositionMs = builder.endPositionMs;
boolean relativeToLiveWindow, this.relativeToLiveWindow = builder.relativeToLiveWindow;
boolean relativeToDefaultPosition, this.relativeToDefaultPosition = builder.relativeToDefaultPosition;
boolean startsAtKeyFrame) { this.startsAtKeyFrame = builder.startsAtKeyFrame;
this.startPositionMs = startPositionMs; }
this.endPositionMs = endPositionMs;
this.relativeToLiveWindow = relativeToLiveWindow; /** Returns a {@link Builder} initialized with the values of this instance. */
this.relativeToDefaultPosition = relativeToDefaultPosition; public Builder buildUpon() {
this.startsAtKeyFrame = startsAtKeyFrame; return new Builder(this);
} }
@Override @Override
@ -1445,13 +1523,20 @@ public final class MediaItem implements Bundleable {
/** Object that can restore {@link ClippingProperties} from a {@link Bundle}. */ /** Object that can restore {@link ClippingProperties} from a {@link Bundle}. */
public static final Creator<ClippingProperties> CREATOR = public static final Creator<ClippingProperties> CREATOR =
bundle -> bundle ->
new ClippingProperties( new ClippingProperties.Builder()
bundle.getLong(keyForField(FIELD_START_POSITION_MS), /* defaultValue= */ 0), .setStartPositionMs(
bundle.getLong(keyForField(FIELD_START_POSITION_MS), /* defaultValue= */ 0))
.setEndPositionMs(
bundle.getLong( bundle.getLong(
keyForField(FIELD_END_POSITION_MS), /* defaultValue= */ C.TIME_END_OF_SOURCE), keyForField(FIELD_END_POSITION_MS),
bundle.getBoolean(keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), false), /* defaultValue= */ C.TIME_END_OF_SOURCE))
bundle.getBoolean(keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), false), .setRelativeToLiveWindow(
bundle.getBoolean(keyForField(FIELD_STARTS_AT_KEY_FRAME), false)); 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) { private static String keyForField(@ClippingProperties.FieldNumber int field) {
return Integer.toString(field, Character.MAX_RADIX); 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)); Bundle clippingPropertiesBundle = bundle.getBundle(keyForField(FIELD_CLIPPING_PROPERTIES));
ClippingProperties clippingProperties; ClippingProperties clippingProperties;
if (clippingPropertiesBundle == null) { if (clippingPropertiesBundle == null) {
clippingProperties = clippingProperties = ClippingProperties.UNSET;
new ClippingProperties(
/* startPositionMs= */ 0,
/* endPositionMs= */ C.TIME_END_OF_SOURCE,
/* relativeToLiveWindow= */ false,
/* relativeToDefaultPosition= */ false,
/* startsAtKeyFrame= */ false);
} else { } else {
clippingProperties = ClippingProperties.CREATOR.fromBundle(clippingPropertiesBundle); clippingProperties = ClippingProperties.CREATOR.fromBundle(clippingPropertiesBundle);
} }

View File

@ -307,6 +307,58 @@ public class MediaItemTest {
} }
@Test @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() { public void builderSetStartPositionMs_setsStartPositionMs() {
MediaItem mediaItem = MediaItem mediaItem =
new MediaItem.Builder().setUri(URI_STRING).setClipStartPositionMs(1000L).build(); new MediaItem.Builder().setUri(URI_STRING).setClipStartPositionMs(1000L).build();
@ -315,13 +367,7 @@ public class MediaItemTest {
} }
@Test @Test
public void builderSetStartPositionMs_zeroByDefault() { @SuppressWarnings("deprecation") // Testing deprecated setter.
MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build();
assertThat(mediaItem.clippingProperties.startPositionMs).isEqualTo(0);
}
@Test
public void builderSetStartPositionMs_negativeValue_throws() { public void builderSetStartPositionMs_negativeValue_throws() {
MediaItem.Builder builder = new MediaItem.Builder(); MediaItem.Builder builder = new MediaItem.Builder();
@ -329,6 +375,7 @@ public class MediaItemTest {
} }
@Test @Test
@SuppressWarnings("deprecation") // Testing deprecated setter.
public void builderSetEndPositionMs_setsEndPositionMs() { public void builderSetEndPositionMs_setsEndPositionMs() {
MediaItem mediaItem = MediaItem mediaItem =
new MediaItem.Builder().setUri(URI_STRING).setClipEndPositionMs(1000L).build(); new MediaItem.Builder().setUri(URI_STRING).setClipEndPositionMs(1000L).build();
@ -337,13 +384,7 @@ public class MediaItemTest {
} }
@Test @Test
public void builderSetEndPositionMs_timeEndOfSourceByDefault() { @SuppressWarnings("deprecation") // Testing deprecated setter.
MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build();
assertThat(mediaItem.clippingProperties.endPositionMs).isEqualTo(C.TIME_END_OF_SOURCE);
}
@Test
public void builderSetEndPositionMs_timeEndOfSource_setsEndPositionMs() { public void builderSetEndPositionMs_timeEndOfSource_setsEndPositionMs() {
MediaItem mediaItem = MediaItem mediaItem =
new MediaItem.Builder() new MediaItem.Builder()
@ -356,6 +397,7 @@ public class MediaItemTest {
} }
@Test @Test
@SuppressWarnings("deprecation") // Testing deprecated setter.
public void builderSetEndPositionMs_negativeValue_throws() { public void builderSetEndPositionMs_negativeValue_throws() {
MediaItem.Builder builder = new MediaItem.Builder(); MediaItem.Builder builder = new MediaItem.Builder();
@ -363,6 +405,7 @@ public class MediaItemTest {
} }
@Test @Test
@SuppressWarnings("deprecation") // Testing deprecated setter.
public void builderSetClippingFlags_setsClippingFlags() { public void builderSetClippingFlags_setsClippingFlags() {
MediaItem mediaItem = MediaItem mediaItem =
new MediaItem.Builder() new MediaItem.Builder()
@ -561,11 +604,14 @@ public class MediaItemTest {
new MediaItem.Builder() new MediaItem.Builder()
.setAdsConfiguration( .setAdsConfiguration(
new MediaItem.AdsConfiguration.Builder(Uri.parse(URI_STRING)).build()) new MediaItem.AdsConfiguration.Builder(Uri.parse(URI_STRING)).build())
.setClipEndPositionMs(1000) .setClippingProperties(
.setClipRelativeToDefaultPosition(true) new MediaItem.ClippingProperties.Builder()
.setClipRelativeToLiveWindow(true) .setEndPositionMs(1000)
.setClipStartPositionMs(100) .setRelativeToDefaultPosition(true)
.setClipStartsAtKeyFrame(true) .setRelativeToLiveWindow(true)
.setStartPositionMs(100)
.setStartsAtKeyFrame(true)
.build())
.setCustomCacheKey("key") .setCustomCacheKey("key")
.setDrmConfiguration( .setDrmConfiguration(
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID) new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)