mirror of
https://github.com/samsonjs/media.git
synced 2026-04-06 11:25:46 +00:00
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
This commit is contained in:
parent
f25c7a859a
commit
6b82d1c2bd
26 changed files with 517 additions and 191 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
|
||||
|
|
|
|||
|
|
@ -311,6 +311,14 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||
internalPlayer.setSeekParameters(seekParameters);
|
||||
}
|
||||
|
||||
@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);
|
||||
|
|
|
|||
|
|
@ -655,6 +655,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.
|
||||
|
|
|
|||
|
|
@ -664,6 +664,11 @@ public class SimpleExoPlayer implements ExoPlayer, Player.VideoComponent, Player
|
|||
player.setSeekParameters(seekParameters);
|
||||
}
|
||||
|
||||
@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() {
|
||||
/** An empty timeline. */
|
||||
public static final Timeline EMPTY =
|
||||
new Timeline() {
|
||||
|
||||
@Override
|
||||
public int getWindowCount() {
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public int getWindowCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Window getWindow(int windowIndex, Window window, boolean setIds,
|
||||
long defaultPositionProjectionUs) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
@Override
|
||||
public Window getWindow(
|
||||
int windowIndex, Window window, boolean setTag, long defaultPositionProjectionUs) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getPeriodCount() {
|
||||
return 0;
|
||||
}
|
||||
@Override
|
||||
public int getPeriodCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
@Override
|
||||
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||
throw new IndexOutOfBoundsException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIndexOfPeriod(Object uid) {
|
||||
return C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
};
|
||||
@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;
|
||||
|
|
@ -153,6 +155,21 @@ public final class ExtractorMediaSource extends BaseMediaSource
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
|
@ -70,6 +71,20 @@ public final class SingleSampleMediaSource extends BaseMediaSource {
|
|||
this.minLoadableRetryCount = DEFAULT_MIN_LOADABLE_RETRY_COUNT;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
|
@ -97,6 +98,21 @@ public final class DashMediaSource extends BaseMediaSource {
|
|||
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
private final @Nullable Object windowTag;
|
||||
|
||||
public DashTimeline(long presentationStartTimeMs, long windowStartTimeMs, int firstPeriodId,
|
||||
long offsetInFirstPeriodUs, long windowDurationUs, long windowDefaultStartPositionUs,
|
||||
DashManifest manifest) {
|
||||
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.
|
||||
|
|
@ -87,6 +88,21 @@ public final class HlsMediaSource extends BaseMediaSource
|
|||
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
|
|
@ -86,6 +87,20 @@ public final class SsMediaSource extends BaseMediaSource
|
|||
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
|
@ -142,6 +143,11 @@ public abstract class StubExoPlayer implements ExoPlayer {
|
|||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue