Allow seeking to a default position in a period.
When seeking to the default position in a period, the containing source may actually return a position in another period. Multi-period live sources can do this to seek the player to the live edge. ExoPlayerImplInternal uses the same functionality when the playback position reaches the end of a period to determine what period/position to play next. This means that when playback transitions to a multi-period live source from some other source (playing a concatenation of those two sources), the player will play the live edge rather than the beginning of the earliest period. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=128984355
This commit is contained in:
parent
abd5653dc4
commit
c1729b640c
@ -300,6 +300,17 @@ public interface ExoPlayer {
|
|||||||
*/
|
*/
|
||||||
void seekTo(int periodIndex, long positionMs);
|
void seekTo(int periodIndex, long positionMs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeks to the default position associated with the specified period. The position can depend on
|
||||||
|
* the type of source passed to {@link #setMediaSource(MediaSource)}. For live streams it will
|
||||||
|
* typically be the live edge. For other types of streams it will typically be the start of the
|
||||||
|
* stream.
|
||||||
|
*
|
||||||
|
* @param periodIndex The index of the period whose associated default position should be seeked
|
||||||
|
* to.
|
||||||
|
*/
|
||||||
|
void seekToDefaultPosition(int periodIndex);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention
|
* Stops playback. Use {@code setPlayWhenReady(false)} rather than this method if the intention
|
||||||
* is to pause playback.
|
* is to pause playback.
|
||||||
|
@ -136,17 +136,25 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
@Override
|
@Override
|
||||||
public void seekTo(int periodIndex, long positionMs) {
|
public void seekTo(int periodIndex, long positionMs) {
|
||||||
boolean periodChanging = periodIndex != getCurrentPeriodIndex();
|
boolean periodChanging = periodIndex != getCurrentPeriodIndex();
|
||||||
|
boolean seekToDefaultPosition = positionMs == ExoPlayer.UNKNOWN_TIME;
|
||||||
maskingPeriodIndex = periodIndex;
|
maskingPeriodIndex = periodIndex;
|
||||||
maskingPositionMs = positionMs;
|
maskingPositionMs = seekToDefaultPosition ? 0 : positionMs;
|
||||||
maskingDurationMs = periodChanging ? ExoPlayer.UNKNOWN_TIME : getDuration();
|
maskingDurationMs = periodChanging ? ExoPlayer.UNKNOWN_TIME : getDuration();
|
||||||
|
|
||||||
pendingSeekAcks++;
|
pendingSeekAcks++;
|
||||||
internalPlayer.seekTo(periodIndex, positionMs * 1000);
|
internalPlayer.seekTo(periodIndex, seekToDefaultPosition ? C.UNSET_TIME_US : positionMs * 1000);
|
||||||
for (EventListener listener : listeners) {
|
if (!seekToDefaultPosition) {
|
||||||
listener.onPositionDiscontinuity(periodIndex, positionMs);
|
for (EventListener listener : listeners) {
|
||||||
|
listener.onPositionDiscontinuity(periodIndex, positionMs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekToDefaultPosition(int periodIndex) {
|
||||||
|
seekTo(periodIndex, ExoPlayer.UNKNOWN_TIME);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
internalPlayer.stop();
|
internalPlayer.stop();
|
||||||
@ -180,8 +188,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getCurrentPosition() {
|
public long getCurrentPosition() {
|
||||||
return pendingSeekAcks == 0 ? playbackInfo.positionUs / 1000
|
return pendingSeekAcks > 0 ? maskingPositionMs
|
||||||
: maskingPositionMs;
|
: playbackInfo.positionUs == C.UNSET_TIME_US ? 0 : (playbackInfo.positionUs / 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -239,14 +247,23 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ExoPlayerImplInternal.MSG_SEEK_ACK: {
|
case ExoPlayerImplInternal.MSG_SEEK_ACK: {
|
||||||
pendingSeekAcks--;
|
if (--pendingSeekAcks == 0) {
|
||||||
|
long positionMs = playbackInfo.startPositionUs == C.UNSET_TIME_US ? 0
|
||||||
|
: playbackInfo.startPositionUs / 1000;
|
||||||
|
if (playbackInfo.periodIndex != maskingPeriodIndex || positionMs != maskingPositionMs) {
|
||||||
|
for (EventListener listener : listeners) {
|
||||||
|
listener.onPositionDiscontinuity(playbackInfo.periodIndex, positionMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ExoPlayerImplInternal.MSG_PERIOD_CHANGED: {
|
case ExoPlayerImplInternal.MSG_POSITION_DISCONTINUITY: {
|
||||||
playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj;
|
playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj;
|
||||||
if (pendingSeekAcks == 0) {
|
if (pendingSeekAcks == 0) {
|
||||||
for (EventListener listener : listeners) {
|
for (EventListener listener : listeners) {
|
||||||
listener.onPositionDiscontinuity(playbackInfo.periodIndex, 0);
|
listener.onPositionDiscontinuity(playbackInfo.periodIndex,
|
||||||
|
playbackInfo.startPositionUs / 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -56,6 +56,7 @@ import java.util.ArrayList;
|
|||||||
public volatile long positionUs;
|
public volatile long positionUs;
|
||||||
public volatile long bufferedPositionUs;
|
public volatile long bufferedPositionUs;
|
||||||
public volatile long durationUs;
|
public volatile long durationUs;
|
||||||
|
public volatile long startPositionUs;
|
||||||
|
|
||||||
public PlaybackInfo(int periodIndex) {
|
public PlaybackInfo(int periodIndex) {
|
||||||
this.periodIndex = periodIndex;
|
this.periodIndex = periodIndex;
|
||||||
@ -71,7 +72,7 @@ import java.util.ArrayList;
|
|||||||
public static final int MSG_LOADING_CHANGED = 2;
|
public static final int MSG_LOADING_CHANGED = 2;
|
||||||
public static final int MSG_SET_PLAY_WHEN_READY_ACK = 3;
|
public static final int MSG_SET_PLAY_WHEN_READY_ACK = 3;
|
||||||
public static final int MSG_SEEK_ACK = 4;
|
public static final int MSG_SEEK_ACK = 4;
|
||||||
public static final int MSG_PERIOD_CHANGED = 5;
|
public static final int MSG_POSITION_DISCONTINUITY = 5;
|
||||||
public static final int MSG_TIMELINE_CHANGED = 6;
|
public static final int MSG_TIMELINE_CHANGED = 6;
|
||||||
public static final int MSG_ERROR = 7;
|
public static final int MSG_ERROR = 7;
|
||||||
|
|
||||||
@ -485,9 +486,20 @@ import java.util.ArrayList;
|
|||||||
|
|
||||||
private void seekToInternal(int periodIndex, long positionUs) throws ExoPlaybackException {
|
private void seekToInternal(int periodIndex, long positionUs) throws ExoPlaybackException {
|
||||||
try {
|
try {
|
||||||
|
if (positionUs == C.UNSET_TIME_US && mediaSource != null) {
|
||||||
|
MediaSource.Position defaultStartPosition =
|
||||||
|
mediaSource.getDefaultStartPosition(periodIndex);
|
||||||
|
if (defaultStartPosition != null) {
|
||||||
|
// We know the default position so seek to it now.
|
||||||
|
periodIndex = defaultStartPosition.periodIndex;
|
||||||
|
positionUs = defaultStartPosition.positionUs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (periodIndex == playbackInfo.periodIndex
|
if (periodIndex == playbackInfo.periodIndex
|
||||||
&& (positionUs / 1000) == (playbackInfo.positionUs / 1000)) {
|
&& ((positionUs == C.UNSET_TIME_US && playbackInfo.positionUs == C.UNSET_TIME_US)
|
||||||
// Seek position equals the current position to the nearest millisecond. Do nothing.
|
|| ((positionUs / 1000) == (playbackInfo.positionUs / 1000)))) {
|
||||||
|
// Seek position equals the current position. Do nothing.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
seekToPeriodPosition(periodIndex, positionUs);
|
seekToPeriodPosition(periodIndex, positionUs);
|
||||||
@ -503,13 +515,15 @@ import java.util.ArrayList;
|
|||||||
positionUs = internalTimeline.seekTo(periodIndex, positionUs);
|
positionUs = internalTimeline.seekTo(periodIndex, positionUs);
|
||||||
if (periodIndex != playbackInfo.periodIndex) {
|
if (periodIndex != playbackInfo.periodIndex) {
|
||||||
playbackInfo = new PlaybackInfo(periodIndex);
|
playbackInfo = new PlaybackInfo(periodIndex);
|
||||||
|
playbackInfo.startPositionUs = positionUs;
|
||||||
playbackInfo.positionUs = positionUs;
|
playbackInfo.positionUs = positionUs;
|
||||||
eventHandler.obtainMessage(MSG_PERIOD_CHANGED, playbackInfo).sendToTarget();
|
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
|
||||||
} else {
|
} else {
|
||||||
|
playbackInfo.startPositionUs = positionUs;
|
||||||
playbackInfo.positionUs = positionUs;
|
playbackInfo.positionUs = positionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePlaybackPositions();
|
updatePlaybackPositions();
|
||||||
|
|
||||||
if (mediaSource != null) {
|
if (mediaSource != null) {
|
||||||
setState(ExoPlayer.STATE_BUFFERING);
|
setState(ExoPlayer.STATE_BUFFERING);
|
||||||
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
|
||||||
@ -681,7 +695,14 @@ import java.util.ArrayList;
|
|||||||
// Release all loaded periods and seek to the new playing period index.
|
// Release all loaded periods and seek to the new playing period index.
|
||||||
releasePeriodsFrom(playingPeriod);
|
releasePeriodsFrom(playingPeriod);
|
||||||
playingPeriod = null;
|
playingPeriod = null;
|
||||||
seekToPeriodPosition(newPlayingPeriodIndex, 0);
|
|
||||||
|
MediaSource.Position defaultStartPosition =
|
||||||
|
mediaSource.getDefaultStartPosition(newPlayingPeriodIndex);
|
||||||
|
if (defaultStartPosition != null) {
|
||||||
|
seekToPeriodPosition(defaultStartPosition.periodIndex, defaultStartPosition.positionUs);
|
||||||
|
} else {
|
||||||
|
seekToPeriodPosition(newPlayingPeriodIndex, C.UNSET_TIME_US);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -746,9 +767,11 @@ import java.util.ArrayList;
|
|||||||
: mediaSource.getNewPlayingPeriodIndex(playbackInfo.periodIndex, oldTimeline);
|
: mediaSource.getNewPlayingPeriodIndex(playbackInfo.periodIndex, oldTimeline);
|
||||||
if (newPlayingIndex != Timeline.NO_PERIOD_INDEX
|
if (newPlayingIndex != Timeline.NO_PERIOD_INDEX
|
||||||
&& newPlayingIndex != playbackInfo.periodIndex) {
|
&& newPlayingIndex != playbackInfo.periodIndex) {
|
||||||
|
long oldPositionUs = playbackInfo.positionUs;
|
||||||
playbackInfo = new PlaybackInfo(newPlayingIndex);
|
playbackInfo = new PlaybackInfo(newPlayingIndex);
|
||||||
|
playbackInfo.startPositionUs = oldPositionUs;
|
||||||
updatePlaybackPositions();
|
updatePlaybackPositions();
|
||||||
eventHandler.obtainMessage(MSG_PERIOD_CHANGED, playbackInfo).sendToTarget();
|
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -762,21 +785,33 @@ import java.util.ArrayList;
|
|||||||
// Update the loading period.
|
// Update the loading period.
|
||||||
if (loadingPeriod == null || (loadingPeriod.isFullyBuffered() && !loadingPeriod.isLast
|
if (loadingPeriod == null || (loadingPeriod.isFullyBuffered() && !loadingPeriod.isLast
|
||||||
&& bufferAheadPeriodCount < MAXIMUM_BUFFER_AHEAD_PERIODS)) {
|
&& bufferAheadPeriodCount < MAXIMUM_BUFFER_AHEAD_PERIODS)) {
|
||||||
// Try to obtain the next period to start loading.
|
int periodIndex =
|
||||||
int periodIndex = loadingPeriod == null ? playbackInfo.periodIndex
|
loadingPeriod == null ? playbackInfo.periodIndex : loadingPeriod.index + 1;
|
||||||
: loadingPeriod.index + 1;
|
long startPositionUs = playbackInfo.positionUs;
|
||||||
// Attempt to create the next period.
|
if (loadingPeriod != null || startPositionUs == C.UNSET_TIME_US) {
|
||||||
MediaPeriod mediaPeriod = mediaSource.createPeriod(periodIndex);
|
// We are starting to load the next period or seeking to the default position, so request
|
||||||
if (mediaPeriod != null) {
|
// a period and position from the source.
|
||||||
|
MediaSource.Position defaultStartPosition =
|
||||||
|
mediaSource.getDefaultStartPosition(periodIndex);
|
||||||
|
if (defaultStartPosition != null) {
|
||||||
|
periodIndex = defaultStartPosition.periodIndex;
|
||||||
|
startPositionUs = defaultStartPosition.positionUs;
|
||||||
|
} else {
|
||||||
|
startPositionUs = C.UNSET_TIME_US;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaPeriod mediaPeriod;
|
||||||
|
if (startPositionUs != C.UNSET_TIME_US
|
||||||
|
&& (mediaPeriod = mediaSource.createPeriod(periodIndex)) != null) {
|
||||||
Period newPeriod = new Period(renderers, rendererCapabilities, trackSelector, mediaPeriod,
|
Period newPeriod = new Period(renderers, rendererCapabilities, trackSelector, mediaPeriod,
|
||||||
timeline.getPeriodId(periodIndex), periodIndex);
|
timeline.getPeriodId(periodIndex), periodIndex, startPositionUs);
|
||||||
newPeriod.isLast = timeline.isFinal() && periodIndex == timeline.getPeriodCount() - 1;
|
newPeriod.isLast = timeline.isFinal() && periodIndex == timeline.getPeriodCount() - 1;
|
||||||
if (loadingPeriod != null) {
|
if (loadingPeriod != null) {
|
||||||
loadingPeriod.setNextPeriod(newPeriod);
|
loadingPeriod.setNextPeriod(newPeriod);
|
||||||
}
|
}
|
||||||
bufferAheadPeriodCount++;
|
bufferAheadPeriodCount++;
|
||||||
loadingPeriod = newPeriod;
|
loadingPeriod = newPeriod;
|
||||||
long startPositionUs = playingPeriod == null ? playbackInfo.positionUs : 0;
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
loadingPeriod.mediaPeriod.preparePeriod(ExoPlayerImplInternal.this,
|
loadingPeriod.mediaPeriod.preparePeriod(ExoPlayerImplInternal.this,
|
||||||
loadControl.getAllocator(), startPositionUs);
|
loadControl.getAllocator(), startPositionUs);
|
||||||
@ -807,8 +842,9 @@ import java.util.ArrayList;
|
|||||||
setPlayingPeriod(playingPeriod.nextPeriod);
|
setPlayingPeriod(playingPeriod.nextPeriod);
|
||||||
bufferAheadPeriodCount--;
|
bufferAheadPeriodCount--;
|
||||||
playbackInfo = new PlaybackInfo(playingPeriod.index);
|
playbackInfo = new PlaybackInfo(playingPeriod.index);
|
||||||
|
playbackInfo.startPositionUs = playingPeriod.startPositionUs;
|
||||||
updatePlaybackPositions();
|
updatePlaybackPositions();
|
||||||
eventHandler.obtainMessage(MSG_PERIOD_CHANGED, playbackInfo).sendToTarget();
|
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
|
||||||
}
|
}
|
||||||
updateTimelineState();
|
updateTimelineState();
|
||||||
if (readingPeriod == null) {
|
if (readingPeriod == null) {
|
||||||
@ -858,12 +894,19 @@ import java.util.ArrayList;
|
|||||||
// Stale event.
|
// Stale event.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long startPositionUs = playingPeriod == null ? playbackInfo.positionUs : 0;
|
loadingPeriod.handlePrepared(loadingPeriod.startPositionUs, loadControl);
|
||||||
loadingPeriod.handlePrepared(startPositionUs, loadControl);
|
|
||||||
if (playingPeriod == null) {
|
if (playingPeriod == null) {
|
||||||
// This is the first prepared period, so start playing it.
|
// This is the first prepared period, so start playing it.
|
||||||
readingPeriod = loadingPeriod;
|
readingPeriod = loadingPeriod;
|
||||||
setPlayingPeriod(readingPeriod);
|
setPlayingPeriod(readingPeriod);
|
||||||
|
if (playbackInfo.startPositionUs == C.UNSET_TIME_US) {
|
||||||
|
// Update the playback info when seeking to a default position.
|
||||||
|
playbackInfo = new PlaybackInfo(playingPeriod.index);
|
||||||
|
playbackInfo.startPositionUs = playingPeriod.startPositionUs;
|
||||||
|
resetInternalPosition(playbackInfo.startPositionUs);
|
||||||
|
updatePlaybackPositions();
|
||||||
|
eventHandler.obtainMessage(MSG_POSITION_DISCONTINUITY, playbackInfo).sendToTarget();
|
||||||
|
}
|
||||||
updateTimelineState();
|
updateTimelineState();
|
||||||
}
|
}
|
||||||
maybeContinueLoading();
|
maybeContinueLoading();
|
||||||
@ -895,6 +938,11 @@ import java.util.ArrayList;
|
|||||||
}
|
}
|
||||||
|
|
||||||
public long seekTo(int periodIndex, long seekPositionUs) throws ExoPlaybackException {
|
public long seekTo(int periodIndex, long seekPositionUs) throws ExoPlaybackException {
|
||||||
|
if (seekPositionUs == C.UNSET_TIME_US) {
|
||||||
|
// We don't know where to seek to yet, so clear the whole timeline.
|
||||||
|
periodIndex = Timeline.NO_PERIOD_INDEX;
|
||||||
|
}
|
||||||
|
|
||||||
// Clear the timeline, but keep the requested period if it is already prepared.
|
// Clear the timeline, but keep the requested period if it is already prepared.
|
||||||
Period period = playingPeriod;
|
Period period = playingPeriod;
|
||||||
Period newPlayingPeriod = null;
|
Period newPlayingPeriod = null;
|
||||||
@ -929,7 +977,9 @@ import java.util.ArrayList;
|
|||||||
playingPeriod = null;
|
playingPeriod = null;
|
||||||
readingPeriod = null;
|
readingPeriod = null;
|
||||||
loadingPeriod = null;
|
loadingPeriod = null;
|
||||||
resetInternalPosition(seekPositionUs);
|
if (seekPositionUs != C.UNSET_TIME_US) {
|
||||||
|
resetInternalPosition(seekPositionUs);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return seekPositionUs;
|
return seekPositionUs;
|
||||||
}
|
}
|
||||||
@ -1120,6 +1170,7 @@ import java.util.ArrayList;
|
|||||||
public final MediaPeriod mediaPeriod;
|
public final MediaPeriod mediaPeriod;
|
||||||
public final Object id;
|
public final Object id;
|
||||||
public final SampleStream[] sampleStreams;
|
public final SampleStream[] sampleStreams;
|
||||||
|
public final long startPositionUs;
|
||||||
|
|
||||||
public int index;
|
public int index;
|
||||||
public boolean isLast;
|
public boolean isLast;
|
||||||
@ -1138,13 +1189,15 @@ import java.util.ArrayList;
|
|||||||
private TrackSelectionArray periodTrackSelections;
|
private TrackSelectionArray periodTrackSelections;
|
||||||
|
|
||||||
public Period(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
|
public Period(Renderer[] renderers, RendererCapabilities[] rendererCapabilities,
|
||||||
TrackSelector trackSelector, MediaPeriod mediaPeriod, Object id, int index) {
|
TrackSelector trackSelector, MediaPeriod mediaPeriod, Object id, int index,
|
||||||
|
long positionUs) {
|
||||||
this.renderers = renderers;
|
this.renderers = renderers;
|
||||||
this.rendererCapabilities = rendererCapabilities;
|
this.rendererCapabilities = rendererCapabilities;
|
||||||
this.trackSelector = trackSelector;
|
this.trackSelector = trackSelector;
|
||||||
this.mediaPeriod = mediaPeriod;
|
this.mediaPeriod = mediaPeriod;
|
||||||
this.id = Assertions.checkNotNull(id);
|
this.id = Assertions.checkNotNull(id);
|
||||||
sampleStreams = new SampleStream[renderers.length];
|
sampleStreams = new SampleStream[renderers.length];
|
||||||
|
startPositionUs = positionUs;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,6 +359,11 @@ public final class SimpleExoPlayer implements ExoPlayer {
|
|||||||
player.seekTo(periodIndex, positionMs);
|
player.seekTo(periodIndex, positionMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekToDefaultPosition(int periodIndex) {
|
||||||
|
player.seekToDefaultPosition(periodIndex);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
player.stop();
|
player.stop();
|
||||||
|
@ -53,15 +53,25 @@ public final class ConcatenatingMediaSource implements MediaSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline)
|
public int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldConcatenatedTimeline)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
ConcatenatedTimeline oldConcatenatedTimeline = (ConcatenatedTimeline) oldTimeline;
|
ConcatenatedTimeline oldTimeline = (ConcatenatedTimeline) oldConcatenatedTimeline;
|
||||||
int sourceIndex = oldConcatenatedTimeline.getSourceIndexForPeriod(oldPlayingPeriodIndex);
|
int sourceIndex = oldTimeline.getSourceIndexForPeriod(oldPlayingPeriodIndex);
|
||||||
int sourceFirstPeriodIndex = oldConcatenatedTimeline.getFirstPeriodIndexInSource(sourceIndex);
|
int oldFirstPeriodIndex = oldTimeline.getFirstPeriodIndexInSource(sourceIndex);
|
||||||
return sourceFirstPeriodIndex == Timeline.NO_PERIOD_INDEX ? Timeline.NO_PERIOD_INDEX
|
int firstPeriodIndex = timeline.getFirstPeriodIndexInSource(sourceIndex);
|
||||||
: sourceFirstPeriodIndex + mediaSources[sourceIndex].getNewPlayingPeriodIndex(
|
return firstPeriodIndex == Timeline.NO_PERIOD_INDEX ? Timeline.NO_PERIOD_INDEX
|
||||||
oldPlayingPeriodIndex - sourceFirstPeriodIndex,
|
: firstPeriodIndex + mediaSources[sourceIndex].getNewPlayingPeriodIndex(
|
||||||
oldConcatenatedTimeline.timelines[sourceIndex]);
|
oldPlayingPeriodIndex - oldFirstPeriodIndex, oldTimeline.timelines[sourceIndex]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Position getDefaultStartPosition(int index) {
|
||||||
|
int sourceIndex = timeline.getSourceIndexForPeriod(index);
|
||||||
|
int sourceFirstPeriodIndex = timeline.getFirstPeriodIndexInSource(sourceIndex);
|
||||||
|
Position defaultStartPosition =
|
||||||
|
mediaSources[sourceIndex].getDefaultStartPosition(index - sourceFirstPeriodIndex);
|
||||||
|
return new Position(defaultStartPosition.periodIndex + sourceFirstPeriodIndex,
|
||||||
|
defaultStartPosition.positionUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -187,6 +187,11 @@ public final class ExtractorMediaSource implements MediaPeriod, MediaSource,
|
|||||||
return oldPlayingPeriodIndex;
|
return oldPlayingPeriodIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Position getDefaultStartPosition(int index) {
|
||||||
|
return Position.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index) {
|
public MediaPeriod createPeriod(int index) {
|
||||||
Assertions.checkArgument(index == 0);
|
Assertions.checkArgument(index == 0);
|
||||||
|
@ -38,6 +38,38 @@ public interface MediaSource {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A position in the timeline.
|
||||||
|
*/
|
||||||
|
final class Position {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A start position at the earliest time in the first period.
|
||||||
|
*/
|
||||||
|
public static final Position DEFAULT = new Position(0, 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The index of the period containing the timeline position.
|
||||||
|
*/
|
||||||
|
public final int periodIndex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The position in microseconds within the period.
|
||||||
|
*/
|
||||||
|
public final long positionUs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new timeline position.
|
||||||
|
*
|
||||||
|
* @param periodIndex The index of the period containing the timeline position.
|
||||||
|
* @param positionUs The position in microseconds within the period.
|
||||||
|
*/
|
||||||
|
public Position(int periodIndex, long positionUs) {
|
||||||
|
this.periodIndex = periodIndex;
|
||||||
|
this.positionUs = positionUs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts preparation of the source.
|
* Starts preparation of the source.
|
||||||
*
|
*
|
||||||
@ -55,6 +87,20 @@ public interface MediaSource {
|
|||||||
*/
|
*/
|
||||||
int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) throws IOException;
|
int getNewPlayingPeriodIndex(int oldPlayingPeriodIndex, Timeline oldTimeline) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the default {@link Position} that the player should play when it reaches the period at
|
||||||
|
* {@code index}, or {@code null} if the default start period and position are not yet known.
|
||||||
|
* <p>
|
||||||
|
* For example, sources can return a {@link Position} with the passed period {@code index} to play
|
||||||
|
* the period at {@code index} immediately after the period at {@code index - 1}. Or, sources
|
||||||
|
* providing multi-period live streams may return the index and position of the live edge when
|
||||||
|
* passed {@code index == 0} so that the playback position jumps to the live edge.
|
||||||
|
*
|
||||||
|
* @param index The index of the period the player has just reached.
|
||||||
|
* @return The default start position.
|
||||||
|
*/
|
||||||
|
Position getDefaultStartPosition(int index);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link MediaPeriod} corresponding to the period at the specified index, or
|
* Returns a {@link MediaPeriod} corresponding to the period at the specified index, or
|
||||||
* {@code null} if the period at the specified index is not yet available.
|
* {@code null} if the period at the specified index is not yet available.
|
||||||
|
@ -64,6 +64,11 @@ public final class MergingMediaSource implements MediaSource {
|
|||||||
return mediaSources[0].getNewPlayingPeriodIndex(oldPlayingPeriodIndex, oldTimeline);
|
return mediaSources[0].getNewPlayingPeriodIndex(oldPlayingPeriodIndex, oldTimeline);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Position getDefaultStartPosition(int index) {
|
||||||
|
return mediaSources[0].getDefaultStartPosition(index);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index) throws IOException {
|
public MediaPeriod createPeriod(int index) throws IOException {
|
||||||
MediaPeriod[] periods = new MediaPeriod[mediaSources.length];
|
MediaPeriod[] periods = new MediaPeriod[mediaSources.length];
|
||||||
|
@ -122,6 +122,11 @@ public final class SingleSampleMediaSource implements MediaPeriod, MediaSource,
|
|||||||
return oldPlayingPeriodIndex;
|
return oldPlayingPeriodIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Position getDefaultStartPosition(int index) {
|
||||||
|
return Position.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index) {
|
public MediaPeriod createPeriod(int index) {
|
||||||
Assertions.checkArgument(index == 0);
|
Assertions.checkArgument(index == 0);
|
||||||
|
@ -122,6 +122,16 @@ public final class DashMediaSource implements MediaSource {
|
|||||||
return Timeline.NO_PERIOD_INDEX;
|
return Timeline.NO_PERIOD_INDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Position getDefaultStartPosition(int index) {
|
||||||
|
if (index == 0 && manifest.dynamic) {
|
||||||
|
// The stream is live, so jump to the live edge.
|
||||||
|
// TODO[playlists]: Actually jump to the live edge, rather than the start of the last period.
|
||||||
|
return new Position(periods.size() - 1, 0);
|
||||||
|
}
|
||||||
|
return new Position(index, 0);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index) throws IOException {
|
public MediaPeriod createPeriod(int index) throws IOException {
|
||||||
if (periods == null || periods.size() <= index) {
|
if (periods == null || periods.size() <= index) {
|
||||||
|
@ -118,6 +118,12 @@ public final class HlsMediaSource implements MediaPeriod, MediaSource,
|
|||||||
return oldPlayingPeriodIndex;
|
return oldPlayingPeriodIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Position getDefaultStartPosition(int index) {
|
||||||
|
// TODO: Return the position of the live edge, if applicable.
|
||||||
|
return Position.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index) {
|
public MediaPeriod createPeriod(int index) {
|
||||||
Assertions.checkArgument(index == 0);
|
Assertions.checkArgument(index == 0);
|
||||||
|
@ -100,6 +100,12 @@ public final class SsMediaSource implements MediaSource,
|
|||||||
return oldPlayingPeriodIndex;
|
return oldPlayingPeriodIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Position getDefaultStartPosition(int index) {
|
||||||
|
// TODO: Return the position of the live edge, if applicable.
|
||||||
|
return Position.DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index) {
|
public MediaPeriod createPeriod(int index) {
|
||||||
Assertions.checkArgument(index == 0);
|
Assertions.checkArgument(index == 0);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user