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 String mediaId;
|
||||||
@Nullable private Uri sourceUri;
|
@Nullable private Uri sourceUri;
|
||||||
@Nullable private String mimeType;
|
@Nullable private String mimeType;
|
||||||
|
private long clipStartPositionMs;
|
||||||
|
private long clipEndPositionMs;
|
||||||
|
private boolean clipRelativeToLiveWindow;
|
||||||
|
private boolean clipRelativeToDefaultPosition;
|
||||||
|
private boolean clipStartsAtKeyFrame;
|
||||||
@Nullable private Uri drmLicenseUri;
|
@Nullable private Uri drmLicenseUri;
|
||||||
private Map<String, String> drmLicenseRequestHeaders;
|
private Map<String, String> drmLicenseRequestHeaders;
|
||||||
@Nullable private UUID drmUuid;
|
@Nullable private UUID drmUuid;
|
||||||
@ -74,6 +79,7 @@ public final class MediaItem {
|
|||||||
subtitles = Collections.emptyList();
|
subtitles = Collections.emptyList();
|
||||||
drmSessionForClearTypes = Collections.emptyList();
|
drmSessionForClearTypes = Collections.emptyList();
|
||||||
drmLicenseRequestHeaders = Collections.emptyMap();
|
drmLicenseRequestHeaders = Collections.emptyMap();
|
||||||
|
clipEndPositionMs = C.TIME_END_OF_SOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -117,6 +123,55 @@ public final class MediaItem {
|
|||||||
return this;
|
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
|
* Sets the optional license server {@link Uri}. If a license uri is set, the {@link
|
||||||
* DrmConfiguration#uuid} needs to be specified as well.
|
* DrmConfiguration#uuid} needs to be specified as well.
|
||||||
@ -303,6 +358,12 @@ public final class MediaItem {
|
|||||||
}
|
}
|
||||||
return new MediaItem(
|
return new MediaItem(
|
||||||
Assertions.checkNotNull(mediaId),
|
Assertions.checkNotNull(mediaId),
|
||||||
|
new ClippingProperties(
|
||||||
|
clipStartPositionMs,
|
||||||
|
clipEndPositionMs,
|
||||||
|
clipRelativeToLiveWindow,
|
||||||
|
clipRelativeToDefaultPosition,
|
||||||
|
clipStartsAtKeyFrame),
|
||||||
playbackProperties,
|
playbackProperties,
|
||||||
mediaMetadata != null ? mediaMetadata : new MediaMetadata.Builder().build());
|
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. */
|
/** Identifies the media item. */
|
||||||
public final String mediaId;
|
public final String mediaId;
|
||||||
|
|
||||||
@ -530,13 +660,18 @@ public final class MediaItem {
|
|||||||
/** The media metadata. */
|
/** The media metadata. */
|
||||||
public final MediaMetadata mediaMetadata;
|
public final MediaMetadata mediaMetadata;
|
||||||
|
|
||||||
|
/** The clipping properties. */
|
||||||
|
public final ClippingProperties clippingProperties;
|
||||||
|
|
||||||
private MediaItem(
|
private MediaItem(
|
||||||
String mediaId,
|
String mediaId,
|
||||||
|
ClippingProperties clippingProperties,
|
||||||
@Nullable PlaybackProperties playbackProperties,
|
@Nullable PlaybackProperties playbackProperties,
|
||||||
MediaMetadata mediaMetadata) {
|
MediaMetadata mediaMetadata) {
|
||||||
this.mediaId = mediaId;
|
this.mediaId = mediaId;
|
||||||
this.playbackProperties = playbackProperties;
|
this.playbackProperties = playbackProperties;
|
||||||
this.mediaMetadata = mediaMetadata;
|
this.mediaMetadata = mediaMetadata;
|
||||||
|
this.clippingProperties = clippingProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -551,6 +686,7 @@ public final class MediaItem {
|
|||||||
MediaItem other = (MediaItem) obj;
|
MediaItem other = (MediaItem) obj;
|
||||||
|
|
||||||
return Util.areEqual(mediaId, other.mediaId)
|
return Util.areEqual(mediaId, other.mediaId)
|
||||||
|
&& clippingProperties.equals(other.clippingProperties)
|
||||||
&& Util.areEqual(playbackProperties, other.playbackProperties)
|
&& Util.areEqual(playbackProperties, other.playbackProperties)
|
||||||
&& Util.areEqual(mediaMetadata, other.mediaMetadata);
|
&& Util.areEqual(mediaMetadata, other.mediaMetadata);
|
||||||
}
|
}
|
||||||
@ -559,6 +695,7 @@ public final class MediaItem {
|
|||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = mediaId.hashCode();
|
int result = mediaId.hashCode();
|
||||||
result = 31 * result + (playbackProperties != null ? playbackProperties.hashCode() : 0);
|
result = 31 * result + (playbackProperties != null ? playbackProperties.hashCode() : 0);
|
||||||
|
result = 31 * result + clippingProperties.hashCode();
|
||||||
result = 31 * result + mediaMetadata.hashCode();
|
result = 31 * result + mediaMetadata.hashCode();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -216,11 +216,11 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||||||
|
|
||||||
MediaSource leafMediaSource = mediaSourceFactory.createMediaSource(mediaItem);
|
MediaSource leafMediaSource = mediaSourceFactory.createMediaSource(mediaItem);
|
||||||
|
|
||||||
if (mediaItem.playbackProperties.subtitles.isEmpty()) {
|
List<MediaItem.Subtitle> subtitles = mediaItem.playbackProperties.subtitles;
|
||||||
return leafMediaSource;
|
if (subtitles.isEmpty()) {
|
||||||
|
return maybeClipMediaSource(mediaItem, leafMediaSource);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<MediaItem.Subtitle> subtitles = mediaItem.playbackProperties.subtitles;
|
|
||||||
MediaSource[] mediaSources = new MediaSource[subtitles.size() + 1];
|
MediaSource[] mediaSources = new MediaSource[subtitles.size() + 1];
|
||||||
mediaSources[0] = leafMediaSource;
|
mediaSources[0] = leafMediaSource;
|
||||||
SingleSampleMediaSource.Factory singleSampleSourceFactory =
|
SingleSampleMediaSource.Factory singleSampleSourceFactory =
|
||||||
@ -234,9 +234,10 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||||||
.setSelectionFlags(subtitle.selectionFlags)
|
.setSelectionFlags(subtitle.selectionFlags)
|
||||||
.build();
|
.build();
|
||||||
mediaSources[i + 1] =
|
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
|
// internal methods
|
||||||
@ -269,6 +270,21 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||||||
return drmCallback;
|
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(
|
private static SparseArray<MediaSourceFactory> loadDelegates(
|
||||||
DataSource.Factory dataSourceFactory) {
|
DataSource.Factory dataSourceFactory) {
|
||||||
SparseArray<MediaSourceFactory> factories = new SparseArray<>();
|
SparseArray<MediaSourceFactory> factories = new SparseArray<>();
|
||||||
|
@ -187,6 +187,77 @@ public class MediaItemTest {
|
|||||||
assertThat(mediaItem.playbackProperties.tag).isEqualTo(tag);
|
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
|
@Test
|
||||||
public void builderSetMediaMetadata_setsMetadata() {
|
public void builderSetMediaMetadata_setsMetadata() {
|
||||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
|
||||||
|
@ -122,6 +122,60 @@ public final class DefaultMediaSourceFactoryTest {
|
|||||||
assertThat(mediaSource.getTag()).isEqualTo(tag);
|
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
|
@Test
|
||||||
public void getSupportedTypes_coreModule_onlyOther() {
|
public void getSupportedTypes_coreModule_onlyOther() {
|
||||||
int[] supportedTypes =
|
int[] supportedTypes =
|
||||||
|
Loading…
x
Reference in New Issue
Block a user