Commit 39e4112e by tonihei Committed by Oliver Woodman

Add proper live stream clipping.

This adds two options to the ClippingMediaSource which allow proper clipping
of live streams:
 1. The clipping stays fixed relative to already created media periods. That
    means that playback actually progresses through the clipped media and
    eventually reaches the end of the clipping. The window is also marked
    as non-dynamic to let playback end in this case.
 2. Allow to specify a clipping duration relative to the default position to
    be able to specify the duration of live stream which is to be played.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=190911049
parent 09cf9277
......@@ -31,6 +31,7 @@
media sources including composite sources.
* Added callbacks to `MediaSourceEventListener` to get notified when media
periods are created, released and being read from.
* Support live stream clipping with `ClippingMediaSource`.
* Audio:
* Factor out `AudioTrack` position tracking from `DefaultAudioSink`.
* Fix an issue where the playback position would pause just after playback
......
......@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
......@@ -221,11 +222,14 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
}
private SeekParameters clipSeekParameters(long positionUs, SeekParameters seekParameters) {
long toleranceBeforeUs = Math.min(positionUs - startUs, seekParameters.toleranceBeforeUs);
long toleranceBeforeUs =
Util.constrainValue(
seekParameters.toleranceBeforeUs, /* min= */ 0, /* max= */ positionUs - startUs);
long toleranceAfterUs =
endUs == C.TIME_END_OF_SOURCE
? seekParameters.toleranceAfterUs
: Math.min(endUs - positionUs, seekParameters.toleranceAfterUs);
Util.constrainValue(
seekParameters.toleranceAfterUs,
/* min= */ 0,
/* max= */ endUs == C.TIME_END_OF_SOURCE ? Long.MAX_VALUE : endUs - positionUs);
if (toleranceBeforeUs == seekParameters.toleranceBeforeUs
&& toleranceAfterUs == seekParameters.toleranceAfterUs) {
return seekParameters;
......
......@@ -76,14 +76,20 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
private final long startUs;
private final long endUs;
private final boolean enableInitialDiscontinuity;
private final boolean allowDynamicClippingUpdates;
private final boolean relativeToDefaultPosition;
private final ArrayList<ClippingMediaPeriod> mediaPeriods;
private final Timeline.Window window;
private @Nullable Object manifest;
private ClippingTimeline clippingTimeline;
private IllegalClippingException clippingError;
private long periodStartUs;
private long periodEndUs;
/**
* Creates a new clipping source that wraps the specified source.
* Creates a new clipping source that wraps the specified source and provides samples between the
* specified start and end position.
*
* @param mediaSource The single-period source to wrap.
* @param startPositionUs The start position within {@code mediaSource}'s window at which to start
......@@ -95,34 +101,83 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
* being clipped.
*/
public ClippingMediaSource(MediaSource mediaSource, long startPositionUs, long endPositionUs) {
this(mediaSource, startPositionUs, endPositionUs, true);
this(
mediaSource,
startPositionUs,
endPositionUs,
/* enableInitialDiscontinuity= */ true,
/* allowDynamicClippingUpdates= */ false,
/* relativeToDefaultPosition= */ false);
}
/**
* Creates a new clipping source that wraps the specified source and provides samples from the
* default position for the specified duration.
*
* @param mediaSource The single-period source to wrap.
* @param durationUs The duration from the default position in the window in {@code mediaSource}'s
* timeline at which to stop providing samples. Specifying a duration that exceeds the {@code
* mediaSource}'s duration will result in the end of the source not being clipped.
*/
public ClippingMediaSource(MediaSource mediaSource, long durationUs) {
this(
mediaSource,
/* startPositionUs= */ 0,
/* endPositionUs= */ durationUs,
/* enableInitialDiscontinuity= */ true,
/* allowDynamicClippingUpdates= */ false,
/* relativeToDefaultPosition= */ true);
}
/**
* Creates a new clipping source that wraps the specified source.
* <p>
* If the start point is guaranteed to be a key frame, pass {@code false} to
* {@code enableInitialPositionDiscontinuity} to suppress an initial discontinuity when a period
* is first read from.
*
* <p>If the start point is guaranteed to be a key frame, pass {@code false} to {@code
* enableInitialPositionDiscontinuity} to suppress an initial discontinuity when a period is first
* read from.
*
* <p>For live streams, if the clipping positions should move with the live window, pass {@code
* true} to {@code allowDynamicClippingUpdates}. Otherwise, the live stream ends when the playback
* reaches {@code endPositionUs} in the last reported live window at the time a media period was
* created.
*
* @param mediaSource The single-period source to wrap.
* @param startPositionUs The start position within {@code mediaSource}'s timeline at which to
* start providing samples, in microseconds.
* @param endPositionUs The end position within {@code mediaSource}'s timeline at which to stop
* providing samples, in microseconds. Specify {@link C#TIME_END_OF_SOURCE} to provide samples
* from the specified start point up to the end of the source. Specifying a position that
* exceeds the {@code mediaSource}'s duration will also result in the end of the source not
* being clipped.
* @param startPositionUs The start position at which to start providing samples, in microseconds.
* If {@code relativeToDefaultPosition} is {@code false}, this position is relative to the
* start of the window in {@code mediaSource}'s timeline. If {@code relativeToDefaultPosition}
* is {@code true}, this position is relative to the default position in the window in {@code
* mediaSource}'s timeline.
* @param endPositionUs The end position at which to stop providing samples, in microseconds.
* Specify {@link C#TIME_END_OF_SOURCE} to provide samples from the specified start point up
* to the end of the source. Specifying a position that exceeds the {@code mediaSource}'s
* duration will also result in the end of the source not being clipped. If {@code
* relativeToDefaultPosition} is {@code false}, the specified position is relative to the
* start of the window in {@code mediaSource}'s timeline. If {@code relativeToDefaultPosition}
* is {@code true}, this position is relative to the default position in the window in {@code
* mediaSource}'s timeline.
* @param enableInitialDiscontinuity Whether the initial discontinuity should be enabled.
* @param allowDynamicClippingUpdates Whether the clipping of active media periods moves with a
* live window. If {@code false}, playback ends when it reaches {@code endPositionUs} in the
* last reported live window at the time a media period was created.
* @param relativeToDefaultPosition Whether {@code startPositionUs} and {@code endPositionUs} are
* relative to the default position in the window in {@code mediaSource}'s timeline.
*/
public ClippingMediaSource(MediaSource mediaSource, long startPositionUs, long endPositionUs,
boolean enableInitialDiscontinuity) {
public ClippingMediaSource(
MediaSource mediaSource,
long startPositionUs,
long endPositionUs,
boolean enableInitialDiscontinuity,
boolean allowDynamicClippingUpdates,
boolean relativeToDefaultPosition) {
Assertions.checkArgument(startPositionUs >= 0);
this.mediaSource = Assertions.checkNotNull(mediaSource);
startUs = startPositionUs;
endUs = endPositionUs;
this.enableInitialDiscontinuity = enableInitialDiscontinuity;
this.allowDynamicClippingUpdates = allowDynamicClippingUpdates;
this.relativeToDefaultPosition = relativeToDefaultPosition;
mediaPeriods = new ArrayList<>();
window = new Timeline.Window();
}
@Override
......@@ -155,12 +210,16 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
public void releasePeriod(MediaPeriod mediaPeriod) {
Assertions.checkState(mediaPeriods.remove(mediaPeriod));
mediaSource.releasePeriod(((ClippingMediaPeriod) mediaPeriod).mediaPeriod);
if (mediaPeriods.isEmpty() && !allowDynamicClippingUpdates) {
refreshClippedTimeline(clippingTimeline.timeline);
}
}
@Override
public void releaseSourceInternal() {
super.releaseSourceInternal();
clippingError = null;
clippingTimeline = null;
}
@Override
......@@ -169,25 +228,47 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
if (clippingError != null) {
return;
}
ClippingTimeline clippingTimeline;
this.manifest = manifest;
refreshClippedTimeline(timeline);
}
private void refreshClippedTimeline(Timeline timeline) {
long windowStartUs;
long windowEndUs;
timeline.getWindow(/* windowIndex= */ 0, window);
long windowPositionInPeriodUs = window.getPositionInFirstPeriodUs();
if (clippingTimeline == null || mediaPeriods.isEmpty() || allowDynamicClippingUpdates) {
windowStartUs = startUs;
windowEndUs = endUs;
if (relativeToDefaultPosition) {
long windowDefaultPositionUs = window.getDefaultPositionUs();
windowStartUs += windowDefaultPositionUs;
windowEndUs += windowDefaultPositionUs;
}
periodStartUs = windowPositionInPeriodUs + windowStartUs;
periodEndUs =
endUs == C.TIME_END_OF_SOURCE
? C.TIME_END_OF_SOURCE
: windowPositionInPeriodUs + windowEndUs;
int count = mediaPeriods.size();
for (int i = 0; i < count; i++) {
mediaPeriods.get(i).updateClipping(periodStartUs, periodEndUs);
}
} else {
// Keep window fixed at previous period position.
windowStartUs = periodStartUs - windowPositionInPeriodUs;
windowEndUs =
endUs == C.TIME_END_OF_SOURCE
? C.TIME_END_OF_SOURCE
: periodEndUs - windowPositionInPeriodUs;
}
try {
clippingTimeline = new ClippingTimeline(timeline, startUs, endUs);
clippingTimeline = new ClippingTimeline(timeline, windowStartUs, windowEndUs);
} catch (IllegalClippingException e) {
clippingError = e;
return;
}
refreshSourceInfo(clippingTimeline, manifest);
long windowPositionInPeriodUs =
timeline
.getWindow(/* windowIndex= */ 0, new Timeline.Window())
.getPositionInFirstPeriodUs();
periodStartUs = windowPositionInPeriodUs + startUs;
periodEndUs =
endUs == C.TIME_END_OF_SOURCE ? C.TIME_END_OF_SOURCE : windowPositionInPeriodUs + endUs;
int count = mediaPeriods.size();
for (int i = 0; i < count; i++) {
mediaPeriods.get(i).updateClipping(periodStartUs, periodEndUs);
}
}
@Override
......@@ -211,6 +292,7 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
private final long startUs;
private final long endUs;
private final long durationUs;
private final boolean isDynamic;
/**
* Creates a new clipping timeline that wraps the specified timeline.
......@@ -228,7 +310,8 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
throw new IllegalClippingException(IllegalClippingException.REASON_INVALID_PERIOD_COUNT);
}
Window window = timeline.getWindow(0, new Window(), false);
long resolvedEndUs = endUs == C.TIME_END_OF_SOURCE ? window.durationUs : endUs;
startUs = Math.max(0, startUs);
long resolvedEndUs = endUs == C.TIME_END_OF_SOURCE ? window.durationUs : Math.max(0, endUs);
if (window.durationUs != C.TIME_UNSET) {
if (resolvedEndUs > window.durationUs) {
resolvedEndUs = window.durationUs;
......@@ -243,14 +326,20 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
this.startUs = startUs;
this.endUs = resolvedEndUs;
durationUs = resolvedEndUs == C.TIME_UNSET ? C.TIME_UNSET : (resolvedEndUs - startUs);
isDynamic =
window.isDynamic
&& (resolvedEndUs == C.TIME_UNSET
|| (window.durationUs != C.TIME_UNSET && resolvedEndUs == window.durationUs));
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
timeline.getWindow(/* windowIndex= */ 0, window, setIds, defaultPositionProjectionUs);
timeline.getWindow(
/* windowIndex= */ 0, window, setIds, /* defaultPositionProjectionUs= */ 0);
window.positionInFirstPeriodUs += startUs;
window.durationUs = durationUs;
window.isDynamic = isDynamic;
if (window.defaultPositionUs != C.TIME_UNSET) {
window.defaultPositionUs = Math.max(window.defaultPositionUs, startUs);
window.defaultPositionUs = endUs == C.TIME_UNSET ? window.defaultPositionUs
......
......@@ -63,7 +63,7 @@ public final class ClippingMediaSourceTest {
@Test
public void testNoClipping() throws IOException {
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false);
Timeline timeline = new SinglePeriodTimeline(TEST_PERIOD_DURATION_US, true, false);
Timeline clippedTimeline = getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US);
......@@ -77,7 +77,7 @@ public final class ClippingMediaSourceTest {
@Test
public void testClippingUnseekableWindowThrows() throws IOException {
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), false, false);
Timeline timeline = new SinglePeriodTimeline(TEST_PERIOD_DURATION_US, false, false);
// If the unseekable window isn't clipped, clipping succeeds.
getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US);
......@@ -92,7 +92,7 @@ public final class ClippingMediaSourceTest {
@Test
public void testClippingStart() throws IOException {
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false);
Timeline timeline = new SinglePeriodTimeline(TEST_PERIOD_DURATION_US, true, false);
Timeline clippedTimeline =
getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US, TEST_PERIOD_DURATION_US);
......@@ -104,7 +104,7 @@ public final class ClippingMediaSourceTest {
@Test
public void testClippingEnd() throws IOException {
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false);
Timeline timeline = new SinglePeriodTimeline(TEST_PERIOD_DURATION_US, true, false);
Timeline clippedTimeline =
getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US);
......@@ -163,7 +163,7 @@ public final class ClippingMediaSourceTest {
@Test
public void testClippingStartAndEnd() throws IOException {
Timeline timeline = new SinglePeriodTimeline(C.msToUs(TEST_PERIOD_DURATION_US), true, false);
Timeline timeline = new SinglePeriodTimeline(TEST_PERIOD_DURATION_US, true, false);
Timeline clippedTimeline =
getClippedTimeline(
......@@ -175,6 +175,206 @@ public final class ClippingMediaSourceTest {
}
@Test
public void testClippingFromDefaultPosition() throws IOException {
Timeline timeline =
new SinglePeriodTimeline(
/* periodDurationUs= */ 3 * TEST_PERIOD_DURATION_US,
/* windowDurationUs= */ TEST_PERIOD_DURATION_US,
/* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true);
Timeline clippedTimeline = getClippedTimeline(timeline, /* durationUs= */ TEST_CLIP_AMOUNT_US);
assertThat(clippedTimeline.getWindow(0, window).getDurationUs()).isEqualTo(TEST_CLIP_AMOUNT_US);
assertThat(clippedTimeline.getWindow(0, window).getDefaultPositionUs()).isEqualTo(0);
assertThat(clippedTimeline.getWindow(0, window).getPositionInFirstPeriodUs())
.isEqualTo(TEST_PERIOD_DURATION_US + TEST_CLIP_AMOUNT_US);
assertThat(clippedTimeline.getPeriod(0, period).getDurationUs())
.isEqualTo(TEST_PERIOD_DURATION_US + 2 * TEST_CLIP_AMOUNT_US);
}
@Test
public void testAllowDynamicUpdatesWithOverlappingLiveWindow() throws IOException {
Timeline timeline1 =
new SinglePeriodTimeline(
/* periodDurationUs= */ 2 * TEST_PERIOD_DURATION_US,
/* windowDurationUs= */ TEST_PERIOD_DURATION_US,
/* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true);
Timeline timeline2 =
new SinglePeriodTimeline(
/* periodDurationUs= */ 3 * TEST_PERIOD_DURATION_US,
/* windowDurationUs= */ TEST_PERIOD_DURATION_US,
/* windowPositionInPeriodUs= */ 2 * TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true);
Timeline[] clippedTimelines =
getClippedTimelines(
/* startUs= */ 0,
/* endUs= */ TEST_PERIOD_DURATION_US,
/* allowDynamicUpdates= */ true,
/* fromDefaultPosition= */ true,
timeline1,
timeline2);
assertThat(clippedTimelines[0].getWindow(0, window).getDurationUs())
.isEqualTo(TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US);
assertThat(clippedTimelines[0].getWindow(0, window).getDefaultPositionUs()).isEqualTo(0);
assertThat(clippedTimelines[0].getWindow(0, window).isDynamic).isTrue();
assertThat(clippedTimelines[0].getWindow(0, window).getPositionInFirstPeriodUs())
.isEqualTo(TEST_PERIOD_DURATION_US + TEST_CLIP_AMOUNT_US);
assertThat(clippedTimelines[0].getPeriod(0, period).getDurationUs())
.isEqualTo(2 * TEST_PERIOD_DURATION_US);
assertThat(clippedTimelines[1].getWindow(0, window).getDurationUs())
.isEqualTo(TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US);
assertThat(clippedTimelines[1].getWindow(0, window).getDefaultPositionUs()).isEqualTo(0);
assertThat(clippedTimelines[1].getWindow(0, window).isDynamic).isTrue();
assertThat(clippedTimelines[1].getWindow(0, window).getPositionInFirstPeriodUs())
.isEqualTo(2 * TEST_PERIOD_DURATION_US + TEST_CLIP_AMOUNT_US);
assertThat(clippedTimelines[1].getPeriod(0, period).getDurationUs())
.isEqualTo(3 * TEST_PERIOD_DURATION_US);
}
@Test
public void testAllowDynamicUpdatesWithNonOverlappingLiveWindow() throws IOException {
Timeline timeline1 =
new SinglePeriodTimeline(
/* periodDurationUs= */ 2 * TEST_PERIOD_DURATION_US,
/* windowDurationUs= */ TEST_PERIOD_DURATION_US,
/* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true);
Timeline timeline2 =
new SinglePeriodTimeline(
/* periodDurationUs= */ 4 * TEST_PERIOD_DURATION_US,
/* windowDurationUs= */ TEST_PERIOD_DURATION_US,
/* windowPositionInPeriodUs= */ 3 * TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true);
Timeline[] clippedTimelines =
getClippedTimelines(
/* startUs= */ 0,
/* endUs= */ TEST_PERIOD_DURATION_US,
/* allowDynamicUpdates= */ true,
/* fromDefaultPosition= */ true,
timeline1,
timeline2);
assertThat(clippedTimelines[0].getWindow(0, window).getDurationUs())
.isEqualTo(TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US);
assertThat(clippedTimelines[0].getWindow(0, window).getDefaultPositionUs()).isEqualTo(0);
assertThat(clippedTimelines[0].getWindow(0, window).isDynamic).isTrue();
assertThat(clippedTimelines[0].getWindow(0, window).getPositionInFirstPeriodUs())
.isEqualTo(TEST_PERIOD_DURATION_US + TEST_CLIP_AMOUNT_US);
assertThat(clippedTimelines[0].getPeriod(0, period).getDurationUs())
.isEqualTo(2 * TEST_PERIOD_DURATION_US);
assertThat(clippedTimelines[1].getWindow(0, window).getDurationUs())
.isEqualTo(TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US);
assertThat(clippedTimelines[1].getWindow(0, window).getDefaultPositionUs()).isEqualTo(0);
assertThat(clippedTimelines[1].getWindow(0, window).isDynamic).isTrue();
assertThat(clippedTimelines[1].getWindow(0, window).getPositionInFirstPeriodUs())
.isEqualTo(3 * TEST_PERIOD_DURATION_US + TEST_CLIP_AMOUNT_US);
assertThat(clippedTimelines[1].getPeriod(0, period).getDurationUs())
.isEqualTo(4 * TEST_PERIOD_DURATION_US);
}
@Test
public void testDisallowDynamicUpdatesWithOverlappingLiveWindow() throws IOException {
Timeline timeline1 =
new SinglePeriodTimeline(
/* periodDurationUs= */ 2 * TEST_PERIOD_DURATION_US,
/* windowDurationUs= */ TEST_PERIOD_DURATION_US,
/* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true);
Timeline timeline2 =
new SinglePeriodTimeline(
/* periodDurationUs= */ 3 * TEST_PERIOD_DURATION_US,
/* windowDurationUs= */ TEST_PERIOD_DURATION_US,
/* windowPositionInPeriodUs= */ 2 * TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true);
Timeline[] clippedTimelines =
getClippedTimelines(
/* startUs= */ 0,
/* endUs= */ TEST_PERIOD_DURATION_US,
/* allowDynamicUpdates= */ false,
/* fromDefaultPosition= */ true,
timeline1,
timeline2);
assertThat(clippedTimelines[0].getWindow(0, window).getDurationUs())
.isEqualTo(TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US);
assertThat(clippedTimelines[0].getWindow(0, window).getDefaultPositionUs()).isEqualTo(0);
assertThat(clippedTimelines[0].getWindow(0, window).isDynamic).isTrue();
assertThat(clippedTimelines[0].getWindow(0, window).getPositionInFirstPeriodUs())
.isEqualTo(TEST_PERIOD_DURATION_US + TEST_CLIP_AMOUNT_US);
assertThat(clippedTimelines[0].getPeriod(0, period).getDurationUs())
.isEqualTo(2 * TEST_PERIOD_DURATION_US);
assertThat(clippedTimelines[1].getWindow(0, window).getDurationUs())
.isEqualTo(TEST_CLIP_AMOUNT_US);
assertThat(clippedTimelines[1].getWindow(0, window).getDefaultPositionUs())
.isEqualTo(TEST_CLIP_AMOUNT_US);
assertThat(clippedTimelines[1].getWindow(0, window).isDynamic).isFalse();
assertThat(clippedTimelines[1].getWindow(0, window).getPositionInFirstPeriodUs())
.isEqualTo(2 * TEST_PERIOD_DURATION_US);
assertThat(clippedTimelines[1].getPeriod(0, period).getDurationUs())
.isEqualTo(2 * TEST_PERIOD_DURATION_US + TEST_CLIP_AMOUNT_US);
}
@Test
public void testDisallowDynamicUpdatesWithNonOverlappingLiveWindow() throws IOException {
Timeline timeline1 =
new SinglePeriodTimeline(
/* periodDurationUs= */ 2 * TEST_PERIOD_DURATION_US,
/* windowDurationUs= */ TEST_PERIOD_DURATION_US,
/* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true);
Timeline timeline2 =
new SinglePeriodTimeline(
/* periodDurationUs= */ 4 * TEST_PERIOD_DURATION_US,
/* windowDurationUs= */ TEST_PERIOD_DURATION_US,
/* windowPositionInPeriodUs= */ 3 * TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true);
Timeline[] clippedTimelines =
getClippedTimelines(
/* startUs= */ 0,
/* endUs= */ TEST_PERIOD_DURATION_US,
/* allowDynamicUpdates= */ false,
/* fromDefaultPosition= */ true,
timeline1,
timeline2);
assertThat(clippedTimelines[0].getWindow(0, window).getDurationUs())
.isEqualTo(TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US);
assertThat(clippedTimelines[0].getWindow(0, window).getDefaultPositionUs()).isEqualTo(0);
assertThat(clippedTimelines[0].getWindow(0, window).isDynamic).isTrue();
assertThat(clippedTimelines[0].getWindow(0, window).getPositionInFirstPeriodUs())
.isEqualTo(TEST_PERIOD_DURATION_US + TEST_CLIP_AMOUNT_US);
assertThat(clippedTimelines[0].getPeriod(0, period).getDurationUs())
.isEqualTo(2 * TEST_PERIOD_DURATION_US);
assertThat(clippedTimelines[1].getWindow(0, window).getDurationUs()).isEqualTo(0);
assertThat(clippedTimelines[1].getWindow(0, window).getDefaultPositionUs()).isEqualTo(0);
assertThat(clippedTimelines[1].getWindow(0, window).isDynamic).isFalse();
assertThat(clippedTimelines[1].getWindow(0, window).getPositionInFirstPeriodUs())
.isEqualTo(3 * TEST_PERIOD_DURATION_US);
assertThat(clippedTimelines[1].getPeriod(0, period).getDurationUs())
.isEqualTo(3 * TEST_PERIOD_DURATION_US);
}
@Test
public void testWindowAndPeriodIndices() throws IOException {
Timeline timeline =
new FakeTimeline(
......@@ -321,14 +521,66 @@ public final class ClippingMediaSourceTest {
*/
private static Timeline getClippedTimeline(Timeline timeline, long startUs, long endUs)
throws IOException {
FakeMediaSource fakeMediaSource = new FakeMediaSource(timeline, null);
FakeMediaSource fakeMediaSource = new FakeMediaSource(timeline, /* manifest= */ null);
ClippingMediaSource mediaSource = new ClippingMediaSource(fakeMediaSource, startUs, endUs);
MediaSourceTestRunner testRunner = new MediaSourceTestRunner(mediaSource, null);
return getClippedTimelines(fakeMediaSource, mediaSource)[0];
}
/**
* Wraps the specified timeline in a {@link ClippingMediaSource} and returns the clipped timeline.
*/
private static Timeline getClippedTimeline(Timeline timeline, long durationUs)
throws IOException {
FakeMediaSource fakeMediaSource = new FakeMediaSource(timeline, /* manifest= */ null);
ClippingMediaSource mediaSource = new ClippingMediaSource(fakeMediaSource, durationUs);
return getClippedTimelines(fakeMediaSource, mediaSource)[0];
}
/**
* Wraps the specified timelines in a {@link ClippingMediaSource} and returns the clipped timeline
* for each timeline update.
*/
private static Timeline[] getClippedTimelines(
long startUs,
long endUs,
boolean allowDynamicUpdates,
boolean fromDefaultPosition,
Timeline firstTimeline,
Timeline... additionalTimelines)
throws IOException {
FakeMediaSource fakeMediaSource = new FakeMediaSource(firstTimeline, /* manifest= */ null);
ClippingMediaSource mediaSource =
new ClippingMediaSource(
fakeMediaSource,
startUs,
endUs,
/* enableInitialDiscontinuity= */ true,
allowDynamicUpdates,
fromDefaultPosition);
return getClippedTimelines(fakeMediaSource, mediaSource, additionalTimelines);
}
private static Timeline[] getClippedTimelines(
FakeMediaSource fakeMediaSource,
ClippingMediaSource clippingMediaSource,
Timeline... additionalTimelines)
throws IOException {
MediaSourceTestRunner testRunner =
new MediaSourceTestRunner(clippingMediaSource, /* allocator= */ null);
Timeline[] clippedTimelines = new Timeline[additionalTimelines.length + 1];
try {
Timeline clippedTimeline = testRunner.prepareSource();
clippedTimelines[0] = testRunner.prepareSource();
MediaPeriod mediaPeriod =
testRunner.createPeriod(
new MediaPeriodId(/* periodIndex= */ 0, /* windowSequenceNumber= */ 0));
for (int i = 0; i < additionalTimelines.length; i++) {
fakeMediaSource.setNewSourceInfo(additionalTimelines[i], /* newManifest= */ null);
clippedTimelines[i + 1] = testRunner.assertTimelineChangeBlocking();
}
testRunner.releasePeriod(mediaPeriod);
testRunner.releaseSource();
fakeMediaSource.assertReleased();
return clippedTimeline;
return clippedTimelines;
} finally {
testRunner.release();
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment