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