mirror of
https://github.com/samsonjs/media.git
synced 2026-03-29 10:05:48 +00:00
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:
parent
26dd4aad68
commit
dd4f9bcaae
17 changed files with 138 additions and 41 deletions
|
|
@ -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) ###
|
||||
|
||||
|
|
|
|||
|
|
@ -121,6 +121,7 @@ import java.util.Arrays;
|
|||
/* windowStartTimeMs= */ C.TIME_UNSET,
|
||||
/* isSeekable= */ !isDynamic,
|
||||
isDynamic,
|
||||
/* isLive= */ isDynamic,
|
||||
defaultPositionsUs[windowIndex],
|
||||
durationUs,
|
||||
/* firstPeriodIndex= */ windowIndex,
|
||||
|
|
|
|||
|
|
@ -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}};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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],
|
||||
|
|
|
|||
Loading…
Reference in a new issue