Calculate ExtractorSampleSource duration if unknown (playlists #4).

When buffering playlist items after the currently-playing one,
continueBuffering should take a position that is negative because the playback
position is before the start of the source being buffered. This change makes
sure that ExtractorSampleSources always have a known duration when they are
fully buffered, which means that the correct (negative) source-relative
position can be calculated.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=124949409
This commit is contained in:
andrewlewis 2016-06-15 07:30:57 -07:00 committed by Oliver Woodman
parent e6267cd253
commit 66a5c96c5a
4 changed files with 55 additions and 72 deletions

View File

@ -364,7 +364,7 @@ import java.util.concurrent.atomic.AtomicInteger;
long operationStartTimeMs = SystemClock.elapsedRealtime(); long operationStartTimeMs = SystemClock.elapsedRealtime();
if (sampleSource == null) { if (sampleSource == null) {
timeline.updateSources(); timeline.updateSources();
sampleSource = timeline.getSampleSource(internalPositionUs); sampleSource = timeline.getSampleSource();
if (sampleSource != null) { if (sampleSource != null) {
resumeInternal(); resumeInternal();
} else { } else {
@ -380,7 +380,7 @@ import java.util.concurrent.atomic.AtomicInteger;
// Process reset if there is one, else update the position. // Process reset if there is one, else update the position.
if (!checkForSourceResetInternal()) { if (!checkForSourceResetInternal()) {
updatePositionUs(); updatePositionUs();
sampleSource = timeline.getSampleSource(internalPositionUs); sampleSource = timeline.getSampleSource();
} }
updateBufferedPositionUs(); updateBufferedPositionUs();
timeline.updateSources(); timeline.updateSources();
@ -405,17 +405,17 @@ import java.util.concurrent.atomic.AtomicInteger;
allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded; allRenderersReadyOrEnded = allRenderersReadyOrEnded && rendererReadyOrEnded;
} }
boolean timelineIsReady = timeline.isReady();
if (allRenderersEnded && (durationUs == C.UNSET_TIME_US || durationUs <= positionUs)) { if (allRenderersEnded && (durationUs == C.UNSET_TIME_US || durationUs <= positionUs)) {
setState(ExoPlayer.STATE_ENDED); setState(ExoPlayer.STATE_ENDED);
stopRenderers(); stopRenderers();
} else if (state == ExoPlayer.STATE_BUFFERING && allRenderersReadyOrEnded } else if (state == ExoPlayer.STATE_BUFFERING && allRenderersReadyOrEnded
&& haveSufficientBuffer() && timeline.isReady(internalPositionUs)) { && haveSufficientBuffer() && timelineIsReady) {
setState(ExoPlayer.STATE_READY); setState(ExoPlayer.STATE_READY);
if (playWhenReady) { if (playWhenReady) {
startRenderers(); startRenderers();
} }
} else if (state == ExoPlayer.STATE_READY && (!allRenderersReadyOrEnded } else if (state == ExoPlayer.STATE_READY && (!allRenderersReadyOrEnded || !timelineIsReady)) {
|| !timeline.isReady(internalPositionUs))) {
rebuffering = playWhenReady; rebuffering = playWhenReady;
setState(ExoPlayer.STATE_BUFFERING); setState(ExoPlayer.STATE_BUFFERING);
stopRenderers(); stopRenderers();
@ -485,9 +485,8 @@ import java.util.concurrent.atomic.AtomicInteger;
if (allRenderersEnded && (durationUs == C.UNSET_TIME_US || durationUs <= positionUs)) { if (allRenderersEnded && (durationUs == C.UNSET_TIME_US || durationUs <= positionUs)) {
setState(ExoPlayer.STATE_ENDED); setState(ExoPlayer.STATE_ENDED);
} else { } else {
setState(allRenderersReadyOrEnded && haveSufficientBuffer() setState(allRenderersReadyOrEnded && haveSufficientBuffer() && timeline.isReady()
&& timeline.isReady(internalPositionUs) ? ExoPlayer.STATE_READY ? ExoPlayer.STATE_READY : ExoPlayer.STATE_BUFFERING);
: ExoPlayer.STATE_BUFFERING);
} }
// Start the renderers if ready, and schedule the first piece of work. // Start the renderers if ready, and schedule the first piece of work.
@ -596,7 +595,7 @@ import java.util.concurrent.atomic.AtomicInteger;
private Source bufferingSource; private Source bufferingSource;
private long playingSourceEndPositionUs; private long playingSourceEndPositionUs;
private long nextSourceOffsetUs; private long bufferingSourceOffsetUs;
public Timeline() { public Timeline() {
rendererWasEnabledFlags = new boolean[renderers.length]; rendererWasEnabledFlags = new boolean[renderers.length];
@ -620,6 +619,7 @@ import java.util.concurrent.atomic.AtomicInteger;
Source newSource = new Source(sampleSource, index, renderers.length); Source newSource = new Source(sampleSource, index, renderers.length);
if (bufferingSource != null) { if (bufferingSource != null) {
bufferingSource.nextSource = newSource; bufferingSource.nextSource = newSource;
bufferingSourceOffsetUs += bufferingSource.sampleSource.getDurationUs();
} }
bufferingSource = newSource; bufferingSource = newSource;
} }
@ -637,23 +637,14 @@ import java.util.concurrent.atomic.AtomicInteger;
bufferingSource.selectTracks(result.first, result.second, startPositionUs); bufferingSource.selectTracks(result.first, result.second, startPositionUs);
if (playingSource == null) { if (playingSource == null) {
// This is the first prepared source, so start playing it. // This is the first prepared source, so start playing it.
sourceOffsetUs = 0; setPlayingSource(bufferingSource, 0);
setPlayingSource(bufferingSource);
} }
} }
} }
if (bufferingSource.hasEnabledTracks) { if (bufferingSource.hasEnabledTracks) {
long bufferingPositionUs; long sourcePositionUs = internalPositionUs - bufferingSourceOffsetUs;
if (bufferingSource == playingSource) { bufferingSource.sampleSource.continueBuffering(sourcePositionUs);
bufferingPositionUs = internalPositionUs - sourceOffsetUs;
} else if (bufferingSource == readingSource) {
// TODO[playlists]: Make sure continueBuffering supports a negative downstream position.
bufferingPositionUs = internalPositionUs - nextSourceOffsetUs;
} else {
bufferingPositionUs = 0;
}
bufferingSource.sampleSource.continueBuffering(bufferingPositionUs);
} }
} }
@ -669,22 +660,7 @@ import java.util.concurrent.atomic.AtomicInteger;
} }
} }
if (playingSourceEndPositionUs == C.UNSET_TIME_US) { if (playingSourceEndPositionUs == C.UNSET_TIME_US) {
// Calculate the next source's start position in the timeline. playingSourceEndPositionUs = sourceOffsetUs + playingSource.sampleSource.getDurationUs();
long playingSourceDurationUs = playingSource.sampleSource.getDurationUs();
if (playingSourceDurationUs == C.UNSET_TIME_US) {
// The duration of the current source is unknown, so use the maximum rendered timestamp
// plus a small extra offset to make sure that renderers don't read two buffers with the
// same timestamp.
playingSourceEndPositionUs = 0;
for (TrackRenderer renderer : enabledRenderers) {
playingSourceEndPositionUs =
Math.max(playingSourceEndPositionUs, renderer.getMaximumTimeUs());
}
nextSourceOffsetUs = playingSourceEndPositionUs + 10000;
} else {
playingSourceEndPositionUs = sourceOffsetUs + playingSourceDurationUs;
nextSourceOffsetUs = playingSourceEndPositionUs;
}
} }
if (sourceCount != SampleSourceProvider.UNKNOWN_SOURCE_COUNT if (sourceCount != SampleSourceProvider.UNKNOWN_SOURCE_COUNT
&& readingSource.index == sourceCount - 1) { && readingSource.index == sourceCount - 1) {
@ -694,9 +670,7 @@ import java.util.concurrent.atomic.AtomicInteger;
} }
readingSource = null; readingSource = null;
playingSourceEndPositionUs = C.UNSET_TIME_US; playingSourceEndPositionUs = C.UNSET_TIME_US;
return; } else if (playingSource.nextSource != null && playingSource.nextSource.prepared) {
}
if (playingSource.nextSource != null && playingSource.nextSource.prepared) {
readingSource = playingSource.nextSource; readingSource = playingSource.nextSource;
// Suppress reading a reset so that the transition can be seamless. // Suppress reading a reset so that the transition can be seamless.
readingSource.sampleSource.readReset(); readingSource.sampleSource.readReset();
@ -713,27 +687,26 @@ import java.util.concurrent.atomic.AtomicInteger;
for (int j = 0; j < formats.length; j++) { for (int j = 0; j < formats.length; j++) {
formats[j] = groups.get(selection.group).getFormat(selection.getTrack(j)); formats[j] = groups.get(selection.group).getFormat(selection.getTrack(j));
} }
renderer.replaceTrackStream(formats, readingSource.trackStreams[i], nextSourceOffsetUs); renderer.replaceTrackStream(formats, readingSource.trackStreams[i],
playingSourceEndPositionUs);
} }
} }
} }
} }
public boolean isReady(long positionUs) { public boolean isReady() {
return playingSourceEndPositionUs == C.UNSET_TIME_US return playingSourceEndPositionUs == C.UNSET_TIME_US
|| positionUs < playingSourceEndPositionUs || playingSource.nextSource != null; || internalPositionUs < playingSourceEndPositionUs || playingSource.nextSource != null;
} }
public SampleSource getSampleSource(long positionUs) throws ExoPlaybackException { public SampleSource getSampleSource() throws ExoPlaybackException {
if (playingSource == null) { if (playingSource == null) {
return null; return null;
} }
if (readingSource != playingSource && playingSourceEndPositionUs != C.UNSET_TIME_US if (readingSource != playingSource && playingSourceEndPositionUs != C.UNSET_TIME_US
&& positionUs >= playingSourceEndPositionUs) { && internalPositionUs >= playingSourceEndPositionUs) {
// Renderers are playing the next source, so update the timeline.
playingSource.release(); playingSource.release();
sourceOffsetUs = nextSourceOffsetUs; setPlayingSource(readingSource, playingSourceEndPositionUs);
setPlayingSource(readingSource);
} }
return playingSource.sampleSource; return playingSource.sampleSource;
} }
@ -753,8 +726,9 @@ import java.util.concurrent.atomic.AtomicInteger;
if (newPlayingSource != null) { if (newPlayingSource != null) {
nextSourceIndex = sourceIndex + 1; nextSourceIndex = sourceIndex + 1;
newPlayingSource.nextSource = null; newPlayingSource.nextSource = null;
setPlayingSource(newPlayingSource); setPlayingSource(newPlayingSource, sourceOffsetUs);
bufferingSource = playingSource; bufferingSource = playingSource;
bufferingSourceOffsetUs = sourceOffsetUs;
if (playingSource.hasEnabledTracks) { if (playingSource.hasEnabledTracks) {
sampleSource.seekToUs(sourcePositionUs); sampleSource.seekToUs(sourcePositionUs);
} }
@ -762,6 +736,7 @@ import java.util.concurrent.atomic.AtomicInteger;
playingSource = null; playingSource = null;
readingSource = null; readingSource = null;
bufferingSource = null; bufferingSource = null;
bufferingSourceOffsetUs = 0;
durationUs = C.UNSET_TIME_US; durationUs = C.UNSET_TIME_US;
sampleSource = null; sampleSource = null;
// Set the next source index so that the required source is created in updateSources. // Set the next source index so that the required source is created in updateSources.
@ -803,6 +778,7 @@ import java.util.concurrent.atomic.AtomicInteger;
bufferingSource = playingSource; bufferingSource = playingSource;
nextSourceIndex = playingSource.index + 1; nextSourceIndex = playingSource.index + 1;
playingSourceEndPositionUs = C.UNSET_TIME_US; playingSourceEndPositionUs = C.UNSET_TIME_US;
bufferingSourceOffsetUs = sourceOffsetUs;
// Update the track selection for the playing source. // Update the track selection for the playing source.
Pair<TrackSelectionArray, Object> result = Pair<TrackSelectionArray, Object> result =
@ -842,10 +818,10 @@ import java.util.concurrent.atomic.AtomicInteger;
readingSource = null; readingSource = null;
bufferingSource = null; bufferingSource = null;
durationUs = C.UNSET_TIME_US; durationUs = C.UNSET_TIME_US;
playingSourceEndPositionUs = C.UNSET_TIME_US;
nextSourceIndex = 0; nextSourceIndex = 0;
sourceOffsetUs = 0; sourceOffsetUs = 0;
playingSourceEndPositionUs = C.UNSET_TIME_US; bufferingSourceOffsetUs = 0;
nextSourceOffsetUs = 0;
} }
@Override @Override
@ -864,8 +840,8 @@ import java.util.concurrent.atomic.AtomicInteger;
return sb.toString(); return sb.toString();
} }
private void setPlayingSource(Source source) throws ExoPlaybackException { private void setPlayingSource(Source source, long offsetUs) throws ExoPlaybackException {
playingSourceEndPositionUs = C.UNSET_TIME_US; sourceOffsetUs = offsetUs;
durationUs = source.sampleSource.getDurationUs(); durationUs = source.sampleSource.getDurationUs();
// Disable/enable renderers for the new source. // Disable/enable renderers for the new source.
@ -875,6 +851,7 @@ import java.util.concurrent.atomic.AtomicInteger;
} }
readingSource = source; readingSource = source;
playingSource = source; playingSource = source;
playingSourceEndPositionUs = C.UNSET_TIME_US;
enableRenderers(source.trackSelections, enabledRendererCount); enableRenderers(source.trackSelections, enabledRendererCount);
// Update the timeline position for the new source index. // Update the timeline position for the new source index.

