Commit dd4f9bca by tonihei Committed by Oliver Woodman

Add Timeline.Window.isLive

This flag is currently merged into Window.isDynamic, which isn't always true
because
1. A window can be dynamic for other reasons (e.g. when the duration is still
missing).
2. A live stream can be become non-dynamic when it ends.

Issue:#2668
Issue:#5973
PiperOrigin-RevId: 271999378
parent 26dd4aad
Showing with 138 additions and 41 deletions
...@@ -80,6 +80,9 @@ ...@@ -80,6 +80,9 @@
and move it to the core library. and move it to the core library.
* Move `LibvpxVideoRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER` to * Move `LibvpxVideoRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER` to
`C.MSG_SET_OUTPUT_BUFFER_RENDERER`. `C.MSG_SET_OUTPUT_BUFFER_RENDERER`.
* Add `Timeline.Window.isLive` to indicate that a window is a live stream
([#2668](https://github.com/google/ExoPlayer/issues/2668) and
[#5973](https://github.com/google/ExoPlayer/issues/5973)).
### 2.10.5 (2019-09-20) ### ### 2.10.5 (2019-09-20) ###
......
...@@ -121,6 +121,7 @@ import java.util.Arrays; ...@@ -121,6 +121,7 @@ import java.util.Arrays;
/* windowStartTimeMs= */ C.TIME_UNSET, /* windowStartTimeMs= */ C.TIME_UNSET,
/* isSeekable= */ !isDynamic, /* isSeekable= */ !isDynamic,
isDynamic, isDynamic,
/* isLive= */ isDynamic,
defaultPositionsUs[windowIndex], defaultPositionsUs[windowIndex],
durationUs, durationUs,
/* firstPeriodIndex= */ windowIndex, /* firstPeriodIndex= */ windowIndex,
......
...@@ -63,7 +63,8 @@ public class ImaAdsLoaderTest { ...@@ -63,7 +63,8 @@ public class ImaAdsLoaderTest {
private static final long CONTENT_DURATION_US = 10 * C.MICROS_PER_SECOND; private static final long CONTENT_DURATION_US = 10 * C.MICROS_PER_SECOND;
private static final Timeline CONTENT_TIMELINE = private static final Timeline CONTENT_TIMELINE =
new SinglePeriodTimeline(CONTENT_DURATION_US, /* isSeekable= */ true, /* isDynamic= */ false); new SinglePeriodTimeline(
CONTENT_DURATION_US, /* isSeekable= */ true, /* isDynamic= */ false, /* isLive= */ false);
private static final Uri TEST_URI = Uri.EMPTY; private static final Uri TEST_URI = Uri.EMPTY;
private static final long TEST_AD_DURATION_US = 5 * C.MICROS_PER_SECOND; private static final long TEST_AD_DURATION_US = 5 * C.MICROS_PER_SECOND;
private static final long[][] PREROLL_ADS_DURATIONS_US = new long[][] {{TEST_AD_DURATION_US}}; private static final long[][] PREROLL_ADS_DURATIONS_US = new long[][] {{TEST_AD_DURATION_US}};
......
...@@ -66,8 +66,9 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -66,8 +66,9 @@ import com.google.android.exoplayer2.util.Assertions;
* duration is unknown, since it's continually extending as more content is broadcast. If content * duration is unknown, since it's continually extending as more content is broadcast. If content
* only remains available for a limited period of time then the window may start at a non-zero * only remains available for a limited period of time then the window may start at a non-zero
* position, defining the region of content that can still be played. The window will have {@link * position, defining the region of content that can still be played. The window will have {@link
* Window#isDynamic} set to true if the stream is still live. Its default position is typically near * Window#isLive} set to true to indicate it's a live stream and {@link Window#isDynamic} set to
* to the live edge (indicated by the black dot in the figure above). * true as long as we expect changes to the live window. Its default position is typically near to
* the live edge (indicated by the black dot in the figure above).
* *
* <h3>Live stream with indefinite availability</h3> * <h3>Live stream with indefinite availability</h3>
* *
...@@ -158,8 +159,13 @@ public abstract class Timeline { ...@@ -158,8 +159,13 @@ public abstract class Timeline {
public boolean isDynamic; public boolean isDynamic;
/** /**
* The index of the first period that belongs to this window. * Whether the media in this window is live. For informational purposes only.
*
* <p>Check {@link #isDynamic} to know whether this window may still change.
*/ */
public boolean isLive;
/** The index of the first period that belongs to this window. */
public int firstPeriodIndex; public int firstPeriodIndex;
/** /**
...@@ -200,6 +206,7 @@ public abstract class Timeline { ...@@ -200,6 +206,7 @@ public abstract class Timeline {
long windowStartTimeMs, long windowStartTimeMs,
boolean isSeekable, boolean isSeekable,
boolean isDynamic, boolean isDynamic,
boolean isLive,
long defaultPositionUs, long defaultPositionUs,
long durationUs, long durationUs,
int firstPeriodIndex, int firstPeriodIndex,
...@@ -212,6 +219,7 @@ public abstract class Timeline { ...@@ -212,6 +219,7 @@ public abstract class Timeline {
this.windowStartTimeMs = windowStartTimeMs; this.windowStartTimeMs = windowStartTimeMs;
this.isSeekable = isSeekable; this.isSeekable = isSeekable;
this.isDynamic = isDynamic; this.isDynamic = isDynamic;
this.isLive = isLive;
this.defaultPositionUs = defaultPositionUs; this.defaultPositionUs = defaultPositionUs;
this.durationUs = durationUs; this.durationUs = durationUs;
this.firstPeriodIndex = firstPeriodIndex; this.firstPeriodIndex = firstPeriodIndex;
......
...@@ -317,6 +317,7 @@ public final class MaskingMediaSource extends CompositeMediaSource<Void> { ...@@ -317,6 +317,7 @@ public final class MaskingMediaSource extends CompositeMediaSource<Void> {
/* isSeekable= */ false, /* isSeekable= */ false,
// Dynamic window to indicate pending timeline updates. // Dynamic window to indicate pending timeline updates.
/* isDynamic= */ true, /* isDynamic= */ true,
/* isLive= */ false,
/* defaultPositionUs= */ 0, /* defaultPositionUs= */ 0,
/* durationUs= */ C.TIME_UNSET, /* durationUs= */ C.TIME_UNSET,
/* firstPeriodIndex= */ 0, /* firstPeriodIndex= */ 0,
......
...@@ -74,13 +74,14 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -74,13 +74,14 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
interface Listener { interface Listener {
/** /**
* Called when the duration or ability to seek within the period changes. * Called when the duration, the ability to seek within the period, or the categorization as
* live stream changes.
* *
* @param durationUs The duration of the period, or {@link C#TIME_UNSET}. * @param durationUs The duration of the period, or {@link C#TIME_UNSET}.
* @param isSeekable Whether the period is seekable. * @param isSeekable Whether the period is seekable.
* @param isLive Whether the period is live.
*/ */
void onSourceInfoRefreshed(long durationUs, boolean isSeekable); void onSourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive);
} }
/** /**
...@@ -129,6 +130,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -129,6 +130,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
private int enabledTrackCount; private int enabledTrackCount;
private long durationUs; private long durationUs;
private long length; private long length;
private boolean isLive;
private long lastSeekPositionUs; private long lastSeekPositionUs;
private long pendingResetPositionUs; private long pendingResetPositionUs;
...@@ -551,7 +553,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -551,7 +553,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
long largestQueuedTimestampUs = getLargestQueuedTimestampUs(); long largestQueuedTimestampUs = getLargestQueuedTimestampUs();
durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0 durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0
: largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US; : largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US;
listener.onSourceInfoRefreshed(durationUs, isSeekable); listener.onSourceInfoRefreshed(durationUs, isSeekable, isLive);
} }
eventDispatcher.loadCompleted( eventDispatcher.loadCompleted(
loadable.dataSpec, loadable.dataSpec,
...@@ -740,14 +742,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType; ...@@ -740,14 +742,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
trackArray[i] = new TrackGroup(trackFormat); trackArray[i] = new TrackGroup(trackFormat);
} }
dataType = isLive = length == C.LENGTH_UNSET && seekMap.getDurationUs() == C.TIME_UNSET;
length == C.LENGTH_UNSET && seekMap.getDurationUs() == C.TIME_UNSET dataType = isLive ? C.DATA_TYPE_MEDIA_PROGRESSIVE_LIVE : C.DATA_TYPE_MEDIA;
? C.DATA_TYPE_MEDIA_PROGRESSIVE_LIVE
: C.DATA_TYPE_MEDIA;
preparedState = preparedState =
new PreparedState(seekMap, new TrackGroupArray(trackArray), trackIsAudioVideoFlags); new PreparedState(seekMap, new TrackGroupArray(trackArray), trackIsAudioVideoFlags);
prepared = true; prepared = true;
listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable()); listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable(), isLive);
Assertions.checkNotNull(callback).onPrepared(this); Assertions.checkNotNull(callback).onPrepared(this);
} }
......
...@@ -220,6 +220,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource ...@@ -220,6 +220,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
private long timelineDurationUs; private long timelineDurationUs;
private boolean timelineIsSeekable; private boolean timelineIsSeekable;
private boolean timelineIsLive;
@Nullable private TransferListener transferListener; @Nullable private TransferListener transferListener;
// TODO: Make private when ExtractorMediaSource is deleted. // TODO: Make private when ExtractorMediaSource is deleted.
...@@ -253,7 +254,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource ...@@ -253,7 +254,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
transferListener = mediaTransferListener; transferListener = mediaTransferListener;
drmSessionManager.prepare(); drmSessionManager.prepare();
notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable); notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable, timelineIsLive);
} }
@Override @Override
...@@ -293,27 +294,32 @@ public final class ProgressiveMediaSource extends BaseMediaSource ...@@ -293,27 +294,32 @@ public final class ProgressiveMediaSource extends BaseMediaSource
// ProgressiveMediaPeriod.Listener implementation. // ProgressiveMediaPeriod.Listener implementation.
@Override @Override
public void onSourceInfoRefreshed(long durationUs, boolean isSeekable) { public void onSourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive) {
// If we already have the duration from a previous source info refresh, use it. // If we already have the duration from a previous source info refresh, use it.
durationUs = durationUs == C.TIME_UNSET ? timelineDurationUs : durationUs; durationUs = durationUs == C.TIME_UNSET ? timelineDurationUs : durationUs;
if (timelineDurationUs == durationUs && timelineIsSeekable == isSeekable) { if (timelineDurationUs == durationUs
&& timelineIsSeekable == isSeekable
&& timelineIsLive == isLive) {
// Suppress no-op source info changes. // Suppress no-op source info changes.
return; return;
} }
notifySourceInfoRefreshed(durationUs, isSeekable); notifySourceInfoRefreshed(durationUs, isSeekable, isLive);
} }
// Internal methods. // Internal methods.
private void notifySourceInfoRefreshed(long durationUs, boolean isSeekable) { private void notifySourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive) {
timelineDurationUs = durationUs; timelineDurationUs = durationUs;
timelineIsSeekable = isSeekable; timelineIsSeekable = isSeekable;
// TODO: Make timeline dynamic until its duration is known. This is non-trivial. See b/69703223. timelineIsLive = isLive;
// TODO: Split up isDynamic into multiple fields to indicate which values may change. Then
// indicate that the duration may change until it's known. See [internal: b/69703223].
refreshSourceInfo( refreshSourceInfo(
new SinglePeriodTimeline( new SinglePeriodTimeline(
timelineDurationUs, timelineDurationUs,
timelineIsSeekable, timelineIsSeekable,
/* isDynamic= */ false, /* isDynamic= */ false,
/* isLive= */ timelineIsLive,
/* manifest= */ null, /* manifest= */ null,
tag)); tag));
} }
......
...@@ -68,7 +68,8 @@ public final class SilenceMediaSource extends BaseMediaSource { ...@@ -68,7 +68,8 @@ public final class SilenceMediaSource extends BaseMediaSource {
@Override @Override
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
refreshSourceInfo( refreshSourceInfo(
new SinglePeriodTimeline(durationUs, /* isSeekable= */ true, /* isDynamic= */ false)); new SinglePeriodTimeline(
durationUs, /* isSeekable= */ true, /* isDynamic= */ false, /* isLive= */ false));
} }
@Override @Override
......
...@@ -35,6 +35,7 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -35,6 +35,7 @@ public final class SinglePeriodTimeline extends Timeline {
private final long windowDefaultStartPositionUs; private final long windowDefaultStartPositionUs;
private final boolean isSeekable; private final boolean isSeekable;
private final boolean isDynamic; private final boolean isDynamic;
private final boolean isLive;
@Nullable private final Object tag; @Nullable private final Object tag;
@Nullable private final Object manifest; @Nullable private final Object manifest;
...@@ -44,9 +45,11 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -44,9 +45,11 @@ public final class SinglePeriodTimeline extends Timeline {
* @param durationUs The duration of the period, in microseconds. * @param durationUs The duration of the period, in microseconds.
* @param isSeekable Whether seeking is supported within the period. * @param isSeekable Whether seeking is supported within the period.
* @param isDynamic Whether the window may change when the timeline is updated. * @param isDynamic Whether the window may change when the timeline is updated.
* @param isLive Whether the window is live.
*/ */
public SinglePeriodTimeline(long durationUs, boolean isSeekable, boolean isDynamic) { public SinglePeriodTimeline(
this(durationUs, isSeekable, isDynamic, /* manifest= */ null, /* tag= */ null); long durationUs, boolean isSeekable, boolean isDynamic, boolean isLive) {
this(durationUs, isSeekable, isDynamic, isLive, /* manifest= */ null, /* tag= */ null);
} }
/** /**
...@@ -55,6 +58,7 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -55,6 +58,7 @@ public final class SinglePeriodTimeline extends Timeline {
* @param durationUs The duration of the period, in microseconds. * @param durationUs The duration of the period, in microseconds.
* @param isSeekable Whether seeking is supported within the period. * @param isSeekable Whether seeking is supported within the period.
* @param isDynamic Whether the window may change when the timeline is updated. * @param isDynamic Whether the window may change when the timeline is updated.
* @param isLive Whether the window is live.
* @param manifest The manifest. May be {@code null}. * @param manifest The manifest. May be {@code null}.
* @param tag A tag used for {@link Window#tag}. * @param tag A tag used for {@link Window#tag}.
*/ */
...@@ -62,6 +66,7 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -62,6 +66,7 @@ public final class SinglePeriodTimeline extends Timeline {
long durationUs, long durationUs,
boolean isSeekable, boolean isSeekable,
boolean isDynamic, boolean isDynamic,
boolean isLive,
@Nullable Object manifest, @Nullable Object manifest,
@Nullable Object tag) { @Nullable Object tag) {
this( this(
...@@ -71,6 +76,7 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -71,6 +76,7 @@ public final class SinglePeriodTimeline extends Timeline {
/* windowDefaultStartPositionUs= */ 0, /* windowDefaultStartPositionUs= */ 0,
isSeekable, isSeekable,
isDynamic, isDynamic,
isLive,
manifest, manifest,
tag); tag);
} }
...@@ -87,6 +93,7 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -87,6 +93,7 @@ public final class SinglePeriodTimeline extends Timeline {
* which to begin playback, in microseconds. * which to begin playback, in microseconds.
* @param isSeekable Whether seeking is supported within the window. * @param isSeekable Whether seeking is supported within the window.
* @param isDynamic Whether the window may change when the timeline is updated. * @param isDynamic Whether the window may change when the timeline is updated.
* @param isLive Whether the window is live.
* @param manifest The manifest. May be (@code null}. * @param manifest The manifest. May be (@code null}.
* @param tag A tag used for {@link Timeline.Window#tag}. * @param tag A tag used for {@link Timeline.Window#tag}.
*/ */
...@@ -97,6 +104,7 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -97,6 +104,7 @@ public final class SinglePeriodTimeline extends Timeline {
long windowDefaultStartPositionUs, long windowDefaultStartPositionUs,
boolean isSeekable, boolean isSeekable,
boolean isDynamic, boolean isDynamic,
boolean isLive,
@Nullable Object manifest, @Nullable Object manifest,
@Nullable Object tag) { @Nullable Object tag) {
this( this(
...@@ -108,6 +116,7 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -108,6 +116,7 @@ public final class SinglePeriodTimeline extends Timeline {
windowDefaultStartPositionUs, windowDefaultStartPositionUs,
isSeekable, isSeekable,
isDynamic, isDynamic,
isLive,
manifest, manifest,
tag); tag);
} }
...@@ -127,6 +136,7 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -127,6 +136,7 @@ public final class SinglePeriodTimeline extends Timeline {
* which to begin playback, in microseconds. * which to begin playback, in microseconds.
* @param isSeekable Whether seeking is supported within the window. * @param isSeekable Whether seeking is supported within the window.
* @param isDynamic Whether the window may change when the timeline is updated. * @param isDynamic Whether the window may change when the timeline is updated.
* @param isLive Whether the window is live.
* @param manifest The manifest. May be {@code null}. * @param manifest The manifest. May be {@code null}.
* @param tag A tag used for {@link Timeline.Window#tag}. * @param tag A tag used for {@link Timeline.Window#tag}.
*/ */
...@@ -139,6 +149,7 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -139,6 +149,7 @@ public final class SinglePeriodTimeline extends Timeline {
long windowDefaultStartPositionUs, long windowDefaultStartPositionUs,
boolean isSeekable, boolean isSeekable,
boolean isDynamic, boolean isDynamic,
boolean isLive,
@Nullable Object manifest, @Nullable Object manifest,
@Nullable Object tag) { @Nullable Object tag) {
this.presentationStartTimeMs = presentationStartTimeMs; this.presentationStartTimeMs = presentationStartTimeMs;
...@@ -149,6 +160,7 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -149,6 +160,7 @@ public final class SinglePeriodTimeline extends Timeline {
this.windowDefaultStartPositionUs = windowDefaultStartPositionUs; this.windowDefaultStartPositionUs = windowDefaultStartPositionUs;
this.isSeekable = isSeekable; this.isSeekable = isSeekable;
this.isDynamic = isDynamic; this.isDynamic = isDynamic;
this.isLive = isLive;
this.manifest = manifest; this.manifest = manifest;
this.tag = tag; this.tag = tag;
} }
...@@ -182,6 +194,7 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -182,6 +194,7 @@ public final class SinglePeriodTimeline extends Timeline {
windowStartTimeMs, windowStartTimeMs,
isSeekable, isSeekable,
isDynamic, isDynamic,
isLive,
windowDefaultStartPositionUs, windowDefaultStartPositionUs,
windowDurationUs, windowDurationUs,
0, 0,
......
...@@ -291,7 +291,12 @@ public final class SingleSampleMediaSource extends BaseMediaSource { ...@@ -291,7 +291,12 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP); dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP);
timeline = timeline =
new SinglePeriodTimeline( new SinglePeriodTimeline(
durationUs, /* isSeekable= */ true, /* isDynamic= */ false, /* manifest= */ null, tag); durationUs,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* isLive= */ false,
/* manifest= */ null,
tag);
} }
// MediaSource implementation. // MediaSource implementation.
......
...@@ -44,7 +44,8 @@ public final class MediaPeriodQueueTest { ...@@ -44,7 +44,8 @@ public final class MediaPeriodQueueTest {
private static final long SECOND_AD_START_TIME_US = 20 * C.MICROS_PER_SECOND; private static final long SECOND_AD_START_TIME_US = 20 * C.MICROS_PER_SECOND;
private static final Timeline CONTENT_TIMELINE = private static final Timeline CONTENT_TIMELINE =
new SinglePeriodTimeline(CONTENT_DURATION_US, /* isSeekable= */ true, /* isDynamic= */ false); new SinglePeriodTimeline(
CONTENT_DURATION_US, /* isSeekable= */ true, /* isDynamic= */ false, /* isLive= */ false);
private static final Uri AD_URI = Uri.EMPTY; private static final Uri AD_URI = Uri.EMPTY;
private MediaPeriodQueue mediaPeriodQueue; private MediaPeriodQueue mediaPeriodQueue;
......
...@@ -64,7 +64,12 @@ public final class ClippingMediaSourceTest { ...@@ -64,7 +64,12 @@ public final class ClippingMediaSourceTest {
@Test @Test
public void testNoClipping() throws IOException { public void testNoClipping() throws IOException {
Timeline timeline = new SinglePeriodTimeline(TEST_PERIOD_DURATION_US, true, false); Timeline timeline =
new SinglePeriodTimeline(
TEST_PERIOD_DURATION_US,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* isLive= */ false);
Timeline clippedTimeline = getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US); Timeline clippedTimeline = getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US);
...@@ -78,7 +83,12 @@ public final class ClippingMediaSourceTest { ...@@ -78,7 +83,12 @@ public final class ClippingMediaSourceTest {
@Test @Test
public void testClippingUnseekableWindowThrows() throws IOException { public void testClippingUnseekableWindowThrows() throws IOException {
Timeline timeline = new SinglePeriodTimeline(TEST_PERIOD_DURATION_US, false, false); Timeline timeline =
new SinglePeriodTimeline(
TEST_PERIOD_DURATION_US,
/* isSeekable= */ false,
/* isDynamic= */ false,
/* isLive= */ false);
// If the unseekable window isn't clipped, clipping succeeds. // If the unseekable window isn't clipped, clipping succeeds.
getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US); getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US);
...@@ -93,7 +103,12 @@ public final class ClippingMediaSourceTest { ...@@ -93,7 +103,12 @@ public final class ClippingMediaSourceTest {
@Test @Test
public void testClippingStart() throws IOException { public void testClippingStart() throws IOException {
Timeline timeline = new SinglePeriodTimeline(TEST_PERIOD_DURATION_US, true, false); Timeline timeline =
new SinglePeriodTimeline(
TEST_PERIOD_DURATION_US,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* isLive= */ false);
Timeline clippedTimeline = Timeline clippedTimeline =
getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US, TEST_PERIOD_DURATION_US); getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US, TEST_PERIOD_DURATION_US);
...@@ -105,7 +120,12 @@ public final class ClippingMediaSourceTest { ...@@ -105,7 +120,12 @@ public final class ClippingMediaSourceTest {
@Test @Test
public void testClippingEnd() throws IOException { public void testClippingEnd() throws IOException {
Timeline timeline = new SinglePeriodTimeline(TEST_PERIOD_DURATION_US, true, false); Timeline timeline =
new SinglePeriodTimeline(
TEST_PERIOD_DURATION_US,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* isLive= */ false);
Timeline clippedTimeline = Timeline clippedTimeline =
getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US); getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US);
...@@ -121,7 +141,8 @@ public final class ClippingMediaSourceTest { ...@@ -121,7 +141,8 @@ public final class ClippingMediaSourceTest {
// to it having loaded sufficient data to establish its duration and seekability. Such timelines // to it having loaded sufficient data to establish its duration and seekability. Such timelines
// should not result in clipping failure. // should not result in clipping failure.
Timeline timeline = Timeline timeline =
new SinglePeriodTimeline(C.TIME_UNSET, /* isSeekable= */ false, /* isDynamic= */ true); new SinglePeriodTimeline(
C.TIME_UNSET, /* isSeekable= */ false, /* isDynamic= */ true, /* isLive= */ true);
Timeline clippedTimeline = Timeline clippedTimeline =
getClippedTimeline( getClippedTimeline(
...@@ -139,7 +160,8 @@ public final class ClippingMediaSourceTest { ...@@ -139,7 +160,8 @@ public final class ClippingMediaSourceTest {
new SinglePeriodTimeline( new SinglePeriodTimeline(
/* durationUs= */ TEST_PERIOD_DURATION_US, /* durationUs= */ TEST_PERIOD_DURATION_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ false); /* isDynamic= */ false,
/* isLive= */ false);
// When clipping to the end, the clipped timeline should also have a duration. // When clipping to the end, the clipped timeline should also have a duration.
Timeline clippedTimeline = Timeline clippedTimeline =
...@@ -153,7 +175,10 @@ public final class ClippingMediaSourceTest { ...@@ -153,7 +175,10 @@ public final class ClippingMediaSourceTest {
// Create a child timeline that has an unknown duration. // Create a child timeline that has an unknown duration.
Timeline timeline = Timeline timeline =
new SinglePeriodTimeline( new SinglePeriodTimeline(
/* durationUs= */ C.TIME_UNSET, /* isSeekable= */ true, /* isDynamic= */ false); /* durationUs= */ C.TIME_UNSET,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* isLive= */ false);
// When clipping to the end, the clipped timeline should also have an unset duration. // When clipping to the end, the clipped timeline should also have an unset duration.
Timeline clippedTimeline = Timeline clippedTimeline =
...@@ -164,7 +189,12 @@ public final class ClippingMediaSourceTest { ...@@ -164,7 +189,12 @@ public final class ClippingMediaSourceTest {
@Test @Test
public void testClippingStartAndEnd() throws IOException { public void testClippingStartAndEnd() throws IOException {
Timeline timeline = new SinglePeriodTimeline(TEST_PERIOD_DURATION_US, true, false); Timeline timeline =
new SinglePeriodTimeline(
TEST_PERIOD_DURATION_US,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* isLive= */ false);
Timeline clippedTimeline = Timeline clippedTimeline =
getClippedTimeline( getClippedTimeline(
...@@ -185,6 +215,7 @@ public final class ClippingMediaSourceTest { ...@@ -185,6 +215,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true, /* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null, /* manifest= */ null,
/* tag= */ null); /* tag= */ null);
...@@ -207,6 +238,7 @@ public final class ClippingMediaSourceTest { ...@@ -207,6 +238,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true, /* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null, /* manifest= */ null,
/* tag= */ null); /* tag= */ null);
Timeline timeline2 = Timeline timeline2 =
...@@ -217,6 +249,7 @@ public final class ClippingMediaSourceTest { ...@@ -217,6 +249,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true, /* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null, /* manifest= */ null,
/* tag= */ null); /* tag= */ null);
...@@ -256,6 +289,7 @@ public final class ClippingMediaSourceTest { ...@@ -256,6 +289,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true, /* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null, /* manifest= */ null,
/* tag= */ null); /* tag= */ null);
Timeline timeline2 = Timeline timeline2 =
...@@ -266,6 +300,7 @@ public final class ClippingMediaSourceTest { ...@@ -266,6 +300,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true, /* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null, /* manifest= */ null,
/* tag= */ null); /* tag= */ null);
...@@ -305,6 +340,7 @@ public final class ClippingMediaSourceTest { ...@@ -305,6 +340,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true, /* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null, /* manifest= */ null,
/* tag= */ null); /* tag= */ null);
Timeline timeline2 = Timeline timeline2 =
...@@ -315,6 +351,7 @@ public final class ClippingMediaSourceTest { ...@@ -315,6 +351,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true, /* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null, /* manifest= */ null,
/* tag= */ null); /* tag= */ null);
...@@ -355,6 +392,7 @@ public final class ClippingMediaSourceTest { ...@@ -355,6 +392,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true, /* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null, /* manifest= */ null,
/* tag= */ null); /* tag= */ null);
Timeline timeline2 = Timeline timeline2 =
...@@ -365,6 +403,7 @@ public final class ClippingMediaSourceTest { ...@@ -365,6 +403,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true, /* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null, /* manifest= */ null,
/* tag= */ null); /* tag= */ null);
...@@ -480,7 +519,10 @@ public final class ClippingMediaSourceTest { ...@@ -480,7 +519,10 @@ public final class ClippingMediaSourceTest {
throws IOException { throws IOException {
Timeline timeline = Timeline timeline =
new SinglePeriodTimeline( new SinglePeriodTimeline(
TEST_PERIOD_DURATION_US, /* isSeekable= */ true, /* isDynamic= */ false); TEST_PERIOD_DURATION_US,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* isLive= */ false);
FakeMediaSource fakeMediaSource = FakeMediaSource fakeMediaSource =
new FakeMediaSource(timeline) { new FakeMediaSource(timeline) {
@Override @Override
......
...@@ -41,7 +41,9 @@ public final class SinglePeriodTimelineTest { ...@@ -41,7 +41,9 @@ public final class SinglePeriodTimelineTest {
@Test @Test
public void testGetPeriodPositionDynamicWindowUnknownDuration() { public void testGetPeriodPositionDynamicWindowUnknownDuration() {
SinglePeriodTimeline timeline = new SinglePeriodTimeline(C.TIME_UNSET, false, true); SinglePeriodTimeline timeline =
new SinglePeriodTimeline(
C.TIME_UNSET, /* isSeekable= */ false, /* isDynamic= */ true, /* isLive= */ true);
// Should return null with any positive position projection. // Should return null with any positive position projection.
Pair<Object, Long> position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, 1); Pair<Object, Long> position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, 1);
assertThat(position).isNull(); assertThat(position).isNull();
...@@ -62,6 +64,7 @@ public final class SinglePeriodTimelineTest { ...@@ -62,6 +64,7 @@ public final class SinglePeriodTimelineTest {
/* windowDefaultStartPositionUs= */ 0, /* windowDefaultStartPositionUs= */ 0,
/* isSeekable= */ false, /* isSeekable= */ false,
/* isDynamic= */ true, /* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null, /* manifest= */ null,
/* tag= */ null); /* tag= */ null);
// Should return null with a positive position projection beyond window duration. // Should return null with a positive position projection beyond window duration.
...@@ -85,6 +88,7 @@ public final class SinglePeriodTimelineTest { ...@@ -85,6 +88,7 @@ public final class SinglePeriodTimelineTest {
/* durationUs= */ C.TIME_UNSET, /* durationUs= */ C.TIME_UNSET,
/* isSeekable= */ false, /* isSeekable= */ false,
/* isDynamic= */ false, /* isDynamic= */ false,
/* isLive= */ false,
/* manifest= */ null, /* manifest= */ null,
/* tag= */ null); /* tag= */ null);
...@@ -104,6 +108,7 @@ public final class SinglePeriodTimelineTest { ...@@ -104,6 +108,7 @@ public final class SinglePeriodTimelineTest {
/* durationUs= */ C.TIME_UNSET, /* durationUs= */ C.TIME_UNSET,
/* isSeekable= */ false, /* isSeekable= */ false,
/* isDynamic= */ false, /* isDynamic= */ false,
/* isLive= */ false,
/* manifest= */ null, /* manifest= */ null,
tag); tag);
...@@ -117,6 +122,7 @@ public final class SinglePeriodTimelineTest { ...@@ -117,6 +122,7 @@ public final class SinglePeriodTimelineTest {
/* durationUs= */ C.TIME_UNSET, /* durationUs= */ C.TIME_UNSET,
/* isSeekable= */ false, /* isSeekable= */ false,
/* isDynamic= */ false, /* isDynamic= */ false,
/* isLive= */ false,
/* manifest= */ null, /* manifest= */ null,
/* tag= */ null); /* tag= */ null);
Object uid = timeline.getPeriod(/* periodIndex= */ 0, period, /* setIds= */ true).uid; Object uid = timeline.getPeriod(/* periodIndex= */ 0, period, /* setIds= */ true).uid;
......
...@@ -1216,10 +1216,6 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1216,10 +1216,6 @@ public final class DashMediaSource extends BaseMediaSource {
Assertions.checkIndex(windowIndex, 0, 1); Assertions.checkIndex(windowIndex, 0, 1);
long windowDefaultStartPositionUs = getAdjustedWindowDefaultStartPositionUs( long windowDefaultStartPositionUs = getAdjustedWindowDefaultStartPositionUs(
defaultPositionProjectionUs); defaultPositionProjectionUs);
boolean isDynamic =
manifest.dynamic
&& manifest.minUpdatePeriodMs != C.TIME_UNSET
&& manifest.durationMs == C.TIME_UNSET;
return window.set( return window.set(
Window.SINGLE_WINDOW_UID, Window.SINGLE_WINDOW_UID,
windowTag, windowTag,
...@@ -1227,7 +1223,8 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1227,7 +1223,8 @@ public final class DashMediaSource extends BaseMediaSource {
presentationStartTimeMs, presentationStartTimeMs,
windowStartTimeMs, windowStartTimeMs,
/* isSeekable= */ true, /* isSeekable= */ true,
isDynamic, /* isDynamic= */ isMovingLiveWindow(manifest),
/* isLive= */ manifest.dynamic,
windowDefaultStartPositionUs, windowDefaultStartPositionUs,
windowDurationUs, windowDurationUs,
/* firstPeriodIndex= */ 0, /* firstPeriodIndex= */ 0,
...@@ -1247,7 +1244,7 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1247,7 +1244,7 @@ public final class DashMediaSource extends BaseMediaSource {
private long getAdjustedWindowDefaultStartPositionUs(long defaultPositionProjectionUs) { private long getAdjustedWindowDefaultStartPositionUs(long defaultPositionProjectionUs) {
long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs; long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs;
if (!manifest.dynamic) { if (!isMovingLiveWindow(manifest)) {
return windowDefaultStartPositionUs; return windowDefaultStartPositionUs;
} }
if (defaultPositionProjectionUs > 0) { if (defaultPositionProjectionUs > 0) {
...@@ -1292,6 +1289,12 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1292,6 +1289,12 @@ public final class DashMediaSource extends BaseMediaSource {
Assertions.checkIndex(periodIndex, 0, getPeriodCount()); Assertions.checkIndex(periodIndex, 0, getPeriodCount());
return firstPeriodId + periodIndex; return firstPeriodId + periodIndex;
} }
private static boolean isMovingLiveWindow(DashManifest manifest) {
return manifest.dynamic
&& manifest.minUpdatePeriodMs != C.TIME_UNSET
&& manifest.durationMs == C.TIME_UNSET;
}
} }
private final class DefaultPlayerEmsgCallback implements PlayerEmsgCallback { private final class DefaultPlayerEmsgCallback implements PlayerEmsgCallback {
......
...@@ -439,6 +439,7 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -439,6 +439,7 @@ public final class HlsMediaSource extends BaseMediaSource
windowDefaultStartPositionUs, windowDefaultStartPositionUs,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ !playlist.hasEndTag, /* isDynamic= */ !playlist.hasEndTag,
/* isLive= */ true,
manifest, manifest,
tag); tag);
} else /* not live */ { } else /* not live */ {
...@@ -455,6 +456,7 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -455,6 +456,7 @@ public final class HlsMediaSource extends BaseMediaSource
windowDefaultStartPositionUs, windowDefaultStartPositionUs,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ false, /* isDynamic= */ false,
/* isLive= */ false,
manifest, manifest,
tag); tag);
} }
......
...@@ -696,7 +696,8 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -696,7 +696,8 @@ public final class SsMediaSource extends BaseMediaSource
/* windowPositionInPeriodUs= */ 0, /* windowPositionInPeriodUs= */ 0,
/* windowDefaultStartPositionUs= */ 0, /* windowDefaultStartPositionUs= */ 0,
/* isSeekable= */ true, /* isSeekable= */ true,
manifest.isLive, /* isDynamic= */ manifest.isLive,
/* isLive= */ manifest.isLive,
manifest, manifest,
tag); tag);
} else if (manifest.isLive) { } else if (manifest.isLive) {
...@@ -719,6 +720,7 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -719,6 +720,7 @@ public final class SsMediaSource extends BaseMediaSource
defaultStartPositionUs, defaultStartPositionUs,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true, /* isDynamic= */ true,
/* isLive= */ true,
manifest, manifest,
tag); tag);
} else { } else {
...@@ -732,6 +734,7 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -732,6 +734,7 @@ public final class SsMediaSource extends BaseMediaSource
/* windowDefaultStartPositionUs= */ 0, /* windowDefaultStartPositionUs= */ 0,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ false, /* isDynamic= */ false,
/* isLive= */ false,
manifest, manifest,
tag); tag);
} }
......
...@@ -189,6 +189,7 @@ public final class FakeTimeline extends Timeline { ...@@ -189,6 +189,7 @@ public final class FakeTimeline extends Timeline {
/* windowStartTimeMs= */ C.TIME_UNSET, /* windowStartTimeMs= */ C.TIME_UNSET,
windowDefinition.isSeekable, windowDefinition.isSeekable,
windowDefinition.isDynamic, windowDefinition.isDynamic,
/* isLive= */ windowDefinition.isDynamic,
/* defaultPositionUs= */ 0, /* defaultPositionUs= */ 0,
windowDefinition.durationUs, windowDefinition.durationUs,
periodOffsets[windowIndex], periodOffsets[windowIndex],
......
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