Implement seeking via a single code path.

When a seek is performed, renderers currently perform the
actions that they need to take in two places: Some changes
are performed in seekTo implementations. Other changes are
performed when discontinuities are read from the source.

In HLS we need to perform what is effectively a seek
originating in the source. To support this, this CL allows
discontinuities read from the source to modify the playback
position. All actions that renderers perform as a result
of a seek are moved to be performed when a discontinuity is
received.

Best way to understand CL:
- Look at SampleSource interface change and then at the
  concrete implementations, to make sure they've been
  changed properly.
- Look at SampleSourceTrackRenderer change.
- Look at concrete renderers. The general pattern is that
  code previously performed in seekTo and READ_DISCONTINUITY
  is merged into onDiscontinuity().

Note: This will be further untangled in V2.

Issue #676
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=112720746
This commit is contained in:
olly 2016-01-21 13:58:01 -08:00 committed by Oliver Woodman
parent 3f0244e214
commit d804446b34
15 changed files with 224 additions and 232 deletions

View File

@ -145,19 +145,11 @@ public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer
} }
@Override @Override
protected void onEnabled(int track, long positionUs, boolean joining) protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
throws ExoPlaybackException { throws ExoPlaybackException {
super.onEnabled(track, positionUs, joining);
seekToInternal(positionUs);
}
@Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException {
if (outputStreamEnded) { if (outputStreamEnded) {
return; return;
} }
sourceIsReady = continueBufferingSource(positionUs);
checkForDiscontinuity(positionUs);
// Try and read a format if we don't have one already. // Try and read a format if we don't have one already.
if (format == null && !readFormat(positionUs)) { if (format == null && !readFormat(positionUs)) {
@ -275,14 +267,10 @@ public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer
} }
} }
int result = readSource(positionUs, formatHolder, inputBuffer.sampleHolder, false); int result = readSource(positionUs, formatHolder, inputBuffer.sampleHolder);
if (result == SampleSource.NOTHING_READ) { if (result == SampleSource.NOTHING_READ) {
return false; return false;
} }
if (result == SampleSource.DISCONTINUITY_READ) {
flushDecoder();
return true;
}
if (result == SampleSource.FORMAT_READ) { if (result == SampleSource.FORMAT_READ) {
format = formatHolder.format; format = formatHolder.format;
return true; return true;
@ -304,16 +292,6 @@ public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer
return true; return true;
} }
private void checkForDiscontinuity(long positionUs) {
if (decoder == null) {
return;
}
int result = readSource(positionUs, formatHolder, null, true);
if (result == SampleSource.DISCONTINUITY_READ) {
flushDecoder();
}
}
private void flushDecoder() { private void flushDecoder() {
inputBuffer = null; inputBuffer = null;
outputBuffer = null; outputBuffer = null;
@ -344,18 +322,16 @@ public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer
} }
@Override @Override
protected void seekTo(long positionUs) throws ExoPlaybackException { protected void onDiscontinuity(long positionUs) {
super.seekTo(positionUs);
seekToInternal(positionUs);
}
private void seekToInternal(long positionUs) {
audioTrack.reset(); audioTrack.reset();
currentPositionUs = positionUs; currentPositionUs = positionUs;
allowPositionDiscontinuity = true; allowPositionDiscontinuity = true;
inputStreamEnded = false; inputStreamEnded = false;
outputStreamEnded = false; outputStreamEnded = false;
sourceIsReady = false; sourceIsReady = false;
if (decoder != null) {
flushDecoder();
}
} }
@Override @Override
@ -387,7 +363,7 @@ public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer
} }
private boolean readFormat(long positionUs) { private boolean readFormat(long positionUs) {
int result = readSource(positionUs, formatHolder, null, false); int result = readSource(positionUs, formatHolder, null);
if (result == SampleSource.FORMAT_READ) { if (result == SampleSource.FORMAT_READ) {
format = formatHolder.format; format = formatHolder.format;
audioTrack.configure(format.getFrameworkMediaFormatV16(), false); audioTrack.configure(format.getFrameworkMediaFormatV16(), false);

View File

@ -175,12 +175,11 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
} }
@Override @Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
throws ExoPlaybackException {
if (outputStreamEnded) { if (outputStreamEnded) {
return; return;
} }
sourceIsReady = continueBufferingSource(positionUs);
checkForDiscontinuity(positionUs);
// Try and read a format if we don't have one already. // Try and read a format if we don't have one already.
if (format == null && !readFormat(positionUs)) { if (format == null && !readFormat(positionUs)) {
@ -311,15 +310,10 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
} }
} }
int result = readSource(positionUs, formatHolder, inputBuffer.sampleHolder, int result = readSource(positionUs, formatHolder, inputBuffer.sampleHolder);
false);
if (result == SampleSource.NOTHING_READ) { if (result == SampleSource.NOTHING_READ) {
return false; return false;
} }
if (result == SampleSource.DISCONTINUITY_READ) {
flushDecoder();
return true;
}
if (result == SampleSource.FORMAT_READ) { if (result == SampleSource.FORMAT_READ) {
format = formatHolder.format; format = formatHolder.format;
return true; return true;
@ -339,16 +333,6 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
return true; return true;
} }
private void checkForDiscontinuity(long positionUs) {
if (decoder == null) {
return;
}
int result = readSource(positionUs, formatHolder, null, true);
if (result == SampleSource.DISCONTINUITY_READ) {
flushDecoder();
}
}
private void flushDecoder() { private void flushDecoder() {
inputBuffer = null; inputBuffer = null;
if (outputBuffer != null) { if (outputBuffer != null) {
@ -369,23 +353,14 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
} }
@Override @Override
protected void seekTo(long positionUs) throws ExoPlaybackException { protected void onDiscontinuity(long positionUs) {
super.seekTo(positionUs);
seekToInternal();
}
@Override
protected void onEnabled(int track, long positionUs, boolean joining)
throws ExoPlaybackException {
super.onEnabled(track, positionUs, joining);
seekToInternal();
}
private void seekToInternal() {
sourceIsReady = false; sourceIsReady = false;
inputStreamEnded = false; inputStreamEnded = false;
outputStreamEnded = false; outputStreamEnded = false;
renderedFirstFrame = false; renderedFirstFrame = false;
if (decoder != null) {
flushDecoder();
}
} }
@Override @Override
@ -416,7 +391,7 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer {
} }
private boolean readFormat(long positionUs) { private boolean readFormat(long positionUs) {
int result = readSource(positionUs, formatHolder, null, false); int result = readSource(positionUs, formatHolder, null);
if (result == SampleSource.FORMAT_READ) { if (result == SampleSource.FORMAT_READ) {
format = formatHolder.format; format = formatHolder.format;
return true; return true;

View File

@ -80,7 +80,8 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
private int[] trackStates; private int[] trackStates;
private boolean[] pendingDiscontinuities; private boolean[] pendingDiscontinuities;
private long seekPositionUs; private long lastSeekPositionUs;
private long pendingSeekPositionUs;
/** /**
* Instantiates a new sample extractor reading from the specified {@code uri}. * Instantiates a new sample extractor reading from the specified {@code uri}.
@ -184,16 +185,21 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
return true; return true;
} }
@Override
public long readDiscontinuity(int track) {
if (pendingDiscontinuities[track]) {
pendingDiscontinuities[track] = false;
return lastSeekPositionUs;
}
return NO_DISCONTINUITY;
}
@Override @Override
public int readData(int track, long positionUs, MediaFormatHolder formatHolder, public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) { SampleHolder sampleHolder) {
Assertions.checkState(prepared); Assertions.checkState(prepared);
Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED); Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED);
if (pendingDiscontinuities[track]) { if (pendingDiscontinuities[track]) {
pendingDiscontinuities[track] = false;
return DISCONTINUITY_READ;
}
if (onlyReadDiscontinuity) {
return NOTHING_READ; return NOTHING_READ;
} }
if (trackStates[track] != TRACK_STATE_FORMAT_SENT) { if (trackStates[track] != TRACK_STATE_FORMAT_SENT) {
@ -216,7 +222,7 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
if (sampleHolder.isEncrypted()) { if (sampleHolder.isEncrypted()) {
sampleHolder.cryptoInfo.setFromExtractorV16(extractor); sampleHolder.cryptoInfo.setFromExtractorV16(extractor);
} }
seekPositionUs = C.UNKNOWN_TIME_US; pendingSeekPositionUs = C.UNKNOWN_TIME_US;
extractor.advance(); extractor.advance();
return SAMPLE_READ; return SAMPLE_READ;
} else { } else {
@ -285,8 +291,9 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe
private void seekToUsInternal(long positionUs, boolean force) { private void seekToUsInternal(long positionUs, boolean force) {
// Unless forced, avoid duplicate calls to the underlying extractor's seek method in the case // Unless forced, avoid duplicate calls to the underlying extractor's seek method in the case
// that there have been no interleaving calls to readSample. // that there have been no interleaving calls to readSample.
if (force || seekPositionUs != positionUs) { if (force || pendingSeekPositionUs != positionUs) {
seekPositionUs = positionUs; lastSeekPositionUs = positionUs;
pendingSeekPositionUs = positionUs;
extractor.seekTo(positionUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); extractor.seekTo(positionUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC);
for (int i = 0; i < trackStates.length; ++i) { for (int i = 0; i < trackStates.length; ++i) {
if (trackStates[i] != TRACK_STATE_DISABLED) { if (trackStates[i] != TRACK_STATE_DISABLED) {

View File

@ -239,13 +239,6 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
return this; return this;
} }
@Override
protected void onEnabled(int track, long positionUs, boolean joining)
throws ExoPlaybackException {
super.onEnabled(track, positionUs, joining);
seekToInternal(positionUs);
}
@Override @Override
protected void onOutputFormatChanged(android.media.MediaFormat outputFormat) { protected void onOutputFormatChanged(android.media.MediaFormat outputFormat) {
boolean passthrough = passthroughMediaFormat != null; boolean passthrough = passthroughMediaFormat != null;
@ -312,13 +305,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
} }
@Override @Override
protected void seekTo(long positionUs) throws ExoPlaybackException { protected void onDiscontinuity(long positionUs) throws ExoPlaybackException {
super.seekTo(positionUs); super.onDiscontinuity(positionUs);
seekToInternal(positionUs);
}
private void seekToInternal(long positionUs) {
// TODO: Try and re-use the same AudioTrack instance once [Internal: b/7941810] is fixed.
audioTrack.reset(); audioTrack.reset();
currentPositionUs = positionUs; currentPositionUs = positionUs;
allowPositionDiscontinuity = true; allowPositionDiscontinuity = true;
@ -376,7 +364,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
// If we are out of sync, allow currentPositionUs to jump backwards. // If we are out of sync, allow currentPositionUs to jump backwards.
if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) { if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) {
handleDiscontinuity(); handleAudioTrackDiscontinuity();
allowPositionDiscontinuity = true; allowPositionDiscontinuity = true;
} }
@ -395,7 +383,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem
audioTrack.handleEndOfStream(); audioTrack.handleEndOfStream();
} }
protected void handleDiscontinuity() { protected void handleAudioTrackDiscontinuity() {
// Do nothing // Do nothing
} }

View File

@ -262,13 +262,6 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
codecReinitializationState = REINITIALIZATION_STATE_NONE; codecReinitializationState = REINITIALIZATION_STATE_NONE;
} }
@Override
protected void onEnabled(int track, long positionUs, boolean joining)
throws ExoPlaybackException {
super.onEnabled(track, positionUs, joining);
seekToInternal();
}
@Override @Override
protected final boolean handlesTrack(MediaFormat mediaFormat) throws DecoderQueryException { protected final boolean handlesTrack(MediaFormat mediaFormat) throws DecoderQueryException {
return handlesTrack(mediaCodecSelector, mediaFormat); return handlesTrack(mediaCodecSelector, mediaFormat);
@ -458,15 +451,13 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
} }
@Override @Override
protected void seekTo(long positionUs) throws ExoPlaybackException { protected void onDiscontinuity(long positionUs) throws ExoPlaybackException {
super.seekTo(positionUs);
seekToInternal();
}
private void seekToInternal() {
sourceState = SOURCE_STATE_NOT_READY; sourceState = SOURCE_STATE_NOT_READY;
inputStreamEnded = false; inputStreamEnded = false;
outputStreamEnded = false; outputStreamEnded = false;
if (codec != null) {
flushCodec();
}
} }
@Override @Override
@ -480,11 +471,11 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
} }
@Override @Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
sourceState = continueBufferingSource(positionUs) throws ExoPlaybackException {
sourceState = sourceIsReady
? (sourceState == SOURCE_STATE_NOT_READY ? SOURCE_STATE_READY : sourceState) ? (sourceState == SOURCE_STATE_NOT_READY ? SOURCE_STATE_READY : sourceState)
: SOURCE_STATE_NOT_READY; : SOURCE_STATE_NOT_READY;
checkForDiscontinuity(positionUs);
if (format == null) { if (format == null) {
readFormat(positionUs); readFormat(positionUs);
} }
@ -501,22 +492,12 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
} }
private void readFormat(long positionUs) throws ExoPlaybackException { private void readFormat(long positionUs) throws ExoPlaybackException {
int result = readSource(positionUs, formatHolder, sampleHolder, false); int result = readSource(positionUs, formatHolder, null);
if (result == SampleSource.FORMAT_READ) { if (result == SampleSource.FORMAT_READ) {
onInputFormatChanged(formatHolder); onInputFormatChanged(formatHolder);
} }
} }
private void checkForDiscontinuity(long positionUs) throws ExoPlaybackException {
if (codec == null) {
return;
}
int result = readSource(positionUs, formatHolder, sampleHolder, true);
if (result == SampleSource.DISCONTINUITY_READ) {
flushCodec();
}
}
private void flushCodec() throws ExoPlaybackException { private void flushCodec() throws ExoPlaybackException {
codecHotswapTimeMs = -1; codecHotswapTimeMs = -1;
inputIndex = -1; inputIndex = -1;
@ -598,7 +579,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
} }
codecReconfigurationState = RECONFIGURATION_STATE_QUEUE_PENDING; codecReconfigurationState = RECONFIGURATION_STATE_QUEUE_PENDING;
} }
result = readSource(positionUs, formatHolder, sampleHolder, false); result = readSource(positionUs, formatHolder, sampleHolder);
if (firstFeed && sourceState == SOURCE_STATE_READY && result == SampleSource.NOTHING_READ) { if (firstFeed && sourceState == SOURCE_STATE_READY && result == SampleSource.NOTHING_READ) {
sourceState = SOURCE_STATE_READY_READ_MAY_FAIL; sourceState = SOURCE_STATE_READY_READ_MAY_FAIL;
} }
@ -607,10 +588,6 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer
if (result == SampleSource.NOTHING_READ) { if (result == SampleSource.NOTHING_READ) {
return false; return false;
} }
if (result == SampleSource.DISCONTINUITY_READ) {
flushCodec();
return true;
}
if (result == SampleSource.FORMAT_READ) { if (result == SampleSource.FORMAT_READ) {
if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) { if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) {
// We received two formats in a row. Clear the current buffer of any reconfiguration data // We received two formats in a row. Clear the current buffer of any reconfiguration data

View File

@ -227,8 +227,6 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
protected void onEnabled(int track, long positionUs, boolean joining) protected void onEnabled(int track, long positionUs, boolean joining)
throws ExoPlaybackException { throws ExoPlaybackException {
super.onEnabled(track, positionUs, joining); super.onEnabled(track, positionUs, joining);
renderedFirstFrame = false;
consecutiveDroppedFrameCount = 0;
if (joining && allowedJoiningTimeUs > 0) { if (joining && allowedJoiningTimeUs > 0) {
joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs; joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs;
} }
@ -236,8 +234,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer {
} }
@Override @Override
protected void seekTo(long positionUs) throws ExoPlaybackException { protected void onDiscontinuity(long positionUs) throws ExoPlaybackException {
super.seekTo(positionUs); super.onDiscontinuity(positionUs);
renderedFirstFrame = false; renderedFirstFrame = false;
consecutiveDroppedFrameCount = 0; consecutiveDroppedFrameCount = 0;
joiningDeadlineUs = -1; joiningDeadlineUs = -1;

View File

@ -46,9 +46,9 @@ public interface SampleSource {
*/ */
public static final int FORMAT_READ = -4; public static final int FORMAT_READ = -4;
/** /**
* A discontinuity in the sample stream. * Returned from {@link SampleSourceReader#readDiscontinuity(int)} to indicate no discontinuity.
*/ */
public static final int DISCONTINUITY_READ = -5; public static final long NO_DISCONTINUITY = Long.MIN_VALUE;
/** /**
* A consumer of samples should call this method to register themselves and gain access to the * A consumer of samples should call this method to register themselves and gain access to the
@ -104,7 +104,7 @@ public interface SampleSource {
* (i.e. @link {@link MediaFormat#adaptive} is true). Hence the track formats returned through * (i.e. @link {@link MediaFormat#adaptive} is true). Hence the track formats returned through
* this method should not be used to configure decoders. Decoder configuration should be * this method should not be used to configure decoders. Decoder configuration should be
* performed using the formats obtained when reading the media stream through calls to * performed using the formats obtained when reading the media stream through calls to
* {@link #readData(int, long, MediaFormatHolder, SampleHolder, boolean)}. * {@link #readData(int, long, MediaFormatHolder, SampleHolder)}.
* <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.
* *
@ -115,7 +115,7 @@ public interface SampleSource {
/** /**
* Enable the specified track. This allows the track's format and samples to be read from * Enable the specified track. This allows the track's format and samples to be read from
* {@link #readData(int, long, MediaFormatHolder, SampleHolder, boolean)}. * {@link #readData(int, long, MediaFormatHolder, SampleHolder)}.
* <p> * <p>
* This method should only be called after the source has been prepared, and when the specified * This method should only be called after the source has been prepared, and when the specified
* track is disabled. * track is disabled.
@ -138,13 +138,27 @@ public interface SampleSource {
public boolean continueBuffering(int track, long positionUs); public boolean continueBuffering(int track, long positionUs);
/** /**
* Attempts to read either a sample, a new format or or a discontinuity from the source. * Attempts to read a pending discontinuity from the source.
* <p>
* This method should only be called when the specified track is enabled.
*
* @param track The track from which to read.
* @return If a discontinuity was read then the playback position after the discontinuity. Else
* {@link #NO_DISCONTINUITY}.
*/
public long readDiscontinuity(int track);
/**
* Attempts to read a sample or a new format from the source.
* <p> * <p>
* This method should only be called when the specified track is enabled. * This method should only be called when the specified track is enabled.
* <p> * <p>
* Note that where multiple tracks are enabled, {@link #NOTHING_READ} may be returned if the * Note that where multiple tracks are enabled, {@link #NOTHING_READ} may be returned if the
* next piece of data to be read from the {@link SampleSource} corresponds to a different track * next piece of data to be read from the {@link SampleSource} corresponds to a different track
* than the one for which data was requested. * than the one for which data was requested.
* <p>
* This method will always return {@link #NOTHING_READ} in the case that there's a pending
* discontinuity to be read from {@link #readDiscontinuity(int)} for the specified track.
* *
* @param track The track from which to read. * @param track The track from which to read.
* @param positionUs The current playback position. * @param positionUs The current playback position.
@ -153,13 +167,11 @@ public interface SampleSource {
* @param sampleHolder A {@link SampleHolder} object to populate in the case of a new sample. * @param sampleHolder A {@link SampleHolder} object to populate in the case of a new sample.
* If the caller requires the sample data then it must ensure that {@link SampleHolder#data} * If the caller requires the sample data then it must ensure that {@link SampleHolder#data}
* references a valid output buffer. * references a valid output buffer.
* @param onlyReadDiscontinuity Whether to only read a discontinuity. If true, only
* {@link #DISCONTINUITY_READ} or {@link #NOTHING_READ} can be returned.
* @return The result, which can be {@link #SAMPLE_READ}, {@link #FORMAT_READ}, * @return The result, which can be {@link #SAMPLE_READ}, {@link #FORMAT_READ},
* {@link #DISCONTINUITY_READ}, {@link #NOTHING_READ} or {@link #END_OF_STREAM}. * {@link #NOTHING_READ} or {@link #END_OF_STREAM}.
*/ */
public int readData(int track, long positionUs, MediaFormatHolder formatHolder, public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity); SampleHolder sampleHolder);
/** /**
* Seeks to the specified time in microseconds. * Seeks to the specified time in microseconds.

View File

@ -48,7 +48,7 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer {
} }
@Override @Override
protected boolean doPrepare(long positionUs) throws ExoPlaybackException { protected final boolean doPrepare(long positionUs) throws ExoPlaybackException {
boolean allSourcesPrepared = true; boolean allSourcesPrepared = true;
for (int i = 0; i < sources.length; i++) { for (int i = 0; i < sources.length; i++) {
allSourcesPrepared &= sources[i].prepare(positionUs); allSourcesPrepared &= sources[i].prepare(positionUs);
@ -103,26 +103,30 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer {
return true; return true;
} }
/**
* Returns whether this renderer is capable of handling the provided track.
*
* @param mediaFormat The format of the track.
* @return True if the renderer can handle the track, false otherwise.
* @throws DecoderQueryException Thrown if there was an error querying decoders.
*/
protected abstract boolean handlesTrack(MediaFormat mediaFormat) throws DecoderQueryException;
@Override @Override
protected void onEnabled(int track, long positionUs, boolean joining) protected void onEnabled(int track, long positionUs, boolean joining)
throws ExoPlaybackException { throws ExoPlaybackException {
positionUs = shiftInputPosition(positionUs);
enabledSource = sources[handledSourceIndices[track]]; enabledSource = sources[handledSourceIndices[track]];
enabledSourceTrackIndex = handledSourceTrackIndices[track]; enabledSourceTrackIndex = handledSourceTrackIndices[track];
enabledSource.enable(enabledSourceTrackIndex, positionUs); enabledSource.enable(enabledSourceTrackIndex, positionUs);
onDiscontinuity(positionUs);
} }
@Override @Override
protected void seekTo(long positionUs) throws ExoPlaybackException { protected final void seekTo(long positionUs) throws ExoPlaybackException {
positionUs = shiftInputPosition(positionUs);
enabledSource.seekToUs(positionUs); enabledSource.seekToUs(positionUs);
checkForDiscontinuity(positionUs);
}
@Override
protected final void doSomeWork(long positionUs, long elapsedRealtimeUs)
throws ExoPlaybackException {
positionUs = shiftInputPosition(positionUs);
boolean sourceIsReady = enabledSource.continueBuffering(enabledSourceTrackIndex, positionUs);
positionUs = checkForDiscontinuity(positionUs);
doSomeWork(positionUs, elapsedRealtimeUs, sourceIsReady);
} }
@Override @Override
@ -147,14 +151,6 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer {
} }
} }
private void maybeThrowError(SampleSourceReader source) throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
@Override @Override
protected void onDisabled() throws ExoPlaybackException { protected void onDisabled() throws ExoPlaybackException {
enabledSource.disable(enabledSourceTrackIndex); enabledSource.disable(enabledSourceTrackIndex);
@ -169,16 +165,6 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer {
} }
} }
protected final boolean continueBufferingSource(long positionUs) {
return enabledSource.continueBuffering(enabledSourceTrackIndex, positionUs);
}
protected final int readSource(long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) {
return enabledSource.readData(enabledSourceTrackIndex, positionUs, formatHolder, sampleHolder,
onlyReadDiscontinuity);
}
@Override @Override
protected final int getTrackCount() { protected final int getTrackCount() {
return handledSourceTrackIndices.length; return handledSourceTrackIndices.length;
@ -190,4 +176,92 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer {
return source.getFormat(handledSourceTrackIndices[track]); return source.getFormat(handledSourceTrackIndices[track]);
} }
/**
* Shifts positions passed to {@link #onEnabled(int, long, boolean)}, {@link #seekTo(long)} and
* {@link #doSomeWork(long, long)}.
* <p>
* The default implementation does not modify the position. Except in very specific cases,
* subclasses should not override this method.
*
* @param positionUs The position in microseconds.
* @return The adjusted position in microseconds.
*/
protected long shiftInputPosition(long positionUs) {
return positionUs;
}
// Methods to be called by subclasses.
/**
* Reads from the enabled upstream source.
*
* @param positionUs The current playback position.
* @param formatHolder A {@link MediaFormatHolder} object to populate in the case of a new format.
* @param sampleHolder A {@link SampleHolder} object to populate in the case of a new sample.
* If the caller requires the sample data then it must ensure that {@link SampleHolder#data}
* references a valid output buffer.
* @return The result, which can be {@link SampleSource#SAMPLE_READ},
* {@link SampleSource#FORMAT_READ}, {@link SampleSource#NOTHING_READ} or
* {@link SampleSource#END_OF_STREAM}.
*/
protected final int readSource(long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder) {
return enabledSource.readData(enabledSourceTrackIndex, positionUs, formatHolder, sampleHolder);
}
// Abstract methods.
/**
* Returns whether this renderer is capable of handling the provided track.
*
* @param mediaFormat The format of the track.
* @return True if the renderer can handle the track, false otherwise.
* @throws DecoderQueryException Thrown if there was an error querying decoders.
*/
protected abstract boolean handlesTrack(MediaFormat mediaFormat) throws DecoderQueryException;
/**
* Invoked when a discontinuity is encountered. Also invoked when the renderer is enabled, for
* convenience.
*
* @param positionUs The playback position after the discontinuity, or the position at which
* the renderer is being enabled.
* @throws ExoPlaybackException If an error occurs handling the discontinuity.
*/
protected abstract void onDiscontinuity(long positionUs) throws ExoPlaybackException;
/**
* Called by {@link #doSomeWork(long, long)}.
*
* @param positionUs The current media time in microseconds, measured at the start of the
* current iteration of the rendering loop.
* @param elapsedRealtimeUs {@link android.os.SystemClock#elapsedRealtime()} in microseconds,
* measured at the start of the current iteration of the rendering loop.
* @param sourceIsReady The result of the most recent call to
* {@link SampleSourceReader#continueBuffering(int, long)}.
* @throws ExoPlaybackException If an error occurs.
* @throws ExoPlaybackException
*/
protected abstract void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
throws ExoPlaybackException;
// Private methods.
private long checkForDiscontinuity(long positionUs) throws ExoPlaybackException {
long discontinuityPositionUs = enabledSource.readDiscontinuity(enabledSourceTrackIndex);
if (discontinuityPositionUs != SampleSource.NO_DISCONTINUITY) {
onDiscontinuity(discontinuityPositionUs);
return discontinuityPositionUs;
}
return positionUs;
}
private void maybeThrowError(SampleSourceReader source) throws ExoPlaybackException {
try {
source.maybeThrowError();
} catch (IOException e) {
throw new ExoPlaybackException(e);
}
}
} }