View File

@ -37,7 +37,10 @@ public interface SampleSource {
boolean prepare(long positionUs) throws IOException; boolean prepare(long positionUs) throws IOException;
/** /**
* Returns the duration of the source. * Returns the duration of the source in microseconds, or {@link C#UNSET_TIME_US} if not known.
* <p>
* If {@link #getBufferedPositionUs()} returns {@link C#END_OF_SOURCE_US}, the duration is
* guaranteed to be known.
* <p> * <p>
* This method should only be called after the source has been prepared. * This method should only be called after the source has been prepared.
* *

View File

@ -109,7 +109,6 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
private int state; private int state;
private TrackStream stream; private TrackStream stream;
private long streamOffsetUs; private long streamOffsetUs;
private long maximumTimeUs;
private boolean readEndOfStream; private boolean readEndOfStream;
private boolean streamIsFinal; private boolean streamIsFinal;
@ -235,7 +234,6 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
*/ */
/* package */ final void reset(long positionUs) throws ExoPlaybackException { /* package */ final void reset(long positionUs) throws ExoPlaybackException {
streamIsFinal = false; streamIsFinal = false;
maximumTimeUs = C.UNSET_TIME_US;
onReset(positionUs, false); onReset(positionUs, false);
} }
@ -260,14 +258,6 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
return readEndOfStream; return readEndOfStream;
} }
/**
* Returns the maximum buffer timestamp read from the stream since the last reset, or
* {@link C#UNSET_TIME_US} if no buffers have been read.
*/
/* package */ final long getMaximumTimeUs() {
return maximumTimeUs;
}
/** /**
* Signals to the renderer that the current {@link TrackStream} will be the final one supplied * Signals to the renderer that the current {@link TrackStream} will be the final one supplied
* before it is next disabled or reset. * before it is next disabled or reset.
@ -372,9 +362,6 @@ public abstract class TrackRenderer implements ExoPlayerComponent {
return streamIsFinal ? TrackStream.BUFFER_READ : TrackStream.NOTHING_READ; return streamIsFinal ? TrackStream.BUFFER_READ : TrackStream.NOTHING_READ;
} }
buffer.timeUs += streamOffsetUs; buffer.timeUs += streamOffsetUs;
if (buffer.timeUs > maximumTimeUs) {
maximumTimeUs = buffer.timeUs;
}
} }
return result; return result;
} }

View File

@ -111,6 +111,12 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
private static final int MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA = -1; private static final int MIN_RETRY_COUNT_DEFAULT_FOR_MEDIA = -1;
/**
* When the source's duration is unknown, it is calculated by adding this value to the largest
* sample timestamp seen when buffering completes.
*/
private static final long DEFAULT_LAST_SAMPLE_DURATION_US = 10000;
// Lazily initialized default extractor classes in priority order. // Lazily initialized default extractor classes in priority order.
private static List<Class<? extends Extractor>> defaultExtractorClasses; private static List<Class<? extends Extractor>> defaultExtractorClasses;
@ -397,11 +403,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
} else if (isPendingReset()) { } else if (isPendingReset()) {
return pendingResetPositionUs; return pendingResetPositionUs;
} else { } else {
long largestQueuedTimestampUs = Long.MIN_VALUE; long largestQueuedTimestampUs = getLargestQueuedTimestampUs();
for (DefaultTrackOutput sampleQueue : sampleQueues) {
largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs,
sampleQueue.getLargestQueuedTimestampUs());
}
return largestQueuedTimestampUs == Long.MIN_VALUE ? lastSeekPositionUs return largestQueuedTimestampUs == Long.MIN_VALUE ? lastSeekPositionUs
: largestQueuedTimestampUs; : largestQueuedTimestampUs;
} }
@ -459,6 +461,11 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
long loadDurationMs) { long loadDurationMs) {
copyLengthFromLoader(loadable); copyLengthFromLoader(loadable);
loadingFinished = true; loadingFinished = true;
if (durationUs == C.UNSET_TIME_US) {
long largestQueuedTimestampUs = getLargestQueuedTimestampUs();
durationUs = largestQueuedTimestampUs == Long.MIN_VALUE ? 0
: largestQueuedTimestampUs + DEFAULT_LAST_SAMPLE_DURATION_US;
}
} }
@Override @Override
@ -579,6 +586,15 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu
return extractedSamplesCount; return extractedSamplesCount;
} }
private long getLargestQueuedTimestampUs() {
long largestQueuedTimestampUs = Long.MIN_VALUE;
for (DefaultTrackOutput sampleQueue : sampleQueues) {
largestQueuedTimestampUs = Math.max(largestQueuedTimestampUs,
sampleQueue.getLargestQueuedTimestampUs());
}
return largestQueuedTimestampUs;
}
private boolean haveFormatsForAllTracks() { private boolean haveFormatsForAllTracks() {
for (DefaultTrackOutput sampleQueue : sampleQueues) { for (DefaultTrackOutput sampleQueue : sampleQueues) {
if (sampleQueue.getUpstreamFormat() == null) { if (sampleQueue.getUpstreamFormat() == null) {