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
This commit is contained in:
tonihei 2019-09-30 17:30:14 +01:00 committed by Oliver Woodman
parent 26dd4aad68
commit dd4f9bcaae
17 changed files with 138 additions and 41 deletions

View file

@ -80,6 +80,9 @@
and move it to the core library.
* Move `LibvpxVideoRenderer.MSG_SET_OUTPUT_BUFFER_RENDERER` to
`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) ###

View file

@ -121,6 +121,7 @@ import java.util.Arrays;
/* windowStartTimeMs= */ C.TIME_UNSET,
/* isSeekable= */ !isDynamic,
isDynamic,
/* isLive= */ isDynamic,
defaultPositionsUs[windowIndex],
durationUs,
/* firstPeriodIndex= */ windowIndex,

View file

@ -63,7 +63,8 @@ public class ImaAdsLoaderTest {
private static final long CONTENT_DURATION_US = 10 * C.MICROS_PER_SECOND;
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 long TEST_AD_DURATION_US = 5 * C.MICROS_PER_SECOND;
private static final long[][] PREROLL_ADS_DURATIONS_US = new long[][] {{TEST_AD_DURATION_US}};

View file

@ -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
* 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
* Window#isDynamic} set to true if the stream is still live. Its default position is typically near
* to the live edge (indicated by the black dot in the figure above).
* Window#isLive} set to true to indicate it's a live stream and {@link Window#isDynamic} set to
* 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>
*
@ -158,8 +159,13 @@ public abstract class Timeline {
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;
/**
@ -200,6 +206,7 @@ public abstract class Timeline {
long windowStartTimeMs,
boolean isSeekable,
boolean isDynamic,
boolean isLive,
long defaultPositionUs,
long durationUs,
int firstPeriodIndex,
@ -212,6 +219,7 @@ public abstract class Timeline {
this.windowStartTimeMs = windowStartTimeMs;
this.isSeekable = isSeekable;
this.isDynamic = isDynamic;
this.isLive = isLive;
this.defaultPositionUs = defaultPositionUs;
this.durationUs = durationUs;
this.firstPeriodIndex = firstPeriodIndex;

View file

@ -317,6 +317,7 @@ public final class MaskingMediaSource extends CompositeMediaSource<Void> {
/* isSeekable= */ false,
// Dynamic window to indicate pending timeline updates.
/* isDynamic= */ true,
/* isLive= */ false,
/* defaultPositionUs= */ 0,
/* durationUs= */ C.TIME_UNSET,
/* firstPeriodIndex= */ 0,

View file

@ -74,13 +74,14 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
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 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;
private int enabledTrackCount;
private long durationUs;
private long length;
private boolean isLive;
private long lastSeekPositionUs;
private long pendingResetPositionUs;
@ -551,7 +553,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
long largestQueuedTimestampUs = getLargestQueuedTimestampUs();
durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0
: largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US;
listener.onSourceInfoRefreshed(durationUs, isSeekable);
listener.onSourceInfoRefreshed(durationUs, isSeekable, isLive);
}
eventDispatcher.loadCompleted(
loadable.dataSpec,
@ -740,14 +742,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
trackArray[i] = new TrackGroup(trackFormat);
}
dataType =
length == C.LENGTH_UNSET && seekMap.getDurationUs() == C.TIME_UNSET
? C.DATA_TYPE_MEDIA_PROGRESSIVE_LIVE
: C.DATA_TYPE_MEDIA;
isLive = length == C.LENGTH_UNSET && seekMap.getDurationUs() == C.TIME_UNSET;
dataType = isLive ? C.DATA_TYPE_MEDIA_PROGRESSIVE_LIVE : C.DATA_TYPE_MEDIA;
preparedState =
new PreparedState(seekMap, new TrackGroupArray(trackArray), trackIsAudioVideoFlags);
prepared = true;
listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable());
listener.onSourceInfoRefreshed(durationUs, seekMap.isSeekable(), isLive);
Assertions.checkNotNull(callback).onPrepared(this);
}

View file

@ -220,6 +220,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
private long timelineDurationUs;
private boolean timelineIsSeekable;
private boolean timelineIsLive;
@Nullable private TransferListener transferListener;
// TODO: Make private when ExtractorMediaSource is deleted.
@ -253,7 +254,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
transferListener = mediaTransferListener;
drmSessionManager.prepare();
notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable);
notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable, timelineIsLive);
}
@Override
@ -293,27 +294,32 @@ public final class ProgressiveMediaSource extends BaseMediaSource
// ProgressiveMediaPeriod.Listener implementation.
@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.
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.
return;
}
notifySourceInfoRefreshed(durationUs, isSeekable);
notifySourceInfoRefreshed(durationUs, isSeekable, isLive);
}
// Internal methods.
private void notifySourceInfoRefreshed(long durationUs, boolean isSeekable) {
private void notifySourceInfoRefreshed(long durationUs, boolean isSeekable, boolean isLive) {
timelineDurationUs = durationUs;
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(
new SinglePeriodTimeline(
timelineDurationUs,
timelineIsSeekable,
/* isDynamic= */ false,
/* isLive= */ timelineIsLive,
/* manifest= */ null,
tag));
}

View file

@ -68,7 +68,8 @@ public final class SilenceMediaSource extends BaseMediaSource {
@Override
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
refreshSourceInfo(
new SinglePeriodTimeline(durationUs, /* isSeekable= */ true, /* isDynamic= */ false));
new SinglePeriodTimeline(
durationUs, /* isSeekable= */ true, /* isDynamic= */ false, /* isLive= */ false));
}
@Override

View file

@ -35,6 +35,7 @@ public final class SinglePeriodTimeline extends Timeline {
private final long windowDefaultStartPositionUs;
private final boolean isSeekable;
private final boolean isDynamic;
private final boolean isLive;
@Nullable private final Object tag;
@Nullable private final Object manifest;
@ -44,9 +45,11 @@ public final class SinglePeriodTimeline extends Timeline {
* @param durationUs The duration of the period, in microseconds.
* @param isSeekable Whether seeking is supported within the period.
* @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) {
this(durationUs, isSeekable, isDynamic, /* manifest= */ null, /* tag= */ null);
public SinglePeriodTimeline(
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 {
* @param durationUs The duration of the period, in microseconds.
* @param isSeekable Whether seeking is supported within the period.
* @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 tag A tag used for {@link Window#tag}.
*/
@ -62,6 +66,7 @@ public final class SinglePeriodTimeline extends Timeline {
long durationUs,
boolean isSeekable,
boolean isDynamic,
boolean isLive,
@Nullable Object manifest,
@Nullable Object tag) {
this(
@ -71,6 +76,7 @@ public final class SinglePeriodTimeline extends Timeline {
/* windowDefaultStartPositionUs= */ 0,
isSeekable,
isDynamic,
isLive,
manifest,
tag);
}
@ -87,6 +93,7 @@ public final class SinglePeriodTimeline extends Timeline {
* which to begin playback, in microseconds.
* @param isSeekable Whether seeking is supported within the window.
* @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 tag A tag used for {@link Timeline.Window#tag}.
*/
@ -97,6 +104,7 @@ public final class SinglePeriodTimeline extends Timeline {
long windowDefaultStartPositionUs,
boolean isSeekable,
boolean isDynamic,
boolean isLive,
@Nullable Object manifest,
@Nullable Object tag) {
this(
@ -108,6 +116,7 @@ public final class SinglePeriodTimeline extends Timeline {
windowDefaultStartPositionUs,
isSeekable,
isDynamic,
isLive,
manifest,
tag);
}
@ -127,6 +136,7 @@ public final class SinglePeriodTimeline extends Timeline {
* which to begin playback, in microseconds.
* @param isSeekable Whether seeking is supported within the window.
* @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 tag A tag used for {@link Timeline.Window#tag}.
*/
@ -139,6 +149,7 @@ public final class SinglePeriodTimeline extends Timeline {
long windowDefaultStartPositionUs,
boolean isSeekable,
boolean isDynamic,
boolean isLive,
@Nullable Object manifest,
@Nullable Object tag) {
this.presentationStartTimeMs = presentationStartTimeMs;
@ -149,6 +160,7 @@ public final class SinglePeriodTimeline extends Timeline {
this.windowDefaultStartPositionUs = windowDefaultStartPositionUs;
this.isSeekable = isSeekable;
this.isDynamic = isDynamic;
this.isLive = isLive;
this.manifest = manifest;
this.tag = tag;
}
@ -182,6 +194,7 @@ public final class SinglePeriodTimeline extends Timeline {
windowStartTimeMs,
isSeekable,
isDynamic,
isLive,
windowDefaultStartPositionUs,
windowDurationUs,
0,

View file

@ -291,7 +291,12 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
dataSpec = new DataSpec(uri, DataSpec.FLAG_ALLOW_GZIP);
timeline =
new SinglePeriodTimeline(
durationUs, /* isSeekable= */ true, /* isDynamic= */ false, /* manifest= */ null, tag);
durationUs,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* isLive= */ false,
/* manifest= */ null,
tag);
}
// MediaSource implementation.

View file

@ -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 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 MediaPeriodQueue mediaPeriodQueue;

View file

@ -64,7 +64,12 @@ public final class ClippingMediaSourceTest {
@Test
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);
@ -78,7 +83,12 @@ public final class ClippingMediaSourceTest {
@Test
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.
getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US);
@ -93,7 +103,12 @@ public final class ClippingMediaSourceTest {
@Test
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 =
getClippedTimeline(timeline, TEST_CLIP_AMOUNT_US, TEST_PERIOD_DURATION_US);
@ -105,7 +120,12 @@ public final class ClippingMediaSourceTest {
@Test
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 =
getClippedTimeline(timeline, 0, TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US);
@ -121,7 +141,8 @@ public final class ClippingMediaSourceTest {
// to it having loaded sufficient data to establish its duration and seekability. Such timelines
// should not result in clipping failure.
Timeline timeline =
new SinglePeriodTimeline(C.TIME_UNSET, /* isSeekable= */ false, /* isDynamic= */ true);
new SinglePeriodTimeline(
C.TIME_UNSET, /* isSeekable= */ false, /* isDynamic= */ true, /* isLive= */ true);
Timeline clippedTimeline =
getClippedTimeline(
@ -139,7 +160,8 @@ public final class ClippingMediaSourceTest {
new SinglePeriodTimeline(
/* durationUs= */ TEST_PERIOD_DURATION_US,
/* isSeekable= */ true,
/* isDynamic= */ false);
/* isDynamic= */ false,
/* isLive= */ false);
// When clipping to the end, the clipped timeline should also have a duration.
Timeline clippedTimeline =
@ -153,7 +175,10 @@ public final class ClippingMediaSourceTest {
// Create a child timeline that has an unknown duration.
Timeline timeline =
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.
Timeline clippedTimeline =
@ -164,7 +189,12 @@ public final class ClippingMediaSourceTest {
@Test
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 =
getClippedTimeline(
@ -185,6 +215,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null,
/* tag= */ null);
@ -207,6 +238,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null,
/* tag= */ null);
Timeline timeline2 =
@ -217,6 +249,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null,
/* tag= */ null);
@ -256,6 +289,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null,
/* tag= */ null);
Timeline timeline2 =
@ -266,6 +300,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null,
/* tag= */ null);
@ -305,6 +340,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null,
/* tag= */ null);
Timeline timeline2 =
@ -315,6 +351,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null,
/* tag= */ null);
@ -355,6 +392,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null,
/* tag= */ null);
Timeline timeline2 =
@ -365,6 +403,7 @@ public final class ClippingMediaSourceTest {
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true,
/* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null,
/* tag= */ null);
@ -480,7 +519,10 @@ public final class ClippingMediaSourceTest {
throws IOException {
Timeline timeline =
new SinglePeriodTimeline(
TEST_PERIOD_DURATION_US, /* isSeekable= */ true, /* isDynamic= */ false);
TEST_PERIOD_DURATION_US,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* isLive= */ false);
FakeMediaSource fakeMediaSource =
new FakeMediaSource(timeline) {
@Override

View file

@ -41,7 +41,9 @@ public final class SinglePeriodTimelineTest {
@Test
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.
Pair<Object, Long> position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, 1);
assertThat(position).isNull();
@ -62,6 +64,7 @@ public final class SinglePeriodTimelineTest {
/* windowDefaultStartPositionUs= */ 0,
/* isSeekable= */ false,
/* isDynamic= */ true,
/* isLive= */ true,
/* manifest= */ null,
/* tag= */ null);
// Should return null with a positive position projection beyond window duration.
@ -85,6 +88,7 @@ public final class SinglePeriodTimelineTest {
/* durationUs= */ C.TIME_UNSET,
/* isSeekable= */ false,
/* isDynamic= */ false,
/* isLive= */ false,
/* manifest= */ null,
/* tag= */ null);
@ -104,6 +108,7 @@ public final class SinglePeriodTimelineTest {
/* durationUs= */ C.TIME_UNSET,
/* isSeekable= */ false,
/* isDynamic= */ false,
/* isLive= */ false,
/* manifest= */ null,
tag);
@ -117,6 +122,7 @@ public final class SinglePeriodTimelineTest {
/* durationUs= */ C.TIME_UNSET,
/* isSeekable= */ false,
/* isDynamic= */ false,
/* isLive= */ false,
/* manifest= */ null,
/* tag= */ null);
Object uid = timeline.getPeriod(/* periodIndex= */ 0, period, /* setIds= */ true).uid;

View file

@ -1216,10 +1216,6 @@ public final class DashMediaSource extends BaseMediaSource {
Assertions.checkIndex(windowIndex, 0, 1);
long windowDefaultStartPositionUs = getAdjustedWindowDefaultStartPositionUs(
defaultPositionProjectionUs);
boolean isDynamic =
manifest.dynamic
&& manifest.minUpdatePeriodMs != C.TIME_UNSET
&& manifest.durationMs == C.TIME_UNSET;
return window.set(
Window.SINGLE_WINDOW_UID,
windowTag,
@ -1227,7 +1223,8 @@ public final class DashMediaSource extends BaseMediaSource {
presentationStartTimeMs,
windowStartTimeMs,
/* isSeekable= */ true,
isDynamic,
/* isDynamic= */ isMovingLiveWindow(manifest),
/* isLive= */ manifest.dynamic,
windowDefaultStartPositionUs,
windowDurationUs,
/* firstPeriodIndex= */ 0,
@ -1247,7 +1244,7 @@ public final class DashMediaSource extends BaseMediaSource {
private long getAdjustedWindowDefaultStartPositionUs(long defaultPositionProjectionUs) {
long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs;
if (!manifest.dynamic) {
if (!isMovingLiveWindow(manifest)) {
return windowDefaultStartPositionUs;
}
if (defaultPositionProjectionUs > 0) {
@ -1292,6 +1289,12 @@ public final class DashMediaSource extends BaseMediaSource {
Assertions.checkIndex(periodIndex, 0, getPeriodCount());
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 {

View file

@ -439,6 +439,7 @@ public final class HlsMediaSource extends BaseMediaSource
windowDefaultStartPositionUs,
/* isSeekable= */ true,
/* isDynamic= */ !playlist.hasEndTag,
/* isLive= */ true,
manifest,
tag);
} else /* not live */ {
@ -455,6 +456,7 @@ public final class HlsMediaSource extends BaseMediaSource
windowDefaultStartPositionUs,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* isLive= */ false,
manifest,
tag);
}

View file

@ -696,7 +696,8 @@ public final class SsMediaSource extends BaseMediaSource
/* windowPositionInPeriodUs= */ 0,
/* windowDefaultStartPositionUs= */ 0,
/* isSeekable= */ true,
manifest.isLive,
/* isDynamic= */ manifest.isLive,
/* isLive= */ manifest.isLive,
manifest,
tag);
} else if (manifest.isLive) {
@ -719,6 +720,7 @@ public final class SsMediaSource extends BaseMediaSource
defaultStartPositionUs,
/* isSeekable= */ true,
/* isDynamic= */ true,
/* isLive= */ true,
manifest,
tag);
} else {
@ -732,6 +734,7 @@ public final class SsMediaSource extends BaseMediaSource
/* windowDefaultStartPositionUs= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ false,
/* isLive= */ false,
manifest,
tag);
}

View file

@ -189,6 +189,7 @@ public final class FakeTimeline extends Timeline {
/* windowStartTimeMs= */ C.TIME_UNSET,
windowDefinition.isSeekable,
windowDefinition.isDynamic,
/* isLive= */ windowDefinition.isDynamic,
/* defaultPositionUs= */ 0,
windowDefinition.durationUs,
periodOffsets[windowIndex],