View File

@ -119,12 +119,15 @@ public final class SingleSampleSource implements SampleSource, SampleSourceReade
} }
} }
@Override
public long readDiscontinuity(int track) {
return NO_DISCONTINUITY;
}
@Override @Override
public int readData(int track, long positionUs, MediaFormatHolder formatHolder, public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) { SampleHolder sampleHolder) {
if (onlyReadDiscontinuity) { if (state == STATE_END_OF_STREAM) {
return NOTHING_READ;
} else if (state == STATE_END_OF_STREAM) {
return END_OF_STREAM; return END_OF_STREAM;
} else if (state == STATE_SEND_FORMAT) { } else if (state == STATE_SEND_FORMAT) {
formatHolder.format = format; formatHolder.format = format;

View File

@ -222,22 +222,22 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load
return loadingFinished || !sampleQueue.isEmpty(); return loadingFinished || !sampleQueue.isEmpty();
} }
@Override
public long readDiscontinuity(int track) {
if (pendingDiscontinuity) {
pendingDiscontinuity = false;
return lastSeekPositionUs;
}
return NO_DISCONTINUITY;
}
@Override @Override
public int readData(int track, long positionUs, MediaFormatHolder formatHolder, public int readData(int track, long positionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) { SampleHolder sampleHolder) {
Assertions.checkState(state == STATE_ENABLED); Assertions.checkState(state == STATE_ENABLED);
downstreamPositionUs = positionUs; downstreamPositionUs = positionUs;
if (pendingDiscontinuity) { if (pendingDiscontinuity || isPendingReset()) {
pendingDiscontinuity = false;
return DISCONTINUITY_READ;
}
if (onlyReadDiscontinuity) {
return NOTHING_READ;
}
if (isPendingReset()) {
return NOTHING_READ; return NOTHING_READ;
} }

View File

@ -384,16 +384,20 @@ public final class ExtractorSampleSource implements SampleSource, SampleSourceRe
} }
@Override @Override
public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder, public long readDiscontinuity(int track) {
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) {
downstreamPositionUs = playbackPositionUs;
if (pendingDiscontinuities[track]) { if (pendingDiscontinuities[track]) {
pendingDiscontinuities[track] = false; pendingDiscontinuities[track] = false;
return DISCONTINUITY_READ; return lastSeekPositionUs;
} }
return NO_DISCONTINUITY;
}
if (onlyReadDiscontinuity || isPendingReset()) { @Override
public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder) {
downstreamPositionUs = playbackPositionUs;
if (pendingDiscontinuities[track] || isPendingReset()) {
return NOTHING_READ; return NOTHING_READ;
} }

View File

@ -281,22 +281,22 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader,
return false; return false;
} }
@Override
public long readDiscontinuity(int track) {
if (pendingDiscontinuities[track]) {
pendingDiscontinuities[track] = false;
return lastSeekPositionUs;
}
return NO_DISCONTINUITY;
}
@Override @Override
public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder, public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder,
SampleHolder sampleHolder, boolean onlyReadDiscontinuity) { SampleHolder sampleHolder) {
Assertions.checkState(prepared); Assertions.checkState(prepared);
downstreamPositionUs = playbackPositionUs; downstreamPositionUs = playbackPositionUs;
if (pendingDiscontinuities[track]) { if (pendingDiscontinuities[track] || isPendingReset()) {
pendingDiscontinuities[track] = false;
return DISCONTINUITY_READ;
}
if (onlyReadDiscontinuity) {
return NOTHING_READ;
}
if (isPendingReset()) {
return NOTHING_READ; return NOTHING_READ;
} }

View File

@ -93,29 +93,17 @@ public final class MetadataTrackRenderer<T> extends SampleSourceTrackRenderer im
} }
@Override @Override
protected void onEnabled(int track, long positionUs, boolean joining) protected void onDiscontinuity(long positionUs) {
throws ExoPlaybackException {
super.onEnabled(track, positionUs, joining);
seekToInternal();
}
@Override
protected void seekTo(long positionUs) throws ExoPlaybackException {
super.seekTo(positionUs);
seekToInternal();
}
private void seekToInternal() {
pendingMetadata = null; pendingMetadata = null;
inputStreamEnded = false; inputStreamEnded = false;
} }
@Override @Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
continueBufferingSource(positionUs); throws ExoPlaybackException {
if (!inputStreamEnded && pendingMetadata == null) { if (!inputStreamEnded && pendingMetadata == null) {
sampleHolder.clearData(); sampleHolder.clearData();
int result = readSource(positionUs, formatHolder, sampleHolder, false); int result = readSource(positionUs, formatHolder, sampleHolder);
if (result == SampleSource.SAMPLE_READ) { if (result == SampleSource.SAMPLE_READ) {
pendingMetadataTimestamp = sampleHolder.timeUs; pendingMetadataTimestamp = sampleHolder.timeUs;
try { try {

View File

@ -185,26 +185,22 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
parserThread = new HandlerThread("textParser"); parserThread = new HandlerThread("textParser");
parserThread.start(); parserThread.start();
parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParsers[parserIndex]); parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParsers[parserIndex]);
seekToInternal();
} }
@Override @Override
protected void seekTo(long positionUs) throws ExoPlaybackException { protected void onDiscontinuity(long positionUs) {
super.seekTo(positionUs);
seekToInternal();
}
private void seekToInternal() {
inputStreamEnded = false; inputStreamEnded = false;
subtitle = null; subtitle = null;
nextSubtitle = null; nextSubtitle = null;
parserHelper.flush();
clearTextRenderer(); clearTextRenderer();
if (parserHelper != null) {
parserHelper.flush();
}
} }
@Override @Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
continueBufferingSource(positionUs); throws ExoPlaybackException {
if (nextSubtitle == null) { if (nextSubtitle == null) {
try { try {
nextSubtitle = parserHelper.getAndClearResult(); nextSubtitle = parserHelper.getAndClearResult();
@ -247,7 +243,7 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
// Try and read the next subtitle from the source. // Try and read the next subtitle from the source.
SampleHolder sampleHolder = parserHelper.getSampleHolder(); SampleHolder sampleHolder = parserHelper.getSampleHolder();
sampleHolder.clearData(); sampleHolder.clearData();
int result = readSource(positionUs, formatHolder, sampleHolder, false); int result = readSource(positionUs, formatHolder, sampleHolder);
if (result == SampleSource.FORMAT_READ) { if (result == SampleSource.FORMAT_READ) {
parserHelper.setFormat(formatHolder.format); parserHelper.setFormat(formatHolder.format);
} else if (result == SampleSource.SAMPLE_READ) { } else if (result == SampleSource.SAMPLE_READ) {

View File

@ -97,16 +97,10 @@ public final class Eia608TrackRenderer extends SampleSourceTrackRenderer impleme
protected void onEnabled(int track, long positionUs, boolean joining) protected void onEnabled(int track, long positionUs, boolean joining)
throws ExoPlaybackException { throws ExoPlaybackException {
super.onEnabled(track, positionUs, joining); super.onEnabled(track, positionUs, joining);
seekToInternal();
} }
@Override @Override
protected void seekTo(long positionUs) throws ExoPlaybackException { protected void onDiscontinuity(long positionUs) {
super.seekTo(positionUs);
seekToInternal();
}
private void seekToInternal() {
inputStreamEnded = false; inputStreamEnded = false;
pendingCaptionLists.clear(); pendingCaptionLists.clear();
clearPendingSample(); clearPendingSample();
@ -116,15 +110,15 @@ public final class Eia608TrackRenderer extends SampleSourceTrackRenderer impleme
} }
@Override @Override
protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady)
continueBufferingSource(positionUs); throws ExoPlaybackException {
if (isSamplePending()) { if (isSamplePending()) {
maybeParsePendingSample(positionUs); maybeParsePendingSample(positionUs);
} }
int result = inputStreamEnded ? SampleSource.END_OF_STREAM : SampleSource.SAMPLE_READ; int result = inputStreamEnded ? SampleSource.END_OF_STREAM : SampleSource.SAMPLE_READ;
while (!isSamplePending() && result == SampleSource.SAMPLE_READ) { while (!isSamplePending() && result == SampleSource.SAMPLE_READ) {
result = readSource(positionUs, formatHolder, sampleHolder, false); result = readSource(positionUs, formatHolder, sampleHolder);
if (result == SampleSource.SAMPLE_READ) { if (result == SampleSource.SAMPLE_READ) {
maybeParsePendingSample(positionUs); maybeParsePendingSample(positionUs);
} else if (result == SampleSource.END_OF_STREAM) { } else if (result == SampleSource.END_OF_STREAM) {