Skip to content

Commit

Permalink
Add Timeline.Window.isLive
Browse files Browse the repository at this point in the history
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
  • Loading branch information
tonihei authored and ojw28 committed Oct 2, 2019
1 parent 26dd4aa commit dd4f9bc
Show file tree
Hide file tree
Showing 17 changed files with 138 additions and 41 deletions.
3 changes: 3 additions & 0 deletions RELEASENOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:/google/ExoPlayer/issues/2668) and
[#5973](https:/google/ExoPlayer/issues/5973)).

### 2.10.5 (2019-09-20) ###

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ public Window getWindow(int windowIndex, Window window, long defaultPositionProj
/* windowStartTimeMs= */ C.TIME_UNSET,
/* isSeekable= */ !isDynamic,
isDynamic,
/* isLive= */ isDynamic,
defaultPositionsUs[windowIndex],
durationUs,
/* firstPeriodIndex= */ windowIndex,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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}};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@
* 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>
*
Expand Down Expand Up @@ -158,8 +159,13 @@ public static final class Window {
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;

/**
Expand Down Expand Up @@ -200,6 +206,7 @@ public Window set(
long windowStartTimeMs,
boolean isSeekable,
boolean isDynamic,
boolean isLive,
long defaultPositionUs,
long durationUs,
int firstPeriodIndex,
Expand All @@ -212,6 +219,7 @@ public Window set(
this.windowStartTimeMs = windowStartTimeMs;
this.isSeekable = isSeekable;
this.isDynamic = isDynamic;
this.isLive = isLive;
this.defaultPositionUs = defaultPositionUs;
this.durationUs = durationUs;
this.firstPeriodIndex = firstPeriodIndex;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,7 @@ public Window getWindow(int windowIndex, Window window, long defaultPositionProj
/* isSeekable= */ false,
// Dynamic window to indicate pending timeline updates.
/* isDynamic= */ true,
/* isLive= */ false,
/* defaultPositionUs= */ 0,
/* durationUs= */ C.TIME_UNSET,
/* firstPeriodIndex= */ 0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,13 +74,14 @@
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);
}

/**
Expand Down Expand Up @@ -129,6 +130,7 @@ interface Listener {
private int enabledTrackCount;
private long durationUs;
private long length;
private boolean isLive;

private long lastSeekPositionUs;
private long pendingResetPositionUs;
Expand Down Expand Up @@ -551,7 +553,7 @@ public void onLoadCompleted(ExtractingLoadable loadable, long elapsedRealtimeMs,
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,
Expand Down Expand Up @@ -740,14 +742,12 @@ private void maybeFinishPrepare() {
}
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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ public int[] getSupportedTypes() {

private long timelineDurationUs;
private boolean timelineIsSeekable;
private boolean timelineIsLive;
@Nullable private TransferListener transferListener;

// TODO: Make private when ExtractorMediaSource is deleted.
Expand Down Expand Up @@ -253,7 +254,7 @@ public Object getTag() {
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
transferListener = mediaTransferListener;
drmSessionManager.prepare();
notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable);
notifySourceInfoRefreshed(timelineDurationUs, timelineIsSeekable, timelineIsLive);
}

@Override
Expand Down Expand Up @@ -293,27 +294,32 @@ protected void releaseSourceInternal() {
// 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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ public SilenceMediaSource(long durationUs) {
@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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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);
}

/**
Expand All @@ -55,13 +58,15 @@ public SinglePeriodTimeline(long durationUs, boolean isSeekable, boolean isDynam
* @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}.
*/
public SinglePeriodTimeline(
long durationUs,
boolean isSeekable,
boolean isDynamic,
boolean isLive,
@Nullable Object manifest,
@Nullable Object tag) {
this(
Expand All @@ -71,6 +76,7 @@ public SinglePeriodTimeline(
/* windowDefaultStartPositionUs= */ 0,
isSeekable,
isDynamic,
isLive,
manifest,
tag);
}
Expand All @@ -87,6 +93,7 @@ public SinglePeriodTimeline(
* 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}.
*/
Expand All @@ -97,6 +104,7 @@ public SinglePeriodTimeline(
long windowDefaultStartPositionUs,
boolean isSeekable,
boolean isDynamic,
boolean isLive,
@Nullable Object manifest,
@Nullable Object tag) {
this(
Expand All @@ -108,6 +116,7 @@ public SinglePeriodTimeline(
windowDefaultStartPositionUs,
isSeekable,
isDynamic,
isLive,
manifest,
tag);
}
Expand All @@ -127,6 +136,7 @@ public SinglePeriodTimeline(
* 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}.
*/
Expand All @@ -139,6 +149,7 @@ public SinglePeriodTimeline(
long windowDefaultStartPositionUs,
boolean isSeekable,
boolean isDynamic,
boolean isLive,
@Nullable Object manifest,
@Nullable Object tag) {
this.presentationStartTimeMs = presentationStartTimeMs;
Expand All @@ -149,6 +160,7 @@ public SinglePeriodTimeline(
this.windowDefaultStartPositionUs = windowDefaultStartPositionUs;
this.isSeekable = isSeekable;
this.isDynamic = isDynamic;
this.isLive = isLive;
this.manifest = manifest;
this.tag = tag;
}
Expand Down Expand Up @@ -182,6 +194,7 @@ public Window getWindow(int windowIndex, Window window, long defaultPositionProj
windowStartTimeMs,
isSeekable,
isDynamic,
isLive,
windowDefaultStartPositionUs,
windowDurationUs,
0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,12 @@ private SingleSampleMediaSource(
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading

0 comments on commit dd4f9bc

Please sign in to comment.