Commit 6b82d1c2 by tonihei Committed by Oliver Woodman

Add setters to MediaSource factories for custom window tag.

This field (formerly "id") is almost impossible to use so far. Having setters
in the factories allows to specify custom tags for all media sources.

Also added a ExoPlayer.getCurrentTag() method to retrieve the tag of the
currently playing media source in a convenient way.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=191738754
parent f25c7a85
Showing with 523 additions and 197 deletions
...@@ -27,6 +27,8 @@ ...@@ -27,6 +27,8 @@
* Added callbacks to `MediaSourceEventListener` to get notified when media * Added callbacks to `MediaSourceEventListener` to get notified when media
periods are created, released and being read from. periods are created, released and being read from.
* Support live stream clipping with `ClippingMediaSource`. * Support live stream clipping with `ClippingMediaSource`.
* Allow setting custom tags for all media sources in their factories. The tag
of the current window can be retrieved with `ExoPlayer.getCurrentTag`.
* Audio: * Audio:
* Factor out `AudioTrack` position tracking from `DefaultAudioSink`. * Factor out `AudioTrack` position tracking from `DefaultAudioSink`.
* Fix an issue where the playback position would pause just after playback * Fix an issue where the playback position would pause just after playback
......
...@@ -481,6 +481,14 @@ public final class CastPlayer implements Player { ...@@ -481,6 +481,14 @@ public final class CastPlayer implements Player {
: currentTimeline.getPreviousWindowIndex(getCurrentWindowIndex(), repeatMode, false); : currentTimeline.getPreviousWindowIndex(getCurrentWindowIndex(), repeatMode, false);
} }
@Override
public @Nullable Object getCurrentTag() {
int windowIndex = getCurrentWindowIndex();
return windowIndex > currentTimeline.getWindowCount()
? null
: currentTimeline.getWindow(windowIndex, window, /* setTag= */ true).tag;
}
// TODO: Fill the cast timeline information with ProgressListener's duration updates. // TODO: Fill the cast timeline information with ProgressListener's duration updates.
// See [Internal: b/65152553]. // See [Internal: b/65152553].
@Override @Override
......
...@@ -73,12 +73,22 @@ import java.util.Map; ...@@ -73,12 +73,22 @@ import java.util.Map;
} }
@Override @Override
public Window getWindow(int windowIndex, Window window, boolean setIds, public Window getWindow(
long defaultPositionProjectionUs) { int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
long durationUs = durationsUs[windowIndex]; long durationUs = durationsUs[windowIndex];
boolean isDynamic = durationUs == C.TIME_UNSET; boolean isDynamic = durationUs == C.TIME_UNSET;
return window.set(ids[windowIndex], C.TIME_UNSET, C.TIME_UNSET, !isDynamic, isDynamic, Object tag = setTag ? ids[windowIndex] : null;
defaultPositionsUs[windowIndex], durationUs, windowIndex, windowIndex, 0); return window.set(
tag,
/* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ C.TIME_UNSET,
/* isSeekable= */ !isDynamic,
isDynamic,
defaultPositionsUs[windowIndex],
durationUs,
/* firstPeriodIndex= */ windowIndex,
/* lastPeriodIndex= */ windowIndex,
/* positionInFirstPeriodUs= */ 0);
} }
@Override @Override
......
...@@ -312,6 +312,14 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -312,6 +312,14 @@ import java.util.concurrent.CopyOnWriteArraySet;
} }
@Override @Override
public @Nullable Object getCurrentTag() {
int windowIndex = getCurrentWindowIndex();
return windowIndex > playbackInfo.timeline.getWindowCount()
? null
: playbackInfo.timeline.getWindow(windowIndex, window, /* setTag= */ true).tag;
}
@Override
public void stop() { public void stop() {
stop(/* reset= */ false); stop(/* reset= */ false);
} }
......
...@@ -656,6 +656,12 @@ public interface Player { ...@@ -656,6 +656,12 @@ public interface Player {
int getPreviousWindowIndex(); int getPreviousWindowIndex();
/** /**
* Returns the tag of the currently playing window in the timeline. May be null if no tag is set
* or the timeline is not yet available.
*/
@Nullable Object getCurrentTag();
/**
* Returns the duration of the current window in milliseconds, or {@link C#TIME_UNSET} if the * Returns the duration of the current window in milliseconds, or {@link C#TIME_UNSET} if the
* duration is not known. * duration is not known.
*/ */
......
...@@ -665,6 +665,11 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player ...@@ -665,6 +665,11 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
} }
@Override @Override
public @Nullable Object getCurrentTag() {
return player.getCurrentTag();
}
@Override
public void stop() { public void stop() {
player.stop(); player.stop();
} }
......
...@@ -118,10 +118,8 @@ public abstract class Timeline { ...@@ -118,10 +118,8 @@ public abstract class Timeline {
*/ */
public static final class Window { public static final class Window {
/** /** A custom tag for the window. Not necessarily unique. */
* An identifier for the window. Not necessarily unique. public Object tag;
*/
public Object id;
/** /**
* The start time of the presentation to which this window belongs in milliseconds since the * The start time of the presentation to which this window belongs in milliseconds since the
...@@ -174,13 +172,19 @@ public abstract class Timeline { ...@@ -174,13 +172,19 @@ public abstract class Timeline {
*/ */
public long positionInFirstPeriodUs; public long positionInFirstPeriodUs;
/** /** Sets the data held by this window. */
* Sets the data held by this window. public Window set(
*/ Object tag,
public Window set(Object id, long presentationStartTimeMs, long windowStartTimeMs, long presentationStartTimeMs,
boolean isSeekable, boolean isDynamic, long defaultPositionUs, long durationUs, long windowStartTimeMs,
int firstPeriodIndex, int lastPeriodIndex, long positionInFirstPeriodUs) { boolean isSeekable,
this.id = id; boolean isDynamic,
long defaultPositionUs,
long durationUs,
int firstPeriodIndex,
int lastPeriodIndex,
long positionInFirstPeriodUs) {
this.tag = tag;
this.presentationStartTimeMs = presentationStartTimeMs; this.presentationStartTimeMs = presentationStartTimeMs;
this.windowStartTimeMs = windowStartTimeMs; this.windowStartTimeMs = windowStartTimeMs;
this.isSeekable = isSeekable; this.isSeekable = isSeekable;
...@@ -486,38 +490,36 @@ public abstract class Timeline { ...@@ -486,38 +490,36 @@ public abstract class Timeline {
} }
/** /** An empty timeline. */
* An empty timeline. public static final Timeline EMPTY =
*/ new Timeline() {
public static final Timeline EMPTY = new Timeline() {
@Override
@Override public int getWindowCount() {
public int getWindowCount() { return 0;
return 0; }
}
@Override
@Override public Window getWindow(
public Window getWindow(int windowIndex, Window window, boolean setIds, int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
long defaultPositionProjectionUs) { throw new IndexOutOfBoundsException();
throw new IndexOutOfBoundsException(); }
}
@Override
@Override public int getPeriodCount() {
public int getPeriodCount() { return 0;
return 0; }
}
@Override
@Override public Period getPeriod(int periodIndex, Period period, boolean setIds) {
public Period getPeriod(int periodIndex, Period period, boolean setIds) { throw new IndexOutOfBoundsException();
throw new IndexOutOfBoundsException(); }
}
@Override
@Override public int getIndexOfPeriod(Object uid) {
public int getIndexOfPeriod(Object uid) { return C.INDEX_UNSET;
return C.INDEX_UNSET; }
} };
};
/** /**
* Returns whether the timeline is empty. * Returns whether the timeline is empty.
...@@ -607,7 +609,7 @@ public abstract class Timeline { ...@@ -607,7 +609,7 @@ public abstract class Timeline {
/** /**
* Populates a {@link Window} with data for the window at the specified index. Does not populate * Populates a {@link Window} with data for the window at the specified index. Does not populate
* {@link Window#id}. * {@link Window#tag}.
* *
* @param windowIndex The index of the window. * @param windowIndex The index of the window.
* @param window The {@link Window} to populate. Must not be null. * @param window The {@link Window} to populate. Must not be null.
...@@ -622,12 +624,12 @@ public abstract class Timeline { ...@@ -622,12 +624,12 @@ public abstract class Timeline {
* *
* @param windowIndex The index of the window. * @param windowIndex The index of the window.
* @param window The {@link Window} to populate. Must not be null. * @param window The {@link Window} to populate. Must not be null.
* @param setIds Whether {@link Window#id} should be populated. If false, the field will be set to * @param setTag Whether {@link Window#tag} should be populated. If false, the field will be set
* null. The caller should pass false for efficiency reasons unless the field is required. * to null. The caller should pass false for efficiency reasons unless the field is required.
* @return The populated {@link Window}, for convenience. * @return The populated {@link Window}, for convenience.
*/ */
public final Window getWindow(int windowIndex, Window window, boolean setIds) { public final Window getWindow(int windowIndex, Window window, boolean setTag) {
return getWindow(windowIndex, window, setIds, 0); return getWindow(windowIndex, window, setTag, 0);
} }
/** /**
...@@ -635,14 +637,14 @@ public abstract class Timeline { ...@@ -635,14 +637,14 @@ public abstract class Timeline {
* *
* @param windowIndex The index of the window. * @param windowIndex The index of the window.
* @param window The {@link Window} to populate. Must not be null. * @param window The {@link Window} to populate. Must not be null.
* @param setIds Whether {@link Window#id} should be populated. If false, the field will be set to * @param setTag Whether {@link Window#tag} should be populated. If false, the field will be set
* null. The caller should pass false for efficiency reasons unless the field is required. * to null. The caller should pass false for efficiency reasons unless the field is required.
* @param defaultPositionProjectionUs A duration into the future that the populated window's * @param defaultPositionProjectionUs A duration into the future that the populated window's
* default start position should be projected. * default start position should be projected.
* @return The populated {@link Window}, for convenience. * @return The populated {@link Window}, for convenience.
*/ */
public abstract Window getWindow(int windowIndex, Window window, boolean setIds, public abstract Window getWindow(
long defaultPositionProjectionUs); int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs);
/** /**
* Returns the number of periods in the timeline. * Returns the number of periods in the timeline.
......
...@@ -155,13 +155,14 @@ import com.google.android.exoplayer2.Timeline; ...@@ -155,13 +155,14 @@ import com.google.android.exoplayer2.Timeline;
} }
@Override @Override
public final Window getWindow(int windowIndex, Window window, boolean setIds, public final Window getWindow(
long defaultPositionProjectionUs) { int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
int childIndex = getChildIndexByWindowIndex(windowIndex); int childIndex = getChildIndexByWindowIndex(windowIndex);
int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex); int firstWindowIndexInChild = getFirstWindowIndexByChildIndex(childIndex);
int firstPeriodIndexInChild = getFirstPeriodIndexByChildIndex(childIndex); int firstPeriodIndexInChild = getFirstPeriodIndexByChildIndex(childIndex);
getTimelineByChildIndex(childIndex).getWindow(windowIndex - firstWindowIndexInChild, window, getTimelineByChildIndex(childIndex)
setIds, defaultPositionProjectionUs); .getWindow(
windowIndex - firstWindowIndexInChild, window, setTag, defaultPositionProjectionUs);
window.firstPeriodIndex += firstPeriodIndexInChild; window.firstPeriodIndex += firstPeriodIndexInChild;
window.lastPeriodIndex += firstPeriodIndexInChild; window.lastPeriodIndex += firstPeriodIndexInChild;
return window; return window;
......
...@@ -363,10 +363,10 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> { ...@@ -363,10 +363,10 @@ public final class ClippingMediaSource extends CompositeMediaSource<Void> {
} }
@Override @Override
public Window getWindow(int windowIndex, Window window, boolean setIds, public Window getWindow(
long defaultPositionProjectionUs) { int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
timeline.getWindow( timeline.getWindow(
/* windowIndex= */ 0, window, setIds, /* defaultPositionProjectionUs= */ 0); /* windowIndex= */ 0, window, setTag, /* defaultPositionProjectionUs= */ 0);
window.positionInFirstPeriodUs += startUs; window.positionInFirstPeriodUs += startUs;
window.durationUs = durationUs; window.durationUs = durationUs;
window.isDynamic = isDynamic; window.isDynamic = isDynamic;
......
...@@ -865,9 +865,9 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo ...@@ -865,9 +865,9 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
@Override @Override
public Window getWindow( public Window getWindow(
int windowIndex, Window window, boolean setIds, long defaultPositionProjectionUs) { int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
return window.set( return window.set(
/* id= */ null, /* tag= */ null,
/* presentationStartTimeMs= */ C.TIME_UNSET, /* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ C.TIME_UNSET, /* windowStartTimeMs= */ C.TIME_UNSET,
/* isSeekable= */ false, /* isSeekable= */ false,
......
...@@ -96,6 +96,7 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -96,6 +96,7 @@ public final class ExtractorMediaSource extends BaseMediaSource
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final String customCacheKey; private final String customCacheKey;
private final int continueLoadingCheckIntervalBytes; private final int continueLoadingCheckIntervalBytes;
private final @Nullable Object tag;
private long timelineDurationUs; private long timelineDurationUs;
private boolean timelineIsSeekable; private boolean timelineIsSeekable;
...@@ -107,6 +108,7 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -107,6 +108,7 @@ public final class ExtractorMediaSource extends BaseMediaSource
private @Nullable ExtractorsFactory extractorsFactory; private @Nullable ExtractorsFactory extractorsFactory;
private @Nullable String customCacheKey; private @Nullable String customCacheKey;
private @Nullable Object tag;
private int minLoadableRetryCount; private int minLoadableRetryCount;
private int continueLoadingCheckIntervalBytes; private int continueLoadingCheckIntervalBytes;
private boolean isCreateCalled; private boolean isCreateCalled;
...@@ -154,6 +156,21 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -154,6 +156,21 @@ public final class ExtractorMediaSource extends BaseMediaSource
} }
/** /**
* Sets a tag for the media source which will be published in the {@link
* com.google.android.exoplayer2.Timeline} of the source as {@link
* com.google.android.exoplayer2.Timeline.Window#tag}.
*
* @param tag A tag for the media source.
* @return This factory, for convenience.
* @throws IllegalStateException If one of the {@code create} methods has already been called.
*/
public Factory setTag(Object tag) {
Assertions.checkState(!isCreateCalled);
this.tag = tag;
return this;
}
/**
* Sets the minimum number of times to retry if a loading error occurs. The default value is * Sets the minimum number of times to retry if a loading error occurs. The default value is
* {@link #MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA}. * {@link #MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA}.
* *
...@@ -202,7 +219,8 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -202,7 +219,8 @@ public final class ExtractorMediaSource extends BaseMediaSource
extractorsFactory, extractorsFactory,
minLoadableRetryCount, minLoadableRetryCount,
customCacheKey, customCacheKey,
continueLoadingCheckIntervalBytes); continueLoadingCheckIntervalBytes,
tag);
} }
/** /**
...@@ -300,7 +318,8 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -300,7 +318,8 @@ public final class ExtractorMediaSource extends BaseMediaSource
extractorsFactory, extractorsFactory,
minLoadableRetryCount, minLoadableRetryCount,
customCacheKey, customCacheKey,
continueLoadingCheckIntervalBytes); continueLoadingCheckIntervalBytes,
/* tag= */ null);
if (eventListener != null && eventHandler != null) { if (eventListener != null && eventHandler != null) {
addEventListener(eventHandler, new EventListenerWrapper(eventListener)); addEventListener(eventHandler, new EventListenerWrapper(eventListener));
} }
...@@ -312,7 +331,8 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -312,7 +331,8 @@ public final class ExtractorMediaSource extends BaseMediaSource
ExtractorsFactory extractorsFactory, ExtractorsFactory extractorsFactory,
int minLoadableRetryCount, int minLoadableRetryCount,
@Nullable String customCacheKey, @Nullable String customCacheKey,
int continueLoadingCheckIntervalBytes) { int continueLoadingCheckIntervalBytes,
@Nullable Object tag) {
this.uri = uri; this.uri = uri;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.extractorsFactory = extractorsFactory; this.extractorsFactory = extractorsFactory;
...@@ -320,6 +340,7 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -320,6 +340,7 @@ public final class ExtractorMediaSource extends BaseMediaSource
this.customCacheKey = customCacheKey; this.customCacheKey = customCacheKey;
this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes; this.continueLoadingCheckIntervalBytes = continueLoadingCheckIntervalBytes;
this.timelineDurationUs = C.TIME_UNSET; this.timelineDurationUs = C.TIME_UNSET;
this.tag = tag;
} }
@Override @Override
...@@ -377,7 +398,8 @@ public final class ExtractorMediaSource extends BaseMediaSource ...@@ -377,7 +398,8 @@ public final class ExtractorMediaSource extends BaseMediaSource
timelineIsSeekable = isSeekable; timelineIsSeekable = isSeekable;
// TODO: Make timeline dynamic until its duration is known. This is non-trivial. See b/69703223. // TODO: Make timeline dynamic until its duration is known. This is non-trivial. See b/69703223.
refreshSourceInfo( refreshSourceInfo(
new SinglePeriodTimeline(timelineDurationUs, timelineIsSeekable, /* isDynamic= */ false), new SinglePeriodTimeline(
timelineDurationUs, timelineIsSeekable, /* isDynamic= */ false, tag),
/* manifest= */ null); /* manifest= */ null);
} }
......
...@@ -57,9 +57,9 @@ public abstract class ForwardingTimeline extends Timeline { ...@@ -57,9 +57,9 @@ public abstract class ForwardingTimeline extends Timeline {
} }
@Override @Override
public Window getWindow(int windowIndex, Window window, boolean setIds, public Window getWindow(
long defaultPositionProjectionUs) { int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
return timeline.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs); return timeline.getWindow(windowIndex, window, setTag, defaultPositionProjectionUs);
} }
@Override @Override
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.source; package com.google.android.exoplayer2.source;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
...@@ -24,7 +25,7 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -24,7 +25,7 @@ import com.google.android.exoplayer2.util.Assertions;
*/ */
public final class SinglePeriodTimeline extends Timeline { public final class SinglePeriodTimeline extends Timeline {
private static final Object ID = new Object(); private static final Object UID = new Object();
private final long presentationStartTimeMs; private final long presentationStartTimeMs;
private final long windowStartTimeMs; private final long windowStartTimeMs;
...@@ -34,6 +35,7 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -34,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 @Nullable Object tag;
/** /**
* Creates a timeline containing a single period and a window that spans it. * Creates a timeline containing a single period and a window that spans it.
...@@ -43,7 +45,27 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -43,7 +45,27 @@ public final class SinglePeriodTimeline extends Timeline {
* @param isDynamic Whether the window may change when the timeline is updated. * @param isDynamic Whether the window may change when the timeline is updated.
*/ */
public SinglePeriodTimeline(long durationUs, boolean isSeekable, boolean isDynamic) { public SinglePeriodTimeline(long durationUs, boolean isSeekable, boolean isDynamic) {
this(durationUs, durationUs, 0, 0, isSeekable, isDynamic); this(durationUs, isSeekable, isDynamic, /* tag= */ null);
}
/**
* Creates a timeline containing a single period and a window that spans it.
*
* @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 tag A custom tag used for {@link Timeline.Window#tag}.
*/
public SinglePeriodTimeline(
long durationUs, boolean isSeekable, boolean isDynamic, @Nullable Object tag) {
this(
durationUs,
durationUs,
/* windowPositionInPeriodUs= */ 0,
/* windowDefaultStartPositionUs= */ 0,
isSeekable,
isDynamic,
tag);
} }
/** /**
...@@ -58,12 +80,26 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -58,12 +80,26 @@ 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 tag A custom tag used for {@link Timeline.Window#tag}.
*/ */
public SinglePeriodTimeline(long periodDurationUs, long windowDurationUs, public SinglePeriodTimeline(
long windowPositionInPeriodUs, long windowDefaultStartPositionUs, boolean isSeekable, long periodDurationUs,
boolean isDynamic) { long windowDurationUs,
this(C.TIME_UNSET, C.TIME_UNSET, periodDurationUs, windowDurationUs, windowPositionInPeriodUs, long windowPositionInPeriodUs,
windowDefaultStartPositionUs, isSeekable, isDynamic); long windowDefaultStartPositionUs,
boolean isSeekable,
boolean isDynamic,
@Nullable Object tag) {
this(
/* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ C.TIME_UNSET,
periodDurationUs,
windowDurationUs,
windowPositionInPeriodUs,
windowDefaultStartPositionUs,
isSeekable,
isDynamic,
tag);
} }
/** /**
...@@ -81,10 +117,19 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -81,10 +117,19 @@ 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 tag A custom tag used for {@link Timeline.Window#tag}. If null, an arbitrary default tag
* is used.
*/ */
public SinglePeriodTimeline(long presentationStartTimeMs, long windowStartTimeMs, public SinglePeriodTimeline(
long periodDurationUs, long windowDurationUs, long windowPositionInPeriodUs, long presentationStartTimeMs,
long windowDefaultStartPositionUs, boolean isSeekable, boolean isDynamic) { long windowStartTimeMs,
long periodDurationUs,
long windowDurationUs,
long windowPositionInPeriodUs,
long windowDefaultStartPositionUs,
boolean isSeekable,
boolean isDynamic,
@Nullable Object tag) {
this.presentationStartTimeMs = presentationStartTimeMs; this.presentationStartTimeMs = presentationStartTimeMs;
this.windowStartTimeMs = windowStartTimeMs; this.windowStartTimeMs = windowStartTimeMs;
this.periodDurationUs = periodDurationUs; this.periodDurationUs = periodDurationUs;
...@@ -93,6 +138,7 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -93,6 +138,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.tag = tag;
} }
@Override @Override
...@@ -101,10 +147,10 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -101,10 +147,10 @@ public final class SinglePeriodTimeline extends Timeline {
} }
@Override @Override
public Window getWindow(int windowIndex, Window window, boolean setIds, public Window getWindow(
long defaultPositionProjectionUs) { int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
Assertions.checkIndex(windowIndex, 0, 1); Assertions.checkIndex(windowIndex, 0, 1);
Object id = setIds ? ID : null; Object tag = setTag ? this.tag : null;
long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs; long windowDefaultStartPositionUs = this.windowDefaultStartPositionUs;
if (isDynamic && defaultPositionProjectionUs != 0) { if (isDynamic && defaultPositionProjectionUs != 0) {
if (windowDurationUs == C.TIME_UNSET) { if (windowDurationUs == C.TIME_UNSET) {
...@@ -118,8 +164,17 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -118,8 +164,17 @@ public final class SinglePeriodTimeline extends Timeline {
} }
} }
} }
return window.set(id, presentationStartTimeMs, windowStartTimeMs, isSeekable, isDynamic, return window.set(
windowDefaultStartPositionUs, windowDurationUs, 0, 0, windowPositionInPeriodUs); tag,
presentationStartTimeMs,
windowStartTimeMs,
isSeekable,
isDynamic,
windowDefaultStartPositionUs,
windowDurationUs,
0,
0,
windowPositionInPeriodUs);
} }
@Override @Override
...@@ -130,13 +185,13 @@ public final class SinglePeriodTimeline extends Timeline { ...@@ -130,13 +185,13 @@ public final class SinglePeriodTimeline extends Timeline {
@Override @Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) { public Period getPeriod(int periodIndex, Period period, boolean setIds) {
Assertions.checkIndex(periodIndex, 0, 1); Assertions.checkIndex(periodIndex, 0, 1);
Object id = setIds ? ID : null; Object uid = setIds ? UID : null;
return period.set(id, id, 0, periodDurationUs, -windowPositionInPeriodUs); return period.set(/* id= */ null, uid, 0, periodDurationUs, -windowPositionInPeriodUs);
} }
@Override @Override
public int getIndexOfPeriod(Object uid) { public int getIndexOfPeriod(Object uid) {
return ID.equals(uid) ? 0 : C.INDEX_UNSET; return UID.equals(uid) ? 0 : C.INDEX_UNSET;
} }
} }
...@@ -58,6 +58,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource { ...@@ -58,6 +58,7 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
private int minLoadableRetryCount; private int minLoadableRetryCount;
private boolean treatLoadErrorsAsEndOfStream; private boolean treatLoadErrorsAsEndOfStream;
private boolean isCreateCalled; private boolean isCreateCalled;
private @Nullable Object tag;
/** /**
* Creates a factory for {@link SingleSampleMediaSource}s. * Creates a factory for {@link SingleSampleMediaSource}s.
...@@ -71,6 +72,20 @@ public final class SingleSampleMediaSource extends BaseMediaSource { ...@@ -71,6 +72,20 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
} }
/** /**
* Sets a tag for the media source which will be published in the {@link Timeline} of the source
* as {@link Timeline.Window#tag}.
*
* @param tag A tag for the media source.
* @return This factory, for convenience.
* @throws IllegalStateException If one of the {@code create} methods has already been called.
*/
public Factory setTag(Object tag) {
Assertions.checkState(!isCreateCalled);
this.tag = tag;
return this;
}
/**
* Sets the minimum number of times to retry if a loading error occurs. The default value is * Sets the minimum number of times to retry if a loading error occurs. The default value is
* {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT}. * {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT}.
* *
...@@ -116,7 +131,8 @@ public final class SingleSampleMediaSource extends BaseMediaSource { ...@@ -116,7 +131,8 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
format, format,
durationUs, durationUs,
minLoadableRetryCount, minLoadableRetryCount,
treatLoadErrorsAsEndOfStream); treatLoadErrorsAsEndOfStream,
tag);
} }
/** /**
...@@ -188,7 +204,8 @@ public final class SingleSampleMediaSource extends BaseMediaSource { ...@@ -188,7 +204,8 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
format, format,
durationUs, durationUs,
minLoadableRetryCount, minLoadableRetryCount,
/* treatLoadErrorsAsEndOfStream= */ false); /* treatLoadErrorsAsEndOfStream= */ false,
/* tag= */ null);
} }
/** /**
...@@ -223,7 +240,8 @@ public final class SingleSampleMediaSource extends BaseMediaSource { ...@@ -223,7 +240,8 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
format, format,
durationUs, durationUs,
minLoadableRetryCount, minLoadableRetryCount,
treatLoadErrorsAsEndOfStream); treatLoadErrorsAsEndOfStream,
/* tag= */ null);
if (eventHandler != null && eventListener != null) { if (eventHandler != null && eventListener != null) {
addEventListener(eventHandler, new EventListenerWrapper(eventListener, eventSourceId)); addEventListener(eventHandler, new EventListenerWrapper(eventListener, eventSourceId));
} }
...@@ -235,14 +253,16 @@ public final class SingleSampleMediaSource extends BaseMediaSource { ...@@ -235,14 +253,16 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
Format format, Format format,
long durationUs, long durationUs,
int minLoadableRetryCount, int minLoadableRetryCount,
boolean treatLoadErrorsAsEndOfStream) { boolean treatLoadErrorsAsEndOfStream,
@Nullable Object tag) {
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.format = format; this.format = format;
this.durationUs = durationUs; this.durationUs = durationUs;
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream; this.treatLoadErrorsAsEndOfStream = treatLoadErrorsAsEndOfStream;
dataSpec = new DataSpec(uri); dataSpec = new DataSpec(uri);
timeline = new SinglePeriodTimeline(durationUs, true, false); timeline =
new SinglePeriodTimeline(durationUs, /* isSeekable= */ true, /* isDynamic= */ false, tag);
} }
// MediaSource implementation. // MediaSource implementation.
......
...@@ -55,9 +55,9 @@ import com.google.android.exoplayer2.util.Assertions; ...@@ -55,9 +55,9 @@ import com.google.android.exoplayer2.util.Assertions;
} }
@Override @Override
public Window getWindow(int windowIndex, Window window, boolean setIds, public Window getWindow(
long defaultPositionProjectionUs) { int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
window = super.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs); window = super.getWindow(windowIndex, window, setTag, defaultPositionProjectionUs);
if (window.durationUs == C.TIME_UNSET) { if (window.durationUs == C.TIME_UNSET) {
window.durationUs = adPlaybackState.contentDurationUs; window.durationUs = adPlaybackState.contentDurationUs;
} }
......
...@@ -34,7 +34,7 @@ public class TimelineTest { ...@@ -34,7 +34,7 @@ public class TimelineTest {
@Test @Test
public void testSinglePeriodTimeline() { public void testSinglePeriodTimeline() {
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(1, 111)); Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(1, 111));
TimelineAsserts.assertWindowIds(timeline, 111); TimelineAsserts.assertWindowTags(timeline, 111);
TimelineAsserts.assertPeriodCounts(timeline, 1); TimelineAsserts.assertPeriodCounts(timeline, 1);
TimelineAsserts.assertPreviousWindowIndices( TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET); timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET);
...@@ -48,7 +48,7 @@ public class TimelineTest { ...@@ -48,7 +48,7 @@ public class TimelineTest {
@Test @Test
public void testMultiPeriodTimeline() { public void testMultiPeriodTimeline() {
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(5, 111)); Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(5, 111));
TimelineAsserts.assertWindowIds(timeline, 111); TimelineAsserts.assertWindowTags(timeline, 111);
TimelineAsserts.assertPeriodCounts(timeline, 5); TimelineAsserts.assertPeriodCounts(timeline, 5);
TimelineAsserts.assertPreviousWindowIndices( TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET); timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET);
......
...@@ -183,7 +183,8 @@ public final class ClippingMediaSourceTest { ...@@ -183,7 +183,8 @@ public final class ClippingMediaSourceTest {
/* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US, /* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true); /* isDynamic= */ true,
/* tag= */ null);
Timeline clippedTimeline = getClippedTimeline(timeline, /* durationUs= */ TEST_CLIP_AMOUNT_US); Timeline clippedTimeline = getClippedTimeline(timeline, /* durationUs= */ TEST_CLIP_AMOUNT_US);
assertThat(clippedTimeline.getWindow(0, window).getDurationUs()).isEqualTo(TEST_CLIP_AMOUNT_US); assertThat(clippedTimeline.getWindow(0, window).getDurationUs()).isEqualTo(TEST_CLIP_AMOUNT_US);
...@@ -203,7 +204,8 @@ public final class ClippingMediaSourceTest { ...@@ -203,7 +204,8 @@ public final class ClippingMediaSourceTest {
/* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US, /* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true); /* isDynamic= */ true,
/* tag= */ null);
Timeline timeline2 = Timeline timeline2 =
new SinglePeriodTimeline( new SinglePeriodTimeline(
/* periodDurationUs= */ 3 * TEST_PERIOD_DURATION_US, /* periodDurationUs= */ 3 * TEST_PERIOD_DURATION_US,
...@@ -211,7 +213,8 @@ public final class ClippingMediaSourceTest { ...@@ -211,7 +213,8 @@ public final class ClippingMediaSourceTest {
/* windowPositionInPeriodUs= */ 2 * TEST_PERIOD_DURATION_US, /* windowPositionInPeriodUs= */ 2 * TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true); /* isDynamic= */ true,
/* tag= */ null);
Timeline[] clippedTimelines = Timeline[] clippedTimelines =
getClippedTimelines( getClippedTimelines(
...@@ -248,7 +251,8 @@ public final class ClippingMediaSourceTest { ...@@ -248,7 +251,8 @@ public final class ClippingMediaSourceTest {
/* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US, /* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true); /* isDynamic= */ true,
/* tag= */ null);
Timeline timeline2 = Timeline timeline2 =
new SinglePeriodTimeline( new SinglePeriodTimeline(
/* periodDurationUs= */ 4 * TEST_PERIOD_DURATION_US, /* periodDurationUs= */ 4 * TEST_PERIOD_DURATION_US,
...@@ -256,7 +260,8 @@ public final class ClippingMediaSourceTest { ...@@ -256,7 +260,8 @@ public final class ClippingMediaSourceTest {
/* windowPositionInPeriodUs= */ 3 * TEST_PERIOD_DURATION_US, /* windowPositionInPeriodUs= */ 3 * TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true); /* isDynamic= */ true,
/* tag= */ null);
Timeline[] clippedTimelines = Timeline[] clippedTimelines =
getClippedTimelines( getClippedTimelines(
...@@ -293,7 +298,8 @@ public final class ClippingMediaSourceTest { ...@@ -293,7 +298,8 @@ public final class ClippingMediaSourceTest {
/* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US, /* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true); /* isDynamic= */ true,
/* tag= */ null);
Timeline timeline2 = Timeline timeline2 =
new SinglePeriodTimeline( new SinglePeriodTimeline(
/* periodDurationUs= */ 3 * TEST_PERIOD_DURATION_US, /* periodDurationUs= */ 3 * TEST_PERIOD_DURATION_US,
...@@ -301,7 +307,8 @@ public final class ClippingMediaSourceTest { ...@@ -301,7 +307,8 @@ public final class ClippingMediaSourceTest {
/* windowPositionInPeriodUs= */ 2 * TEST_PERIOD_DURATION_US, /* windowPositionInPeriodUs= */ 2 * TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true); /* isDynamic= */ true,
/* tag= */ null);
Timeline[] clippedTimelines = Timeline[] clippedTimelines =
getClippedTimelines( getClippedTimelines(
...@@ -339,7 +346,8 @@ public final class ClippingMediaSourceTest { ...@@ -339,7 +346,8 @@ public final class ClippingMediaSourceTest {
/* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US, /* windowPositionInPeriodUs= */ TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true); /* isDynamic= */ true,
/* tag= */ null);
Timeline timeline2 = Timeline timeline2 =
new SinglePeriodTimeline( new SinglePeriodTimeline(
/* periodDurationUs= */ 4 * TEST_PERIOD_DURATION_US, /* periodDurationUs= */ 4 * TEST_PERIOD_DURATION_US,
...@@ -347,7 +355,8 @@ public final class ClippingMediaSourceTest { ...@@ -347,7 +355,8 @@ public final class ClippingMediaSourceTest {
/* windowPositionInPeriodUs= */ 3 * TEST_PERIOD_DURATION_US, /* windowPositionInPeriodUs= */ 3 * TEST_PERIOD_DURATION_US,
/* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US, /* windowDefaultStartPositionUs= */ TEST_CLIP_AMOUNT_US,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ true); /* isDynamic= */ true,
/* tag= */ null);
Timeline[] clippedTimelines = Timeline[] clippedTimelines =
getClippedTimelines( getClippedTimelines(
...@@ -382,7 +391,7 @@ public final class ClippingMediaSourceTest { ...@@ -382,7 +391,7 @@ public final class ClippingMediaSourceTest {
Timeline clippedTimeline = Timeline clippedTimeline =
getClippedTimeline( getClippedTimeline(
timeline, TEST_CLIP_AMOUNT_US, TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US); timeline, TEST_CLIP_AMOUNT_US, TEST_PERIOD_DURATION_US - TEST_CLIP_AMOUNT_US);
TimelineAsserts.assertWindowIds(clippedTimeline, 111); TimelineAsserts.assertWindowTags(clippedTimeline, 111);
TimelineAsserts.assertPeriodCounts(clippedTimeline, 1); TimelineAsserts.assertPeriodCounts(clippedTimeline, 1);
TimelineAsserts.assertPreviousWindowIndices( TimelineAsserts.assertPreviousWindowIndices(
clippedTimeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET); clippedTimeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET);
......
...@@ -75,50 +75,50 @@ public final class ConcatenatingMediaSourceTest { ...@@ -75,50 +75,50 @@ public final class ConcatenatingMediaSourceTest {
mediaSource.addMediaSource(childSources[0]); mediaSource.addMediaSource(childSources[0]);
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 1); TimelineAsserts.assertPeriodCounts(timeline, 1);
TimelineAsserts.assertWindowIds(timeline, 111); TimelineAsserts.assertWindowTags(timeline, 111);
// Add at front of queue. // Add at front of queue.
mediaSource.addMediaSource(0, childSources[1]); mediaSource.addMediaSource(0, childSources[1]);
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 2, 1); TimelineAsserts.assertPeriodCounts(timeline, 2, 1);
TimelineAsserts.assertWindowIds(timeline, 222, 111); TimelineAsserts.assertWindowTags(timeline, 222, 111);
// Add at back of queue. // Add at back of queue.
mediaSource.addMediaSource(childSources[2]); mediaSource.addMediaSource(childSources[2]);
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 2, 1, 3); TimelineAsserts.assertPeriodCounts(timeline, 2, 1, 3);
TimelineAsserts.assertWindowIds(timeline, 222, 111, 333); TimelineAsserts.assertWindowTags(timeline, 222, 111, 333);
// Add in the middle. // Add in the middle.
mediaSource.addMediaSource(1, childSources[3]); mediaSource.addMediaSource(1, childSources[3]);
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 3); TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 3);
TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 333); TimelineAsserts.assertWindowTags(timeline, 222, 444, 111, 333);
// Add bulk. // Add bulk.
mediaSource.addMediaSources( mediaSource.addMediaSources(
3, Arrays.<MediaSource>asList(childSources[4], childSources[5], childSources[6])); 3, Arrays.<MediaSource>asList(childSources[4], childSources[5], childSources[6]));
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3); TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3);
TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 555, 666, 777, 333); TimelineAsserts.assertWindowTags(timeline, 222, 444, 111, 555, 666, 777, 333);
// Move sources. // Move sources.
mediaSource.moveMediaSource(2, 3); mediaSource.moveMediaSource(2, 3);
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 5, 1, 6, 7, 3); TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 5, 1, 6, 7, 3);
TimelineAsserts.assertWindowIds(timeline, 222, 444, 555, 111, 666, 777, 333); TimelineAsserts.assertWindowTags(timeline, 222, 444, 555, 111, 666, 777, 333);
mediaSource.moveMediaSource(3, 2); mediaSource.moveMediaSource(3, 2);
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3); TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3);
TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 555, 666, 777, 333); TimelineAsserts.assertWindowTags(timeline, 222, 444, 111, 555, 666, 777, 333);
mediaSource.moveMediaSource(0, 6); mediaSource.moveMediaSource(0, 6);
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 4, 1, 5, 6, 7, 3, 2); TimelineAsserts.assertPeriodCounts(timeline, 4, 1, 5, 6, 7, 3, 2);
TimelineAsserts.assertWindowIds(timeline, 444, 111, 555, 666, 777, 333, 222); TimelineAsserts.assertWindowTags(timeline, 444, 111, 555, 666, 777, 333, 222);
mediaSource.moveMediaSource(6, 0); mediaSource.moveMediaSource(6, 0);
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3); TimelineAsserts.assertPeriodCounts(timeline, 2, 4, 1, 5, 6, 7, 3);
TimelineAsserts.assertWindowIds(timeline, 222, 444, 111, 555, 666, 777, 333); TimelineAsserts.assertWindowTags(timeline, 222, 444, 111, 555, 666, 777, 333);
// Remove in the middle. // Remove in the middle.
mediaSource.removeMediaSource(3); mediaSource.removeMediaSource(3);
...@@ -130,7 +130,7 @@ public final class ConcatenatingMediaSourceTest { ...@@ -130,7 +130,7 @@ public final class ConcatenatingMediaSourceTest {
mediaSource.removeMediaSource(1); mediaSource.removeMediaSource(1);
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 2, 1, 3); TimelineAsserts.assertPeriodCounts(timeline, 2, 1, 3);
TimelineAsserts.assertWindowIds(timeline, 222, 111, 333); TimelineAsserts.assertWindowTags(timeline, 222, 111, 333);
for (int i = 3; i <= 6; i++) { for (int i = 3; i <= 6; i++) {
childSources[i].assertReleased(); childSources[i].assertReleased();
} }
...@@ -169,14 +169,14 @@ public final class ConcatenatingMediaSourceTest { ...@@ -169,14 +169,14 @@ public final class ConcatenatingMediaSourceTest {
mediaSource.removeMediaSource(0); mediaSource.removeMediaSource(0);
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 1, 3); TimelineAsserts.assertPeriodCounts(timeline, 1, 3);
TimelineAsserts.assertWindowIds(timeline, 111, 333); TimelineAsserts.assertWindowTags(timeline, 111, 333);
childSources[1].assertReleased(); childSources[1].assertReleased();
// Remove at back of queue. // Remove at back of queue.
mediaSource.removeMediaSource(1); mediaSource.removeMediaSource(1);
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 1); TimelineAsserts.assertPeriodCounts(timeline, 1);
TimelineAsserts.assertWindowIds(timeline, 111); TimelineAsserts.assertWindowTags(timeline, 111);
childSources[2].assertReleased(); childSources[2].assertReleased();
// Remove last source. // Remove last source.
...@@ -200,7 +200,7 @@ public final class ConcatenatingMediaSourceTest { ...@@ -200,7 +200,7 @@ public final class ConcatenatingMediaSourceTest {
Timeline timeline = testRunner.prepareSource(); Timeline timeline = testRunner.prepareSource();
TimelineAsserts.assertPeriodCounts(timeline, 3, 4, 2); TimelineAsserts.assertPeriodCounts(timeline, 3, 4, 2);
TimelineAsserts.assertWindowIds(timeline, 333, 444, 222); TimelineAsserts.assertWindowTags(timeline, 333, 444, 222);
TimelineAsserts.assertNextWindowIndices( TimelineAsserts.assertNextWindowIndices(
timeline, Player.REPEAT_MODE_OFF, false, 1, 2, C.INDEX_UNSET); timeline, Player.REPEAT_MODE_OFF, false, 1, 2, C.INDEX_UNSET);
TimelineAsserts.assertPreviousWindowIndices( TimelineAsserts.assertPreviousWindowIndices(
...@@ -241,7 +241,7 @@ public final class ConcatenatingMediaSourceTest { ...@@ -241,7 +241,7 @@ public final class ConcatenatingMediaSourceTest {
// placeholder information for lazy sources. // placeholder information for lazy sources.
Timeline timeline = testRunner.prepareSource(); Timeline timeline = testRunner.prepareSource();
TimelineAsserts.assertPeriodCounts(timeline, 1, 1); TimelineAsserts.assertPeriodCounts(timeline, 1, 1);
TimelineAsserts.assertWindowIds(timeline, 111, null); TimelineAsserts.assertWindowTags(timeline, 111, null);
TimelineAsserts.assertWindowIsDynamic(timeline, false, true); TimelineAsserts.assertWindowIsDynamic(timeline, false, true);
// Trigger source info refresh for lazy source and check that the timeline now contains all // Trigger source info refresh for lazy source and check that the timeline now contains all
...@@ -255,7 +255,7 @@ public final class ConcatenatingMediaSourceTest { ...@@ -255,7 +255,7 @@ public final class ConcatenatingMediaSourceTest {
}); });
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 1, 9); TimelineAsserts.assertPeriodCounts(timeline, 1, 9);
TimelineAsserts.assertWindowIds(timeline, 111, 999); TimelineAsserts.assertWindowTags(timeline, 111, 999);
TimelineAsserts.assertWindowIsDynamic(timeline, false, false); TimelineAsserts.assertWindowIsDynamic(timeline, false, false);
testRunner.assertPrepareAndReleaseAllPeriods(); testRunner.assertPrepareAndReleaseAllPeriods();
testRunner.assertCompletedManifestLoads(0, 1); testRunner.assertCompletedManifestLoads(0, 1);
...@@ -272,7 +272,7 @@ public final class ConcatenatingMediaSourceTest { ...@@ -272,7 +272,7 @@ public final class ConcatenatingMediaSourceTest {
mediaSource.removeMediaSource(2); mediaSource.removeMediaSource(2);
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 2, 9); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 2, 9);
TimelineAsserts.assertWindowIds(timeline, null, 111, 222, 999); TimelineAsserts.assertWindowTags(timeline, null, 111, 222, 999);
TimelineAsserts.assertWindowIsDynamic(timeline, true, false, false, false); TimelineAsserts.assertWindowIsDynamic(timeline, true, false, false, false);
// Create a period from an unprepared lazy media source and assert Callback.onPrepared is not // Create a period from an unprepared lazy media source and assert Callback.onPrepared is not
...@@ -300,7 +300,7 @@ public final class ConcatenatingMediaSourceTest { ...@@ -300,7 +300,7 @@ public final class ConcatenatingMediaSourceTest {
}); });
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertPeriodCounts(timeline, 8, 1, 2, 9); TimelineAsserts.assertPeriodCounts(timeline, 8, 1, 2, 9);
TimelineAsserts.assertWindowIds(timeline, 888, 111, 222, 999); TimelineAsserts.assertWindowTags(timeline, 888, 111, 222, 999);
TimelineAsserts.assertWindowIsDynamic(timeline, false, false, false, false); TimelineAsserts.assertWindowIsDynamic(timeline, false, false, false, false);
assertThat(preparedCondition.getCount()).isEqualTo(0); assertThat(preparedCondition.getCount()).isEqualTo(0);
...@@ -346,7 +346,7 @@ public final class ConcatenatingMediaSourceTest { ...@@ -346,7 +346,7 @@ public final class ConcatenatingMediaSourceTest {
testRunner.assertTimelineChangeBlocking(); testRunner.assertTimelineChangeBlocking();
mediaSource.addMediaSource(6, mediaSources[2]); mediaSource.addMediaSource(6, mediaSources[2]);
timeline = testRunner.assertTimelineChangeBlocking(); timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertWindowTags(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3); TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3);
TimelineAsserts.assertPreviousWindowIndices( TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET, 0, 1); timeline, Player.REPEAT_MODE_OFF, false, C.INDEX_UNSET, 0, 1);
...@@ -685,7 +685,7 @@ public final class ConcatenatingMediaSourceTest { ...@@ -685,7 +685,7 @@ public final class ConcatenatingMediaSourceTest {
testRunner = new MediaSourceTestRunner(mediaSource, null); testRunner = new MediaSourceTestRunner(mediaSource, null);
mediaSource.addMediaSources(Arrays.<MediaSource>asList(createMediaSources(3))); mediaSource.addMediaSources(Arrays.<MediaSource>asList(createMediaSources(3)));
Timeline timeline = testRunner.prepareSource(); Timeline timeline = testRunner.prepareSource();
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertWindowTags(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3); TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3);
TimelineAsserts.assertPreviousWindowIndices( TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false, C.INDEX_UNSET, 0, 1); timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false, C.INDEX_UNSET, 0, 1);
...@@ -736,7 +736,7 @@ public final class ConcatenatingMediaSourceTest { ...@@ -736,7 +736,7 @@ public final class ConcatenatingMediaSourceTest {
nestedSource2.addMediaSource(childSources[3]); nestedSource2.addMediaSource(childSources[3]);
Timeline timeline = testRunner.assertTimelineChangeBlocking(); Timeline timeline = testRunner.assertTimelineChangeBlocking();
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333, 444); TimelineAsserts.assertWindowTags(timeline, 111, 222, 333, 444);
TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3, 4); TimelineAsserts.assertPeriodCounts(timeline, 1, 2, 3, 4);
TimelineAsserts.assertPreviousWindowIndices( TimelineAsserts.assertPreviousWindowIndices(
timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false, C.INDEX_UNSET, 0, 1, 2); timeline, Player.REPEAT_MODE_OFF, /* shuffleModeEnabled= */ false, C.INDEX_UNSET, 0, 1, 2);
......
...@@ -50,7 +50,7 @@ public class LoopingMediaSourceTest { ...@@ -50,7 +50,7 @@ public class LoopingMediaSourceTest {
@Test @Test
public void testSingleLoop() throws IOException { public void testSingleLoop() throws IOException {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, 1); Timeline timeline = getLoopingTimeline(multiWindowTimeline, 1);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertWindowTags(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1);
for (boolean shuffled : new boolean[] {false, true}) { for (boolean shuffled : new boolean[] {false, true}) {
TimelineAsserts.assertPreviousWindowIndices( TimelineAsserts.assertPreviousWindowIndices(
...@@ -69,7 +69,7 @@ public class LoopingMediaSourceTest { ...@@ -69,7 +69,7 @@ public class LoopingMediaSourceTest {
@Test @Test
public void testMultiLoop() throws IOException { public void testMultiLoop() throws IOException {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, 3); Timeline timeline = getLoopingTimeline(multiWindowTimeline, 3);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333, 111, 222, 333, 111, 222, 333); TimelineAsserts.assertWindowTags(timeline, 111, 222, 333, 111, 222, 333, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1, 1, 1, 1, 1, 1); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1, 1, 1, 1, 1, 1, 1);
for (boolean shuffled : new boolean[] {false, true}) { for (boolean shuffled : new boolean[] {false, true}) {
TimelineAsserts.assertPreviousWindowIndices( TimelineAsserts.assertPreviousWindowIndices(
...@@ -90,7 +90,7 @@ public class LoopingMediaSourceTest { ...@@ -90,7 +90,7 @@ public class LoopingMediaSourceTest {
@Test @Test
public void testInfiniteLoop() throws IOException { public void testInfiniteLoop() throws IOException {
Timeline timeline = getLoopingTimeline(multiWindowTimeline, Integer.MAX_VALUE); Timeline timeline = getLoopingTimeline(multiWindowTimeline, Integer.MAX_VALUE);
TimelineAsserts.assertWindowIds(timeline, 111, 222, 333); TimelineAsserts.assertWindowTags(timeline, 111, 222, 333);
TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1); TimelineAsserts.assertPeriodCounts(timeline, 1, 1, 1);
for (boolean shuffled : new boolean[] {false, true}) { for (boolean shuffled : new boolean[] {false, true}) {
TimelineAsserts.assertPreviousWindowIndices( TimelineAsserts.assertPreviousWindowIndices(
......
...@@ -56,8 +56,15 @@ public final class SinglePeriodTimelineTest { ...@@ -56,8 +56,15 @@ public final class SinglePeriodTimelineTest {
@Test @Test
public void testGetPeriodPositionDynamicWindowKnownDuration() { public void testGetPeriodPositionDynamicWindowKnownDuration() {
long windowDurationUs = 1000; long windowDurationUs = 1000;
SinglePeriodTimeline timeline = new SinglePeriodTimeline(windowDurationUs, windowDurationUs, 0, SinglePeriodTimeline timeline =
0, false, true); new SinglePeriodTimeline(
windowDurationUs,
windowDurationUs,
/* windowPositionInPeriodUs= */ 0,
/* windowDefaultStartPositionUs= */ 0,
/* isSeekable= */ false,
/* isDynamic= */ true,
/* tag= */ null);
// Should return null with a positive position projection beyond window duration. // Should return null with a positive position projection beyond window duration.
Pair<Integer, Long> position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET, Pair<Integer, Long> position = timeline.getPeriodPosition(window, period, 0, C.TIME_UNSET,
windowDurationUs + 1); windowDurationUs + 1);
...@@ -72,4 +79,48 @@ public final class SinglePeriodTimelineTest { ...@@ -72,4 +79,48 @@ public final class SinglePeriodTimelineTest {
assertThat(position.second).isEqualTo(0); assertThat(position.second).isEqualTo(0);
} }
@Test
public void setNullTag_returnsNullTag_butUsesDefaultUid() {
SinglePeriodTimeline timeline =
new SinglePeriodTimeline(
/* durationUs= */ C.TIME_UNSET,
/* isSeekable= */ false,
/* isDynamic= */ false,
/* tag= */ null);
assertThat(timeline.getWindow(/* windowIndex= */ 0, window, /* setTag= */ false).tag).isNull();
assertThat(timeline.getWindow(/* windowIndex= */ 0, window, /* setTag= */ true).tag).isNull();
assertThat(timeline.getPeriod(/* periodIndex= */ 0, period, /* setIds= */ false).id).isNull();
assertThat(timeline.getPeriod(/* periodIndex= */ 0, period, /* setIds= */ true).id).isNull();
assertThat(timeline.getPeriod(/* periodIndex= */ 0, period, /* setIds= */ false).uid).isNull();
assertThat(timeline.getPeriod(/* periodIndex= */ 0, period, /* setIds= */ true).uid)
.isNotNull();
}
@Test
public void setTag_isUsedForWindowTag() {
Object tag = new Object();
SinglePeriodTimeline timeline =
new SinglePeriodTimeline(
/* durationUs= */ C.TIME_UNSET, /* isSeekable= */ false, /* isDynamic= */ false, tag);
assertThat(timeline.getWindow(/* windowIndex= */ 0, window, /* setTag= */ false).tag).isNull();
assertThat(timeline.getWindow(/* windowIndex= */ 0, window, /* setTag= */ true).tag)
.isEqualTo(tag);
}
@Test
public void getIndexOfPeriod_returnsPeriod() {
SinglePeriodTimeline timeline =
new SinglePeriodTimeline(
/* durationUs= */ C.TIME_UNSET,
/* isSeekable= */ false,
/* isDynamic= */ false,
/* tag= */ null);
Object uid = timeline.getPeriod(/* periodIndex= */ 0, period, /* setIds= */ true).uid;
assertThat(timeline.getIndexOfPeriod(uid)).isEqualTo(0);
assertThat(timeline.getIndexOfPeriod(/* uid= */ null)).isEqualTo(C.INDEX_UNSET);
assertThat(timeline.getIndexOfPeriod(/* uid= */ new Object())).isEqualTo(C.INDEX_UNSET);
}
} }
...@@ -77,6 +77,7 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -77,6 +77,7 @@ public final class DashMediaSource extends BaseMediaSource {
private int minLoadableRetryCount; private int minLoadableRetryCount;
private long livePresentationDelayMs; private long livePresentationDelayMs;
private boolean isCreateCalled; private boolean isCreateCalled;
private @Nullable Object tag;
/** /**
* Creates a new factory for {@link DashMediaSource}s. * Creates a new factory for {@link DashMediaSource}s.
...@@ -98,6 +99,21 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -98,6 +99,21 @@ public final class DashMediaSource extends BaseMediaSource {
} }
/** /**
* Sets a tag for the media source which will be published in the {@link
* com.google.android.exoplayer2.Timeline} of the source as {@link
* com.google.android.exoplayer2.Timeline.Window#tag}.
*
* @param tag A tag for the media source.
* @return This factory, for convenience.
* @throws IllegalStateException If one of the {@code create} methods has already been called.
*/
public Factory setTag(Object tag) {
Assertions.checkState(!isCreateCalled);
this.tag = tag;
return this;
}
/**
* Sets the minimum number of times to retry if a loading error occurs. The default value is * Sets the minimum number of times to retry if a loading error occurs. The default value is
* {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT}. * {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT}.
* *
...@@ -175,13 +191,14 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -175,13 +191,14 @@ public final class DashMediaSource extends BaseMediaSource {
isCreateCalled = true; isCreateCalled = true;
return new DashMediaSource( return new DashMediaSource(
manifest, manifest,
null, /* manifestUri= */ null,
null, /* manifestDataSourceFactory= */ null,
null, /* manifestParser= */ null,
chunkSourceFactory, chunkSourceFactory,
compositeSequenceableLoaderFactory, compositeSequenceableLoaderFactory,
minLoadableRetryCount, minLoadableRetryCount,
livePresentationDelayMs); livePresentationDelayMs,
tag);
} }
/** /**
...@@ -213,14 +230,15 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -213,14 +230,15 @@ public final class DashMediaSource extends BaseMediaSource {
manifestParser = new DashManifestParser(); manifestParser = new DashManifestParser();
} }
return new DashMediaSource( return new DashMediaSource(
null, /* manifest= */ null,
Assertions.checkNotNull(manifestUri), Assertions.checkNotNull(manifestUri),
manifestDataSourceFactory, manifestDataSourceFactory,
manifestParser, manifestParser,
chunkSourceFactory, chunkSourceFactory,
compositeSequenceableLoaderFactory, compositeSequenceableLoaderFactory,
minLoadableRetryCount, minLoadableRetryCount,
livePresentationDelayMs); livePresentationDelayMs,
tag);
} }
/** /**
...@@ -290,6 +308,7 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -290,6 +308,7 @@ public final class DashMediaSource extends BaseMediaSource {
private final Runnable simulateManifestRefreshRunnable; private final Runnable simulateManifestRefreshRunnable;
private final PlayerEmsgCallback playerEmsgCallback; private final PlayerEmsgCallback playerEmsgCallback;
private final LoaderErrorThrower manifestLoadErrorThrower; private final LoaderErrorThrower manifestLoadErrorThrower;
private final @Nullable Object tag;
private DataSource dataSource; private DataSource dataSource;
private Loader loader; private Loader loader;
...@@ -349,13 +368,14 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -349,13 +368,14 @@ public final class DashMediaSource extends BaseMediaSource {
MediaSourceEventListener eventListener) { MediaSourceEventListener eventListener) {
this( this(
manifest, manifest,
null, /* manifestUri= */ null,
null, /* manifestDataSourceFactory= */ null,
null, /* manifestParser= */ null,
chunkSourceFactory, chunkSourceFactory,
new DefaultCompositeSequenceableLoaderFactory(), new DefaultCompositeSequenceableLoaderFactory(),
minLoadableRetryCount, minLoadableRetryCount,
DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS); DEFAULT_LIVE_PRESENTATION_DELAY_PREFER_MANIFEST_MS,
/* tag= */ null);
if (eventHandler != null && eventListener != null) { if (eventHandler != null && eventListener != null) {
addEventListener(eventHandler, eventListener); addEventListener(eventHandler, eventListener);
} }
...@@ -444,14 +464,15 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -444,14 +464,15 @@ public final class DashMediaSource extends BaseMediaSource {
Handler eventHandler, Handler eventHandler,
MediaSourceEventListener eventListener) { MediaSourceEventListener eventListener) {
this( this(
null, /* manifest= */ null,
manifestUri, manifestUri,
manifestDataSourceFactory, manifestDataSourceFactory,
manifestParser, manifestParser,
chunkSourceFactory, chunkSourceFactory,
new DefaultCompositeSequenceableLoaderFactory(), new DefaultCompositeSequenceableLoaderFactory(),
minLoadableRetryCount, minLoadableRetryCount,
livePresentationDelayMs); livePresentationDelayMs,
/* tag= */ null);
if (eventHandler != null && eventListener != null) { if (eventHandler != null && eventListener != null) {
addEventListener(eventHandler, eventListener); addEventListener(eventHandler, eventListener);
} }
...@@ -465,7 +486,8 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -465,7 +486,8 @@ public final class DashMediaSource extends BaseMediaSource {
DashChunkSource.Factory chunkSourceFactory, DashChunkSource.Factory chunkSourceFactory,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
int minLoadableRetryCount, int minLoadableRetryCount,
long livePresentationDelayMs) { long livePresentationDelayMs,
@Nullable Object tag) {
this.initialManifestUri = manifestUri; this.initialManifestUri = manifestUri;
this.manifest = manifest; this.manifest = manifest;
this.manifestUri = manifestUri; this.manifestUri = manifestUri;
...@@ -475,6 +497,7 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -475,6 +497,7 @@ public final class DashMediaSource extends BaseMediaSource {
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.livePresentationDelayMs = livePresentationDelayMs; this.livePresentationDelayMs = livePresentationDelayMs;
this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory; this.compositeSequenceableLoaderFactory = compositeSequenceableLoaderFactory;
this.tag = tag;
sideloadedManifest = manifest != null; sideloadedManifest = manifest != null;
manifestEventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null); manifestEventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null);
manifestUriLock = new Object(); manifestUriLock = new Object();
...@@ -862,9 +885,16 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -862,9 +885,16 @@ public final class DashMediaSource extends BaseMediaSource {
} }
long windowStartTimeMs = manifest.availabilityStartTimeMs long windowStartTimeMs = manifest.availabilityStartTimeMs
+ manifest.getPeriod(0).startMs + C.usToMs(currentStartTimeUs); + manifest.getPeriod(0).startMs + C.usToMs(currentStartTimeUs);
DashTimeline timeline = new DashTimeline(manifest.availabilityStartTimeMs, windowStartTimeMs, DashTimeline timeline =
firstPeriodId, currentStartTimeUs, windowDurationUs, windowDefaultStartPositionUs, new DashTimeline(
manifest); manifest.availabilityStartTimeMs,
windowStartTimeMs,
firstPeriodId,
currentStartTimeUs,
windowDurationUs,
windowDefaultStartPositionUs,
manifest,
tag);
refreshSourceInfo(timeline, manifest); refreshSourceInfo(timeline, manifest);
if (!sideloadedManifest) { if (!sideloadedManifest) {
...@@ -993,10 +1023,17 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -993,10 +1023,17 @@ public final class DashMediaSource extends BaseMediaSource {
private final long windowDurationUs; private final long windowDurationUs;
private final long windowDefaultStartPositionUs; private final long windowDefaultStartPositionUs;
private final DashManifest manifest; private final DashManifest manifest;
private final @Nullable Object windowTag;
public DashTimeline(long presentationStartTimeMs, long windowStartTimeMs, int firstPeriodId,
long offsetInFirstPeriodUs, long windowDurationUs, long windowDefaultStartPositionUs, public DashTimeline(
DashManifest manifest) { long presentationStartTimeMs,
long windowStartTimeMs,
int firstPeriodId,
long offsetInFirstPeriodUs,
long windowDurationUs,
long windowDefaultStartPositionUs,
DashManifest manifest,
@Nullable Object windowTag) {
this.presentationStartTimeMs = presentationStartTimeMs; this.presentationStartTimeMs = presentationStartTimeMs;
this.windowStartTimeMs = windowStartTimeMs; this.windowStartTimeMs = windowStartTimeMs;
this.firstPeriodId = firstPeriodId; this.firstPeriodId = firstPeriodId;
...@@ -1004,6 +1041,7 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1004,6 +1041,7 @@ public final class DashMediaSource extends BaseMediaSource {
this.windowDurationUs = windowDurationUs; this.windowDurationUs = windowDurationUs;
this.windowDefaultStartPositionUs = windowDefaultStartPositionUs; this.windowDefaultStartPositionUs = windowDefaultStartPositionUs;
this.manifest = manifest; this.manifest = manifest;
this.windowTag = windowTag;
} }
@Override @Override
...@@ -1028,14 +1066,23 @@ public final class DashMediaSource extends BaseMediaSource { ...@@ -1028,14 +1066,23 @@ public final class DashMediaSource extends BaseMediaSource {
} }
@Override @Override
public Window getWindow(int windowIndex, Window window, boolean setIdentifier, public Window getWindow(
long defaultPositionProjectionUs) { int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
Assertions.checkIndex(windowIndex, 0, 1); Assertions.checkIndex(windowIndex, 0, 1);
long windowDefaultStartPositionUs = getAdjustedWindowDefaultStartPositionUs( long windowDefaultStartPositionUs = getAdjustedWindowDefaultStartPositionUs(
defaultPositionProjectionUs); defaultPositionProjectionUs);
return window.set(null, presentationStartTimeMs, windowStartTimeMs, true /* isSeekable */, Object tag = setTag ? windowTag : null;
manifest.dynamic, windowDefaultStartPositionUs, windowDurationUs, 0, return window.set(
manifest.getPeriodCount() - 1, offsetInFirstPeriodUs); tag,
presentationStartTimeMs,
windowStartTimeMs,
/* isSeekable= */ true,
manifest.dynamic,
windowDefaultStartPositionUs,
windowDurationUs,
/* firstPeriodIndex= */ 0,
manifest.getPeriodCount() - 1,
offsetInFirstPeriodUs);
} }
@Override @Override
......
...@@ -62,6 +62,7 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -62,6 +62,7 @@ public final class HlsMediaSource extends BaseMediaSource
private int minLoadableRetryCount; private int minLoadableRetryCount;
private boolean allowChunklessPreparation; private boolean allowChunklessPreparation;
private boolean isCreateCalled; private boolean isCreateCalled;
private @Nullable Object tag;
/** /**
* Creates a new factory for {@link HlsMediaSource}s. * Creates a new factory for {@link HlsMediaSource}s.
...@@ -88,6 +89,21 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -88,6 +89,21 @@ public final class HlsMediaSource extends BaseMediaSource
} }
/** /**
* Sets a tag for the media source which will be published in the {@link
* com.google.android.exoplayer2.Timeline} of the source as {@link
* com.google.android.exoplayer2.Timeline.Window#tag}.
*
* @param tag A tag for the media source.
* @return This factory, for convenience.
* @throws IllegalStateException If one of the {@code create} methods has already been called.
*/
public Factory setTag(Object tag) {
Assertions.checkState(!isCreateCalled);
this.tag = tag;
return this;
}
/**
* Sets the factory for {@link Extractor}s for the segments. The default value is {@link * Sets the factory for {@link Extractor}s for the segments. The default value is {@link
* HlsExtractorFactory#DEFAULT}. * HlsExtractorFactory#DEFAULT}.
* *
...@@ -181,7 +197,8 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -181,7 +197,8 @@ public final class HlsMediaSource extends BaseMediaSource
compositeSequenceableLoaderFactory, compositeSequenceableLoaderFactory,
minLoadableRetryCount, minLoadableRetryCount,
playlistParser, playlistParser,
allowChunklessPreparation); allowChunklessPreparation,
tag);
} }
/** /**
...@@ -218,6 +235,7 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -218,6 +235,7 @@ public final class HlsMediaSource extends BaseMediaSource
private final int minLoadableRetryCount; private final int minLoadableRetryCount;
private final ParsingLoadable.Parser<HlsPlaylist> playlistParser; private final ParsingLoadable.Parser<HlsPlaylist> playlistParser;
private final boolean allowChunklessPreparation; private final boolean allowChunklessPreparation;
private final @Nullable Object tag;
private HlsPlaylistTracker playlistTracker; private HlsPlaylistTracker playlistTracker;
...@@ -292,7 +310,8 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -292,7 +310,8 @@ public final class HlsMediaSource extends BaseMediaSource
new DefaultCompositeSequenceableLoaderFactory(), new DefaultCompositeSequenceableLoaderFactory(),
minLoadableRetryCount, minLoadableRetryCount,
playlistParser, playlistParser,
false); /* allowChunklessPreparation= */ false,
/* tag= */ null);
if (eventHandler != null && eventListener != null) { if (eventHandler != null && eventListener != null) {
addEventListener(eventHandler, eventListener); addEventListener(eventHandler, eventListener);
} }
...@@ -305,7 +324,8 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -305,7 +324,8 @@ public final class HlsMediaSource extends BaseMediaSource
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
int minLoadableRetryCount, int minLoadableRetryCount,
ParsingLoadable.Parser<HlsPlaylist> playlistParser, ParsingLoadable.Parser<HlsPlaylist> playlistParser,
boolean allowChunklessPreparation) { boolean allowChunklessPreparation,
@Nullable Object tag) {
this.manifestUri = manifestUri; this.manifestUri = manifestUri;
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.extractorFactory = extractorFactory; this.extractorFactory = extractorFactory;
...@@ -313,6 +333,7 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -313,6 +333,7 @@ public final class HlsMediaSource extends BaseMediaSource
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.playlistParser = playlistParser; this.playlistParser = playlistParser;
this.allowChunklessPreparation = allowChunklessPreparation; this.allowChunklessPreparation = allowChunklessPreparation;
this.tag = tag;
} }
@Override @Override
...@@ -388,7 +409,8 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -388,7 +409,8 @@ public final class HlsMediaSource extends BaseMediaSource
/* windowPositionInPeriodUs= */ offsetFromInitialStartTimeUs, /* windowPositionInPeriodUs= */ offsetFromInitialStartTimeUs,
windowDefaultStartPositionUs, windowDefaultStartPositionUs,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ !playlist.hasEndTag); /* isDynamic= */ !playlist.hasEndTag,
tag);
} else /* not live */ { } else /* not live */ {
if (windowDefaultStartPositionUs == C.TIME_UNSET) { if (windowDefaultStartPositionUs == C.TIME_UNSET) {
windowDefaultStartPositionUs = 0; windowDefaultStartPositionUs = 0;
...@@ -402,7 +424,8 @@ public final class HlsMediaSource extends BaseMediaSource ...@@ -402,7 +424,8 @@ public final class HlsMediaSource extends BaseMediaSource
/* windowPositionInPeriodUs= */ 0, /* windowPositionInPeriodUs= */ 0,
windowDefaultStartPositionUs, windowDefaultStartPositionUs,
/* isSeekable= */ true, /* isSeekable= */ true,
/* isDynamic= */ false); /* isDynamic= */ false,
tag);
} }
refreshSourceInfo(timeline, new HlsManifest(playlistTracker.getMasterPlaylist(), playlist)); refreshSourceInfo(timeline, new HlsManifest(playlistTracker.getMasterPlaylist(), playlist));
} }
......
...@@ -66,6 +66,7 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -66,6 +66,7 @@ public final class SsMediaSource extends BaseMediaSource
private int minLoadableRetryCount; private int minLoadableRetryCount;
private long livePresentationDelayMs; private long livePresentationDelayMs;
private boolean isCreateCalled; private boolean isCreateCalled;
private @Nullable Object tag;
/** /**
* Creates a new factory for {@link SsMediaSource}s. * Creates a new factory for {@link SsMediaSource}s.
...@@ -87,6 +88,20 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -87,6 +88,20 @@ public final class SsMediaSource extends BaseMediaSource
} }
/** /**
* Sets a tag for the media source which will be published in the {@link Timeline} of the source
* as {@link Timeline.Window#tag}.
*
* @param tag A tag for the media source.
* @return This factory, for convenience.
* @throws IllegalStateException If one of the {@code create} methods has already been called.
*/
public Factory setTag(Object tag) {
Assertions.checkState(!isCreateCalled);
this.tag = tag;
return this;
}
/**
* Sets the minimum number of times to retry if a loading error occurs. The default value is * Sets the minimum number of times to retry if a loading error occurs. The default value is
* {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT}. * {@link #DEFAULT_MIN_LOADABLE_RETRY_COUNT}.
* *
...@@ -161,13 +176,14 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -161,13 +176,14 @@ public final class SsMediaSource extends BaseMediaSource
isCreateCalled = true; isCreateCalled = true;
return new SsMediaSource( return new SsMediaSource(
manifest, manifest,
null, /* manifestUri= */ null,
null, /* manifestDataSourceFactory= */ null,
null, /* manifestParser= */ null,
chunkSourceFactory, chunkSourceFactory,
compositeSequenceableLoaderFactory, compositeSequenceableLoaderFactory,
minLoadableRetryCount, minLoadableRetryCount,
livePresentationDelayMs); livePresentationDelayMs,
tag);
} }
/** /**
...@@ -199,14 +215,15 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -199,14 +215,15 @@ public final class SsMediaSource extends BaseMediaSource
manifestParser = new SsManifestParser(); manifestParser = new SsManifestParser();
} }
return new SsMediaSource( return new SsMediaSource(
null, /* manifest= */ null,
Assertions.checkNotNull(manifestUri), Assertions.checkNotNull(manifestUri),
manifestDataSourceFactory, manifestDataSourceFactory,
manifestParser, manifestParser,
chunkSourceFactory, chunkSourceFactory,
compositeSequenceableLoaderFactory, compositeSequenceableLoaderFactory,
minLoadableRetryCount, minLoadableRetryCount,
livePresentationDelayMs); livePresentationDelayMs,
tag);
} }
/** /**
...@@ -261,6 +278,7 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -261,6 +278,7 @@ public final class SsMediaSource extends BaseMediaSource
private final EventDispatcher manifestEventDispatcher; private final EventDispatcher manifestEventDispatcher;
private final ParsingLoadable.Parser<? extends SsManifest> manifestParser; private final ParsingLoadable.Parser<? extends SsManifest> manifestParser;
private final ArrayList<SsMediaPeriod> mediaPeriods; private final ArrayList<SsMediaPeriod> mediaPeriods;
private final @Nullable Object tag;
private DataSource manifestDataSource; private DataSource manifestDataSource;
private Loader manifestLoader; private Loader manifestLoader;
...@@ -309,13 +327,14 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -309,13 +327,14 @@ public final class SsMediaSource extends BaseMediaSource
MediaSourceEventListener eventListener) { MediaSourceEventListener eventListener) {
this( this(
manifest, manifest,
null, /* manifestUri= */ null,
null, /* manifestDataSourceFactory= */ null,
null, /* manifestParser= */ null,
chunkSourceFactory, chunkSourceFactory,
new DefaultCompositeSequenceableLoaderFactory(), new DefaultCompositeSequenceableLoaderFactory(),
minLoadableRetryCount, minLoadableRetryCount,
DEFAULT_LIVE_PRESENTATION_DELAY_MS); DEFAULT_LIVE_PRESENTATION_DELAY_MS,
/* tag= */ null);
if (eventHandler != null && eventListener != null) { if (eventHandler != null && eventListener != null) {
addEventListener(eventHandler, eventListener); addEventListener(eventHandler, eventListener);
} }
...@@ -400,14 +419,15 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -400,14 +419,15 @@ public final class SsMediaSource extends BaseMediaSource
Handler eventHandler, Handler eventHandler,
MediaSourceEventListener eventListener) { MediaSourceEventListener eventListener) {
this( this(
null, /* manifest= */ null,
manifestUri, manifestUri,
manifestDataSourceFactory, manifestDataSourceFactory,
manifestParser, manifestParser,
chunkSourceFactory, chunkSourceFactory,
new DefaultCompositeSequenceableLoaderFactory(), new DefaultCompositeSequenceableLoaderFactory(),
minLoadableRetryCount, minLoadableRetryCount,
livePresentationDelayMs); livePresentationDelayMs,
/* tag= */ null);
if (eventHandler != null && eventListener != null) { if (eventHandler != null && eventListener != null) {
addEventListener(eventHandler, eventListener); addEventListener(eventHandler, eventListener);
} }
...@@ -421,7 +441,8 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -421,7 +441,8 @@ public final class SsMediaSource extends BaseMediaSource
SsChunkSource.Factory chunkSourceFactory, SsChunkSource.Factory chunkSourceFactory,
CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory, CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory,
int minLoadableRetryCount, int minLoadableRetryCount,
long livePresentationDelayMs) { long livePresentationDelayMs,
@Nullable Object tag) {
Assertions.checkState(manifest == null || !manifest.isLive); Assertions.checkState(manifest == null || !manifest.isLive);
this.manifest = manifest; this.manifest = manifest;
this.manifestUri = manifestUri == null ? null : SsUtil.fixManifestUri(manifestUri); this.manifestUri = manifestUri == null ? null : SsUtil.fixManifestUri(manifestUri);
...@@ -432,6 +453,7 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -432,6 +453,7 @@ public final class SsMediaSource extends BaseMediaSource
this.minLoadableRetryCount = minLoadableRetryCount; this.minLoadableRetryCount = minLoadableRetryCount;
this.livePresentationDelayMs = livePresentationDelayMs; this.livePresentationDelayMs = livePresentationDelayMs;
this.manifestEventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null); this.manifestEventDispatcher = createEventDispatcher(/* mediaPeriodId= */ null);
this.tag = tag;
sideloadedManifest = manifest != null; sideloadedManifest = manifest != null;
mediaPeriods = new ArrayList<>(); mediaPeriods = new ArrayList<>();
} }
...@@ -555,8 +577,15 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -555,8 +577,15 @@ public final class SsMediaSource extends BaseMediaSource
Timeline timeline; Timeline timeline;
if (startTimeUs == Long.MAX_VALUE) { if (startTimeUs == Long.MAX_VALUE) {
long periodDurationUs = manifest.isLive ? C.TIME_UNSET : 0; long periodDurationUs = manifest.isLive ? C.TIME_UNSET : 0;
timeline = new SinglePeriodTimeline(periodDurationUs, 0, 0, 0, true /* isSeekable */, timeline =
manifest.isLive /* isDynamic */); new SinglePeriodTimeline(
periodDurationUs,
/* windowDurationUs= */ 0,
/* windowPositionInPeriodUs= */ 0,
/* windowDefaultStartPositionUs= */ 0,
/* isSeekable= */ true,
manifest.isLive,
tag);
} else if (manifest.isLive) { } else if (manifest.isLive) {
if (manifest.dvrWindowLengthUs != C.TIME_UNSET && manifest.dvrWindowLengthUs > 0) { if (manifest.dvrWindowLengthUs != C.TIME_UNSET && manifest.dvrWindowLengthUs > 0) {
startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs); startTimeUs = Math.max(startTimeUs, endTimeUs - manifest.dvrWindowLengthUs);
...@@ -569,13 +598,27 @@ public final class SsMediaSource extends BaseMediaSource ...@@ -569,13 +598,27 @@ public final class SsMediaSource extends BaseMediaSource
// it to the middle of the window. // it to the middle of the window.
defaultStartPositionUs = Math.min(MIN_LIVE_DEFAULT_START_POSITION_US, durationUs / 2); defaultStartPositionUs = Math.min(MIN_LIVE_DEFAULT_START_POSITION_US, durationUs / 2);
} }
timeline = new SinglePeriodTimeline(C.TIME_UNSET, durationUs, startTimeUs, timeline =
defaultStartPositionUs, true /* isSeekable */, true /* isDynamic */); new SinglePeriodTimeline(
/* periodDurationUs= */ C.TIME_UNSET,
durationUs,
startTimeUs,
defaultStartPositionUs,
/* isSeekable= */ true,
/* isDynamic= */ true,
tag);
} else { } else {
long durationUs = manifest.durationUs != C.TIME_UNSET ? manifest.durationUs long durationUs = manifest.durationUs != C.TIME_UNSET ? manifest.durationUs
: endTimeUs - startTimeUs; : endTimeUs - startTimeUs;
timeline = new SinglePeriodTimeline(startTimeUs + durationUs, durationUs, startTimeUs, 0, timeline =
true /* isSeekable */, false /* isDynamic */); new SinglePeriodTimeline(
startTimeUs + durationUs,
durationUs,
startTimeUs,
/* windowDefaultStartPositionUs= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ false,
tag);
} }
refreshSourceInfo(timeline, manifest); refreshSourceInfo(timeline, manifest);
} }
......
...@@ -164,13 +164,21 @@ public final class FakeTimeline extends Timeline { ...@@ -164,13 +164,21 @@ public final class FakeTimeline extends Timeline {
} }
@Override @Override
public Window getWindow(int windowIndex, Window window, boolean setIds, public Window getWindow(
long defaultPositionProjectionUs) { int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex]; TimelineWindowDefinition windowDefinition = windowDefinitions[windowIndex];
Object id = setIds ? windowDefinition.id : null; Object tag = setTag ? windowDefinition.id : null;
return window.set(id, C.TIME_UNSET, C.TIME_UNSET, windowDefinition.isSeekable, return window.set(
windowDefinition.isDynamic, 0, windowDefinition.durationUs, periodOffsets[windowIndex], tag,
periodOffsets[windowIndex + 1] - 1, 0); /* presentationStartTimeMs= */ C.TIME_UNSET,
/* windowStartTimeMs= */ C.TIME_UNSET,
windowDefinition.isSeekable,
windowDefinition.isDynamic,
/* defaultPositionUs= */ 0,
windowDefinition.durationUs,
periodOffsets[windowIndex],
periodOffsets[windowIndex + 1] - 1,
/* positionInFirstPeriodUs= */ 0);
} }
@Override @Override
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package com.google.android.exoplayer2.testutil; package com.google.android.exoplayer2.testutil;
import android.os.Looper; import android.os.Looper;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
...@@ -143,6 +144,11 @@ public abstract class StubExoPlayer implements ExoPlayer { ...@@ -143,6 +144,11 @@ public abstract class StubExoPlayer implements ExoPlayer {
} }
@Override @Override
public @Nullable Object getCurrentTag() {
throw new UnsupportedOperationException();
}
@Override
public void stop() { public void stop() {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
......
...@@ -34,7 +34,7 @@ public final class TimelineAsserts { ...@@ -34,7 +34,7 @@ public final class TimelineAsserts {
/** Assert that timeline is empty (i.e. has no windows or periods). */ /** Assert that timeline is empty (i.e. has no windows or periods). */
public static void assertEmpty(Timeline timeline) { public static void assertEmpty(Timeline timeline) {
assertWindowIds(timeline); assertWindowTags(timeline);
assertPeriodCounts(timeline); assertPeriodCounts(timeline);
for (boolean shuffled : new boolean[] {false, true}) { for (boolean shuffled : new boolean[] {false, true}) {
assertThat(timeline.getFirstWindowIndex(shuffled)).isEqualTo(C.INDEX_UNSET); assertThat(timeline.getFirstWindowIndex(shuffled)).isEqualTo(C.INDEX_UNSET);
...@@ -43,18 +43,18 @@ public final class TimelineAsserts { ...@@ -43,18 +43,18 @@ public final class TimelineAsserts {
} }
/** /**
* Asserts that window IDs are set correctly. * Asserts that window tags are set correctly.
* *
* @param expectedWindowIds A list of expected window IDs. If an ID is unknown or not important * @param expectedWindowTags A list of expected window tags. If a tag is unknown or not important
* {@code null} can be passed to skip this window. * {@code null} can be passed to skip this window.
*/ */
public static void assertWindowIds(Timeline timeline, Object... expectedWindowIds) { public static void assertWindowTags(Timeline timeline, Object... expectedWindowTags) {
Window window = new Window(); Window window = new Window();
assertThat(timeline.getWindowCount()).isEqualTo(expectedWindowIds.length); assertThat(timeline.getWindowCount()).isEqualTo(expectedWindowTags.length);
for (int i = 0; i < timeline.getWindowCount(); i++) { for (int i = 0; i < timeline.getWindowCount(); i++) {
timeline.getWindow(i, window, true); timeline.getWindow(i, window, true);
if (expectedWindowIds[i] != null) { if (expectedWindowTags[i] != null) {
assertThat(window.id).isEqualTo(expectedWindowIds[i]); assertThat(window.tag).isEqualTo(expectedWindowTags[i]);
} }
} }
} }
......
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