From d804446b343401f6487f62777c9a23173f79309b Mon Sep 17 00:00:00 2001 From: olly Date: Thu, 21 Jan 2016 13:58:01 -0800 Subject: [PATCH] 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 --- .../ext/opus/LibopusAudioTrackRenderer.java | 38 +---- .../ext/vp9/LibvpxVideoTrackRenderer.java | 41 ++---- .../exoplayer/FrameworkSampleSource.java | 25 ++-- .../MediaCodecAudioTrackRenderer.java | 20 +-- .../exoplayer/MediaCodecTrackRenderer.java | 41 ++---- .../MediaCodecVideoTrackRenderer.java | 6 +- .../android/exoplayer/SampleSource.java | 30 ++-- .../exoplayer/SampleSourceTrackRenderer.java | 132 ++++++++++++++---- .../android/exoplayer/SingleSampleSource.java | 11 +- .../exoplayer/chunk/ChunkSampleSource.java | 22 +-- .../extractor/ExtractorSampleSource.java | 16 ++- .../exoplayer/hls/HlsSampleSource.java | 22 +-- .../metadata/MetadataTrackRenderer.java | 20 +-- .../exoplayer/text/TextTrackRenderer.java | 18 +-- .../text/eia608/Eia608TrackRenderer.java | 14 +- 15 files changed, 224 insertions(+), 232 deletions(-) diff --git a/extensions/opus/src/main/java/com/google/android/exoplayer/ext/opus/LibopusAudioTrackRenderer.java b/extensions/opus/src/main/java/com/google/android/exoplayer/ext/opus/LibopusAudioTrackRenderer.java index f444e9b2d9..acffce3f97 100644 --- a/extensions/opus/src/main/java/com/google/android/exoplayer/ext/opus/LibopusAudioTrackRenderer.java +++ b/extensions/opus/src/main/java/com/google/android/exoplayer/ext/opus/LibopusAudioTrackRenderer.java @@ -145,19 +145,11 @@ public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer } @Override - protected void onEnabled(int track, long positionUs, boolean joining) + protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady) throws ExoPlaybackException { - super.onEnabled(track, positionUs, joining); - seekToInternal(positionUs); - } - - @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { if (outputStreamEnded) { return; } - sourceIsReady = continueBufferingSource(positionUs); - checkForDiscontinuity(positionUs); // Try and read a format if we don't have one already. 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) { return false; } - if (result == SampleSource.DISCONTINUITY_READ) { - flushDecoder(); - return true; - } if (result == SampleSource.FORMAT_READ) { format = formatHolder.format; return true; @@ -304,16 +292,6 @@ public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer 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() { inputBuffer = null; outputBuffer = null; @@ -344,18 +322,16 @@ public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer } @Override - protected void seekTo(long positionUs) throws ExoPlaybackException { - super.seekTo(positionUs); - seekToInternal(positionUs); - } - - private void seekToInternal(long positionUs) { + protected void onDiscontinuity(long positionUs) { audioTrack.reset(); currentPositionUs = positionUs; allowPositionDiscontinuity = true; inputStreamEnded = false; outputStreamEnded = false; sourceIsReady = false; + if (decoder != null) { + flushDecoder(); + } } @Override @@ -387,7 +363,7 @@ public final class LibopusAudioTrackRenderer extends SampleSourceTrackRenderer } private boolean readFormat(long positionUs) { - int result = readSource(positionUs, formatHolder, null, false); + int result = readSource(positionUs, formatHolder, null); if (result == SampleSource.FORMAT_READ) { format = formatHolder.format; audioTrack.configure(format.getFrameworkMediaFormatV16(), false); diff --git a/extensions/vp9/src/main/java/com/google/android/exoplayer/ext/vp9/LibvpxVideoTrackRenderer.java b/extensions/vp9/src/main/java/com/google/android/exoplayer/ext/vp9/LibvpxVideoTrackRenderer.java index fbd64f9940..f20f877c5a 100644 --- a/extensions/vp9/src/main/java/com/google/android/exoplayer/ext/vp9/LibvpxVideoTrackRenderer.java +++ b/extensions/vp9/src/main/java/com/google/android/exoplayer/ext/vp9/LibvpxVideoTrackRenderer.java @@ -175,12 +175,11 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer { } @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { + protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady) + throws ExoPlaybackException { if (outputStreamEnded) { return; } - sourceIsReady = continueBufferingSource(positionUs); - checkForDiscontinuity(positionUs); // Try and read a format if we don't have one already. if (format == null && !readFormat(positionUs)) { @@ -311,15 +310,10 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer { } } - int result = readSource(positionUs, formatHolder, inputBuffer.sampleHolder, - false); + int result = readSource(positionUs, formatHolder, inputBuffer.sampleHolder); if (result == SampleSource.NOTHING_READ) { return false; } - if (result == SampleSource.DISCONTINUITY_READ) { - flushDecoder(); - return true; - } if (result == SampleSource.FORMAT_READ) { format = formatHolder.format; return true; @@ -339,16 +333,6 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer { 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() { inputBuffer = null; if (outputBuffer != null) { @@ -369,23 +353,14 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer { } @Override - protected void seekTo(long positionUs) throws ExoPlaybackException { - 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() { + protected void onDiscontinuity(long positionUs) { sourceIsReady = false; inputStreamEnded = false; outputStreamEnded = false; renderedFirstFrame = false; + if (decoder != null) { + flushDecoder(); + } } @Override @@ -416,7 +391,7 @@ public final class LibvpxVideoTrackRenderer extends SampleSourceTrackRenderer { } private boolean readFormat(long positionUs) { - int result = readSource(positionUs, formatHolder, null, false); + int result = readSource(positionUs, formatHolder, null); if (result == SampleSource.FORMAT_READ) { format = formatHolder.format; return true; diff --git a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java index e5ea8f7863..eb54971a39 100644 --- a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java @@ -80,7 +80,8 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe private int[] trackStates; private boolean[] pendingDiscontinuities; - private long seekPositionUs; + private long lastSeekPositionUs; + private long pendingSeekPositionUs; /** * Instantiates a new sample extractor reading from the specified {@code uri}. @@ -184,16 +185,21 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe return true; } + @Override + public long readDiscontinuity(int track) { + if (pendingDiscontinuities[track]) { + pendingDiscontinuities[track] = false; + return lastSeekPositionUs; + } + return NO_DISCONTINUITY; + } + @Override public int readData(int track, long positionUs, MediaFormatHolder formatHolder, - SampleHolder sampleHolder, boolean onlyReadDiscontinuity) { + SampleHolder sampleHolder) { Assertions.checkState(prepared); Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED); if (pendingDiscontinuities[track]) { - pendingDiscontinuities[track] = false; - return DISCONTINUITY_READ; - } - if (onlyReadDiscontinuity) { return NOTHING_READ; } if (trackStates[track] != TRACK_STATE_FORMAT_SENT) { @@ -216,7 +222,7 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe if (sampleHolder.isEncrypted()) { sampleHolder.cryptoInfo.setFromExtractorV16(extractor); } - seekPositionUs = C.UNKNOWN_TIME_US; + pendingSeekPositionUs = C.UNKNOWN_TIME_US; extractor.advance(); return SAMPLE_READ; } else { @@ -285,8 +291,9 @@ public final class FrameworkSampleSource implements SampleSource, SampleSourceRe private void seekToUsInternal(long positionUs, boolean force) { // Unless forced, avoid duplicate calls to the underlying extractor's seek method in the case // that there have been no interleaving calls to readSample. - if (force || seekPositionUs != positionUs) { - seekPositionUs = positionUs; + if (force || pendingSeekPositionUs != positionUs) { + lastSeekPositionUs = positionUs; + pendingSeekPositionUs = positionUs; extractor.seekTo(positionUs, MediaExtractor.SEEK_TO_PREVIOUS_SYNC); for (int i = 0; i < trackStates.length; ++i) { if (trackStates[i] != TRACK_STATE_DISABLED) { diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java index 4bcc7a5f64..afe93e6a7a 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecAudioTrackRenderer.java @@ -239,13 +239,6 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem return this; } - @Override - protected void onEnabled(int track, long positionUs, boolean joining) - throws ExoPlaybackException { - super.onEnabled(track, positionUs, joining); - seekToInternal(positionUs); - } - @Override protected void onOutputFormatChanged(android.media.MediaFormat outputFormat) { boolean passthrough = passthroughMediaFormat != null; @@ -312,13 +305,8 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem } @Override - protected void seekTo(long positionUs) throws ExoPlaybackException { - super.seekTo(positionUs); - seekToInternal(positionUs); - } - - private void seekToInternal(long positionUs) { - // TODO: Try and re-use the same AudioTrack instance once [Internal: b/7941810] is fixed. + protected void onDiscontinuity(long positionUs) throws ExoPlaybackException { + super.onDiscontinuity(positionUs); audioTrack.reset(); currentPositionUs = positionUs; allowPositionDiscontinuity = true; @@ -376,7 +364,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem // If we are out of sync, allow currentPositionUs to jump backwards. if ((handleBufferResult & AudioTrack.RESULT_POSITION_DISCONTINUITY) != 0) { - handleDiscontinuity(); + handleAudioTrackDiscontinuity(); allowPositionDiscontinuity = true; } @@ -395,7 +383,7 @@ public class MediaCodecAudioTrackRenderer extends MediaCodecTrackRenderer implem audioTrack.handleEndOfStream(); } - protected void handleDiscontinuity() { + protected void handleAudioTrackDiscontinuity() { // Do nothing } diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java index df75064113..0f1d42e5e1 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecTrackRenderer.java @@ -262,13 +262,6 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer codecReinitializationState = REINITIALIZATION_STATE_NONE; } - @Override - protected void onEnabled(int track, long positionUs, boolean joining) - throws ExoPlaybackException { - super.onEnabled(track, positionUs, joining); - seekToInternal(); - } - @Override protected final boolean handlesTrack(MediaFormat mediaFormat) throws DecoderQueryException { return handlesTrack(mediaCodecSelector, mediaFormat); @@ -458,15 +451,13 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer } @Override - protected void seekTo(long positionUs) throws ExoPlaybackException { - super.seekTo(positionUs); - seekToInternal(); - } - - private void seekToInternal() { + protected void onDiscontinuity(long positionUs) throws ExoPlaybackException { sourceState = SOURCE_STATE_NOT_READY; inputStreamEnded = false; outputStreamEnded = false; + if (codec != null) { + flushCodec(); + } } @Override @@ -480,11 +471,11 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer } @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { - sourceState = continueBufferingSource(positionUs) + protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady) + throws ExoPlaybackException { + sourceState = sourceIsReady ? (sourceState == SOURCE_STATE_NOT_READY ? SOURCE_STATE_READY : sourceState) : SOURCE_STATE_NOT_READY; - checkForDiscontinuity(positionUs); if (format == null) { readFormat(positionUs); } @@ -501,22 +492,12 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer } 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) { 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 { codecHotswapTimeMs = -1; inputIndex = -1; @@ -598,7 +579,7 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer } 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) { sourceState = SOURCE_STATE_READY_READ_MAY_FAIL; } @@ -607,10 +588,6 @@ public abstract class MediaCodecTrackRenderer extends SampleSourceTrackRenderer if (result == SampleSource.NOTHING_READ) { return false; } - if (result == SampleSource.DISCONTINUITY_READ) { - flushCodec(); - return true; - } if (result == SampleSource.FORMAT_READ) { if (codecReconfigurationState == RECONFIGURATION_STATE_QUEUE_PENDING) { // We received two formats in a row. Clear the current buffer of any reconfiguration data diff --git a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java index 07affe94e3..cc3f114b7d 100644 --- a/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/MediaCodecVideoTrackRenderer.java @@ -227,8 +227,6 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { protected void onEnabled(int track, long positionUs, boolean joining) throws ExoPlaybackException { super.onEnabled(track, positionUs, joining); - renderedFirstFrame = false; - consecutiveDroppedFrameCount = 0; if (joining && allowedJoiningTimeUs > 0) { joiningDeadlineUs = SystemClock.elapsedRealtime() * 1000L + allowedJoiningTimeUs; } @@ -236,8 +234,8 @@ public class MediaCodecVideoTrackRenderer extends MediaCodecTrackRenderer { } @Override - protected void seekTo(long positionUs) throws ExoPlaybackException { - super.seekTo(positionUs); + protected void onDiscontinuity(long positionUs) throws ExoPlaybackException { + super.onDiscontinuity(positionUs); renderedFirstFrame = false; consecutiveDroppedFrameCount = 0; joiningDeadlineUs = -1; diff --git a/library/src/main/java/com/google/android/exoplayer/SampleSource.java b/library/src/main/java/com/google/android/exoplayer/SampleSource.java index 0243387c78..c122532d56 100644 --- a/library/src/main/java/com/google/android/exoplayer/SampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/SampleSource.java @@ -46,9 +46,9 @@ public interface SampleSource { */ 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 @@ -104,7 +104,7 @@ public interface SampleSource { * (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 * 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)}. *

* 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 - * {@link #readData(int, long, MediaFormatHolder, SampleHolder, boolean)}. + * {@link #readData(int, long, MediaFormatHolder, SampleHolder)}. *

* This method should only be called after the source has been prepared, and when the specified * track is disabled. @@ -138,13 +138,27 @@ public interface SampleSource { 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. + *

+ * 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. *

* This method should only be called when the specified track is enabled. *

* 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 * than the one for which data was requested. + *

+ * 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 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. * If the caller requires the sample data then it must ensure that {@link SampleHolder#data} * 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}, - * {@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, - SampleHolder sampleHolder, boolean onlyReadDiscontinuity); + SampleHolder sampleHolder); /** * Seeks to the specified time in microseconds. diff --git a/library/src/main/java/com/google/android/exoplayer/SampleSourceTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/SampleSourceTrackRenderer.java index 8673d774cd..08edae3bc3 100644 --- a/library/src/main/java/com/google/android/exoplayer/SampleSourceTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/SampleSourceTrackRenderer.java @@ -48,7 +48,7 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer { } @Override - protected boolean doPrepare(long positionUs) throws ExoPlaybackException { + protected final boolean doPrepare(long positionUs) throws ExoPlaybackException { boolean allSourcesPrepared = true; for (int i = 0; i < sources.length; i++) { allSourcesPrepared &= sources[i].prepare(positionUs); @@ -103,26 +103,30 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer { 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 protected void onEnabled(int track, long positionUs, boolean joining) throws ExoPlaybackException { + positionUs = shiftInputPosition(positionUs); enabledSource = sources[handledSourceIndices[track]]; enabledSourceTrackIndex = handledSourceTrackIndices[track]; enabledSource.enable(enabledSourceTrackIndex, positionUs); + onDiscontinuity(positionUs); } @Override - protected void seekTo(long positionUs) throws ExoPlaybackException { + protected final void seekTo(long positionUs) throws ExoPlaybackException { + positionUs = shiftInputPosition(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 @@ -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 protected void onDisabled() throws ExoPlaybackException { 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 protected final int getTrackCount() { return handledSourceTrackIndices.length; @@ -190,4 +176,92 @@ public abstract class SampleSourceTrackRenderer extends TrackRenderer { return source.getFormat(handledSourceTrackIndices[track]); } + /** + * Shifts positions passed to {@link #onEnabled(int, long, boolean)}, {@link #seekTo(long)} and + * {@link #doSomeWork(long, long)}. + *

+ * 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); + } + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java b/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java index 55fde5057b..ec7a8dcbb7 100644 --- a/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/SingleSampleSource.java @@ -119,12 +119,15 @@ public final class SingleSampleSource implements SampleSource, SampleSourceReade } } + @Override + public long readDiscontinuity(int track) { + return NO_DISCONTINUITY; + } + @Override public int readData(int track, long positionUs, MediaFormatHolder formatHolder, - SampleHolder sampleHolder, boolean onlyReadDiscontinuity) { - if (onlyReadDiscontinuity) { - return NOTHING_READ; - } else if (state == STATE_END_OF_STREAM) { + SampleHolder sampleHolder) { + if (state == STATE_END_OF_STREAM) { return END_OF_STREAM; } else if (state == STATE_SEND_FORMAT) { formatHolder.format = format; diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java index d9c9aa6cec..f7d4e12dfc 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkSampleSource.java @@ -222,22 +222,22 @@ public class ChunkSampleSource implements SampleSource, SampleSourceReader, Load return loadingFinished || !sampleQueue.isEmpty(); } + @Override + public long readDiscontinuity(int track) { + if (pendingDiscontinuity) { + pendingDiscontinuity = false; + return lastSeekPositionUs; + } + return NO_DISCONTINUITY; + } + @Override public int readData(int track, long positionUs, MediaFormatHolder formatHolder, - SampleHolder sampleHolder, boolean onlyReadDiscontinuity) { + SampleHolder sampleHolder) { Assertions.checkState(state == STATE_ENABLED); downstreamPositionUs = positionUs; - if (pendingDiscontinuity) { - pendingDiscontinuity = false; - return DISCONTINUITY_READ; - } - - if (onlyReadDiscontinuity) { - return NOTHING_READ; - } - - if (isPendingReset()) { + if (pendingDiscontinuity || isPendingReset()) { return NOTHING_READ; } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java index 2033b7b4bc..8b725597cc 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ExtractorSampleSource.java @@ -384,16 +384,20 @@ public final class ExtractorSampleSource implements SampleSource, SampleSourceRe } @Override - public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder, - SampleHolder sampleHolder, boolean onlyReadDiscontinuity) { - downstreamPositionUs = playbackPositionUs; - + public long readDiscontinuity(int track) { if (pendingDiscontinuities[track]) { 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; } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java index 32a46c9223..63b0740418 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/HlsSampleSource.java @@ -281,22 +281,22 @@ public final class HlsSampleSource implements SampleSource, SampleSourceReader, return false; } + @Override + public long readDiscontinuity(int track) { + if (pendingDiscontinuities[track]) { + pendingDiscontinuities[track] = false; + return lastSeekPositionUs; + } + return NO_DISCONTINUITY; + } + @Override public int readData(int track, long playbackPositionUs, MediaFormatHolder formatHolder, - SampleHolder sampleHolder, boolean onlyReadDiscontinuity) { + SampleHolder sampleHolder) { Assertions.checkState(prepared); downstreamPositionUs = playbackPositionUs; - if (pendingDiscontinuities[track]) { - pendingDiscontinuities[track] = false; - return DISCONTINUITY_READ; - } - - if (onlyReadDiscontinuity) { - return NOTHING_READ; - } - - if (isPendingReset()) { + if (pendingDiscontinuities[track] || isPendingReset()) { return NOTHING_READ; } diff --git a/library/src/main/java/com/google/android/exoplayer/metadata/MetadataTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/metadata/MetadataTrackRenderer.java index a59519a7e6..5bd3ffbf5f 100644 --- a/library/src/main/java/com/google/android/exoplayer/metadata/MetadataTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/metadata/MetadataTrackRenderer.java @@ -93,29 +93,17 @@ public final class MetadataTrackRenderer extends SampleSourceTrackRenderer im } @Override - protected void onEnabled(int track, long positionUs, boolean joining) - throws ExoPlaybackException { - super.onEnabled(track, positionUs, joining); - seekToInternal(); - } - - @Override - protected void seekTo(long positionUs) throws ExoPlaybackException { - super.seekTo(positionUs); - seekToInternal(); - } - - private void seekToInternal() { + protected void onDiscontinuity(long positionUs) { pendingMetadata = null; inputStreamEnded = false; } @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { - continueBufferingSource(positionUs); + protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady) + throws ExoPlaybackException { if (!inputStreamEnded && pendingMetadata == null) { sampleHolder.clearData(); - int result = readSource(positionUs, formatHolder, sampleHolder, false); + int result = readSource(positionUs, formatHolder, sampleHolder); if (result == SampleSource.SAMPLE_READ) { pendingMetadataTimestamp = sampleHolder.timeUs; try { diff --git a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java index da8f25efd9..865966ad53 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java @@ -185,26 +185,22 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement parserThread = new HandlerThread("textParser"); parserThread.start(); parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParsers[parserIndex]); - seekToInternal(); } @Override - protected void seekTo(long positionUs) throws ExoPlaybackException { - super.seekTo(positionUs); - seekToInternal(); - } - - private void seekToInternal() { + protected void onDiscontinuity(long positionUs) { inputStreamEnded = false; subtitle = null; nextSubtitle = null; - parserHelper.flush(); clearTextRenderer(); + if (parserHelper != null) { + parserHelper.flush(); + } } @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { - continueBufferingSource(positionUs); + protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady) + throws ExoPlaybackException { if (nextSubtitle == null) { try { nextSubtitle = parserHelper.getAndClearResult(); @@ -247,7 +243,7 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement // Try and read the next subtitle from the source. SampleHolder sampleHolder = parserHelper.getSampleHolder(); sampleHolder.clearData(); - int result = readSource(positionUs, formatHolder, sampleHolder, false); + int result = readSource(positionUs, formatHolder, sampleHolder); if (result == SampleSource.FORMAT_READ) { parserHelper.setFormat(formatHolder.format); } else if (result == SampleSource.SAMPLE_READ) { diff --git a/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.java index 316b660cd4..4004846621 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/text/eia608/Eia608TrackRenderer.java @@ -97,16 +97,10 @@ public final class Eia608TrackRenderer extends SampleSourceTrackRenderer impleme protected void onEnabled(int track, long positionUs, boolean joining) throws ExoPlaybackException { super.onEnabled(track, positionUs, joining); - seekToInternal(); } @Override - protected void seekTo(long positionUs) throws ExoPlaybackException { - super.seekTo(positionUs); - seekToInternal(); - } - - private void seekToInternal() { + protected void onDiscontinuity(long positionUs) { inputStreamEnded = false; pendingCaptionLists.clear(); clearPendingSample(); @@ -116,15 +110,15 @@ public final class Eia608TrackRenderer extends SampleSourceTrackRenderer impleme } @Override - protected void doSomeWork(long positionUs, long elapsedRealtimeUs) throws ExoPlaybackException { - continueBufferingSource(positionUs); + protected void doSomeWork(long positionUs, long elapsedRealtimeUs, boolean sourceIsReady) + throws ExoPlaybackException { if (isSamplePending()) { maybeParsePendingSample(positionUs); } int result = inputStreamEnded ? SampleSource.END_OF_STREAM : 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) { maybeParsePendingSample(positionUs); } else if (result == SampleSource.END_OF_STREAM) {