add start and end position to media item
PiperOrigin-RevId: 304795753
This commit is contained in:
parent
8583cd92c7
commit
fc785871b3
@ -57,6 +57,11 @@ public final class MediaItem {
|
||||
@Nullable private String mediaId;
|
||||
@Nullable private Uri sourceUri;
|
||||
@Nullable private String mimeType;
|
||||
private long clipStartPositionMs;
|
||||
private long clipEndPositionMs;
|
||||
private boolean clipRelativeToLiveWindow;
|
||||
private boolean clipRelativeToDefaultPosition;
|
||||
private boolean clipStartsAtKeyFrame;
|
||||
@Nullable private Uri drmLicenseUri;
|
||||
private Map<String, String> drmLicenseRequestHeaders;
|
||||
@Nullable private UUID drmUuid;
|
||||
@ -74,6 +79,7 @@ public final class MediaItem {
|
||||
subtitles = Collections.emptyList();
|
||||
drmSessionForClearTypes = Collections.emptyList();
|
||||
drmLicenseRequestHeaders = Collections.emptyMap();
|
||||
clipEndPositionMs = C.TIME_END_OF_SOURCE;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -117,6 +123,55 @@ public final class MediaItem {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional start position in milliseconds which must be a value larger than or equal
|
||||
* to zero (Default: 0).
|
||||
*/
|
||||
public Builder setClipStartPositionMs(long startPositionMs) {
|
||||
Assertions.checkArgument(startPositionMs >= 0);
|
||||
this.clipStartPositionMs = 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 setClipEndPositionMs(long endPositionMs) {
|
||||
Assertions.checkArgument(endPositionMs == C.TIME_END_OF_SOURCE || endPositionMs >= 0);
|
||||
this.clipEndPositionMs = 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 setClipRelativeToLiveWindow(boolean relativeToLiveWindow) {
|
||||
this.clipRelativeToLiveWindow = 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 setClipRelativeToDefaultPosition(boolean relativeToDefaultPosition) {
|
||||
this.clipRelativeToDefaultPosition = 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 setClipStartsAtKeyFrame(boolean startsAtKeyFrame) {
|
||||
this.clipStartsAtKeyFrame = startsAtKeyFrame;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the optional license server {@link Uri}. If a license uri is set, the {@link
|
||||
* DrmConfiguration#uuid} needs to be specified as well.
|
||||
@ -303,6 +358,12 @@ public final class MediaItem {
|
||||
}
|
||||
return new MediaItem(
|
||||
Assertions.checkNotNull(mediaId),
|
||||
new ClippingProperties(
|
||||
clipStartPositionMs,
|
||||
clipEndPositionMs,
|
||||
clipRelativeToLiveWindow,
|
||||
clipRelativeToDefaultPosition,
|
||||
clipStartsAtKeyFrame),
|
||||
playbackProperties,
|
||||
mediaMetadata != null ? mediaMetadata : new MediaMetadata.Builder().build());
|
||||
}
|
||||
@ -521,6 +582,75 @@ public final class MediaItem {
|
||||
}
|
||||
}
|
||||
|
||||
/** Optionally clips the media item to a custom start and end position. */
|
||||
public static final class ClippingProperties {
|
||||
|
||||
/** The start position in milliseconds. This is a value larger than or equal to zero. */
|
||||
public final long startPositionMs;
|
||||
|
||||
/**
|
||||
* The end position in milliseconds. This is a value larger than or equal to zero or {@link
|
||||
* C#TIME_END_OF_SOURCE} to play to the end of the stream.
|
||||
*/
|
||||
public final long endPositionMs;
|
||||
|
||||
/**
|
||||
* Whether the clipping of active media periods moves with a live window. If {@code false},
|
||||
* playback ends when it reaches {@link #endPositionMs}.
|
||||
*/
|
||||
public final boolean relativeToLiveWindow;
|
||||
|
||||
/**
|
||||
* Whether {@link #startPositionMs} and {@link #endPositionMs} are relative to the default
|
||||
* position.
|
||||
*/
|
||||
public final boolean relativeToDefaultPosition;
|
||||
|
||||
/** 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (!(obj instanceof ClippingProperties)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ClippingProperties other = (ClippingProperties) obj;
|
||||
|
||||
return startPositionMs == other.startPositionMs
|
||||
&& endPositionMs == other.endPositionMs
|
||||
&& relativeToLiveWindow == other.relativeToLiveWindow
|
||||
&& relativeToDefaultPosition == other.relativeToDefaultPosition
|
||||
&& startsAtKeyFrame == other.startsAtKeyFrame;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = Long.valueOf(startPositionMs).hashCode();
|
||||
result = 31 * result + Long.valueOf(endPositionMs).hashCode();
|
||||
result = 31 * result + (relativeToLiveWindow ? 1 : 0);
|
||||
result = 31 * result + (relativeToDefaultPosition ? 1 : 0);
|
||||
result = 31 * result + (startsAtKeyFrame ? 1 : 0);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/** Identifies the media item. */
|
||||
public final String mediaId;
|
||||
|
||||
@ -530,13 +660,18 @@ public final class MediaItem {
|
||||
/** The media metadata. */
|
||||
public final MediaMetadata mediaMetadata;
|
||||
|
||||
/** The clipping properties. */
|
||||
public final ClippingProperties clippingProperties;
|
||||
|
||||
private MediaItem(
|
||||
String mediaId,
|
||||
ClippingProperties clippingProperties,
|
||||
@Nullable PlaybackProperties playbackProperties,
|
||||
MediaMetadata mediaMetadata) {
|
||||
this.mediaId = mediaId;
|
||||
this.playbackProperties = playbackProperties;
|
||||
this.mediaMetadata = mediaMetadata;
|
||||
this.clippingProperties = clippingProperties;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -551,6 +686,7 @@ public final class MediaItem {
|
||||
MediaItem other = (MediaItem) obj;
|
||||
|
||||
return Util.areEqual(mediaId, other.mediaId)
|
||||
&& clippingProperties.equals(other.clippingProperties)
|
||||
&& Util.areEqual(playbackProperties, other.playbackProperties)
|
||||
&& Util.areEqual(mediaMetadata, other.mediaMetadata);
|
||||
}
|
||||
@ -559,6 +695,7 @@ public final class MediaItem {
|
||||
public int hashCode() {
|
||||
int result = mediaId.hashCode();
|
||||
result = 31 * result + (playbackProperties != null ? playbackProperties.hashCode() : 0);
|
||||
result = 31 * result + clippingProperties.hashCode();
|
||||
result = 31 * result + mediaMetadata.hashCode();
|
||||
return result;
|
||||
}
|
||||
|
@ -216,11 +216,11 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
|
||||
MediaSource leafMediaSource = mediaSourceFactory.createMediaSource(mediaItem);
|
||||
|
||||
if (mediaItem.playbackProperties.subtitles.isEmpty()) {
|
||||
return leafMediaSource;
|
||||
List<MediaItem.Subtitle> subtitles = mediaItem.playbackProperties.subtitles;
|
||||
if (subtitles.isEmpty()) {
|
||||
return maybeClipMediaSource(mediaItem, leafMediaSource);
|
||||
}
|
||||
|
||||
List<MediaItem.Subtitle> subtitles = mediaItem.playbackProperties.subtitles;
|
||||
MediaSource[] mediaSources = new MediaSource[subtitles.size() + 1];
|
||||
mediaSources[0] = leafMediaSource;
|
||||
SingleSampleMediaSource.Factory singleSampleSourceFactory =
|
||||
@ -234,9 +234,10 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
.setSelectionFlags(subtitle.selectionFlags)
|
||||
.build();
|
||||
mediaSources[i + 1] =
|
||||
singleSampleSourceFactory.createMediaSource(subtitle.uri, subtitleFormat, C.TIME_UNSET);
|
||||
singleSampleSourceFactory.createMediaSource(
|
||||
subtitle.uri, subtitleFormat, /* durationUs= */ C.TIME_UNSET);
|
||||
}
|
||||
return new MergingMediaSource(mediaSources);
|
||||
return maybeClipMediaSource(mediaItem, new MergingMediaSource(mediaSources));
|
||||
}
|
||||
|
||||
// internal methods
|
||||
@ -269,6 +270,21 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
return drmCallback;
|
||||
}
|
||||
|
||||
private static MediaSource maybeClipMediaSource(MediaItem mediaItem, MediaSource mediaSource) {
|
||||
if (mediaItem.clippingProperties.startPositionMs == 0
|
||||
&& mediaItem.clippingProperties.endPositionMs == C.TIME_END_OF_SOURCE
|
||||
&& !mediaItem.clippingProperties.relativeToDefaultPosition) {
|
||||
return mediaSource;
|
||||
}
|
||||
return new ClippingMediaSource(
|
||||
mediaSource,
|
||||
C.msToUs(mediaItem.clippingProperties.startPositionMs),
|
||||
C.msToUs(mediaItem.clippingProperties.endPositionMs),
|
||||
/* enableInitialDiscontinuity= */ !mediaItem.clippingProperties.startsAtKeyFrame,
|
||||
/* allowDynamicClippingUpdates= */ mediaItem.clippingProperties.relativeToLiveWindow,
|
||||
mediaItem.clippingProperties.relativeToDefaultPosition);
|
||||
}
|
||||
|
||||
private static SparseArray<MediaSourceFactory> loadDelegates(
|
||||
DataSource.Factory dataSourceFactory) {
|
||||
SparseArray<MediaSourceFactory> factories = new SparseArray<>();
|
||||
|
@ -187,6 +187,77 @@ public class MediaItemTest {
|
||||
assertThat(mediaItem.playbackProperties.tag).isEqualTo(tag);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderSetStartPositionMs_setsStartPositionMs() {
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder().setSourceUri(URI_STRING).setClipStartPositionMs(1000L).build();
|
||||
|
||||
assertThat(mediaItem.clippingProperties.startPositionMs).isEqualTo(1000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderSetStartPositionMs_zeroByDefault() {
|
||||
MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_STRING).build();
|
||||
|
||||
assertThat(mediaItem.clippingProperties.startPositionMs).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderSetStartPositionMs_negativeValue_throws() {
|
||||
MediaItem.Builder builder = new MediaItem.Builder();
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> builder.setClipStartPositionMs(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderSetEndPositionMs_setsEndPositionMs() {
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder().setSourceUri(URI_STRING).setClipEndPositionMs(1000L).build();
|
||||
|
||||
assertThat(mediaItem.clippingProperties.endPositionMs).isEqualTo(1000L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderSetEndPositionMs_timeEndOfSourceByDefault() {
|
||||
MediaItem mediaItem = new MediaItem.Builder().setSourceUri(URI_STRING).build();
|
||||
|
||||
assertThat(mediaItem.clippingProperties.endPositionMs).isEqualTo(C.TIME_END_OF_SOURCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderSetEndPositionMs_timeEndOfSource_setsEndPositionMs() {
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setSourceUri(URI_STRING)
|
||||
.setClipEndPositionMs(1000)
|
||||
.setClipEndPositionMs(C.TIME_END_OF_SOURCE)
|
||||
.build();
|
||||
|
||||
assertThat(mediaItem.clippingProperties.endPositionMs).isEqualTo(C.TIME_END_OF_SOURCE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderSetEndPositionMs_negativeValue_throws() {
|
||||
MediaItem.Builder builder = new MediaItem.Builder();
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () -> builder.setClipEndPositionMs(-1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderSetClippingFlags_setsClippingFlags() {
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setSourceUri(URI_STRING)
|
||||
.setClipRelativeToDefaultPosition(true)
|
||||
.setClipRelativeToLiveWindow(true)
|
||||
.setClipStartsAtKeyFrame(true)
|
||||
.build();
|
||||
|
||||
assertThat(mediaItem.clippingProperties.relativeToDefaultPosition).isTrue();
|
||||
assertThat(mediaItem.clippingProperties.relativeToLiveWindow).isTrue();
|
||||
assertThat(mediaItem.clippingProperties.startsAtKeyFrame).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void builderSetMediaMetadata_setsMetadata() {
|
||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
|
||||
|
@ -122,6 +122,60 @@ public final class DefaultMediaSourceFactoryTest {
|
||||
assertThat(mediaSource.getTag()).isEqualTo(tag);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMediaSource_withStartPosition_isClippingMediaSource() {
|
||||
DefaultMediaSourceFactory defaultMediaSourceFactory =
|
||||
DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder().setSourceUri(URI_MEDIA).setClipStartPositionMs(1000L).build();
|
||||
|
||||
MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
|
||||
|
||||
assertThat(mediaSource).isInstanceOf(ClippingMediaSource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMediaSource_withEndPosition_isClippingMediaSource() {
|
||||
DefaultMediaSourceFactory defaultMediaSourceFactory =
|
||||
DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder().setSourceUri(URI_MEDIA).setClipEndPositionMs(1000L).build();
|
||||
|
||||
MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
|
||||
|
||||
assertThat(mediaSource).isInstanceOf(ClippingMediaSource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMediaSource_relativeToDefaultPosition_isClippingMediaSource() {
|
||||
DefaultMediaSourceFactory defaultMediaSourceFactory =
|
||||
DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setSourceUri(URI_MEDIA)
|
||||
.setClipRelativeToDefaultPosition(true)
|
||||
.build();
|
||||
|
||||
MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
|
||||
|
||||
assertThat(mediaSource).isInstanceOf(ClippingMediaSource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMediaSource_defaultToEnd_isNotClippingMediaSource() {
|
||||
DefaultMediaSourceFactory defaultMediaSourceFactory =
|
||||
DefaultMediaSourceFactory.newInstance(ApplicationProvider.getApplicationContext());
|
||||
MediaItem mediaItem =
|
||||
new MediaItem.Builder()
|
||||
.setSourceUri(URI_MEDIA)
|
||||
.setClipEndPositionMs(C.TIME_END_OF_SOURCE)
|
||||
.build();
|
||||
|
||||
MediaSource mediaSource = defaultMediaSourceFactory.createMediaSource(mediaItem);
|
||||
|
||||
assertThat(mediaSource).isInstanceOf(ProgressiveMediaSource.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getSupportedTypes_coreModule_onlyOther() {
|
||||
int[] supportedTypes =
|
||||
|
Loading…
x
Reference in New Issue
Block a user