From 90b70818243bdd1a7758e50f08fcf445c6095d91 Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 22 Apr 2016 03:06:30 -0700 Subject: [PATCH] Propagate resets at the source rather than track level. - Code is simpler. We only ever reset all tracks. - Allows the standalone media clock to be updated properly. This allows simpler recovery for live streams in ExtractorSampleSource. - Fixes #1285 and paves the way for a fix for #758. Issue: #1285 Issue: #758 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=120530682 --- .../exoplayer/ExoPlayerImplInternal.java | 31 +++++++---- .../exoplayer/FrameworkSampleSource.java | 34 +++++-------- .../android/exoplayer/MultiSampleSource.java | 14 +++++ .../android/exoplayer/SampleSource.java | 8 +++ .../android/exoplayer/TrackRenderer.java | 15 ------ .../google/android/exoplayer/TrackStream.java | 11 ---- .../extractor/ExtractorSampleSource.java | 51 ++++++------------- .../exoplayer/hls/HlsSampleSource.java | 31 +++++------ 8 files changed, 81 insertions(+), 114 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java index 22f6d561c5..3086db0ab2 100644 --- a/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java +++ b/library/src/main/java/com/google/android/exoplayer/ExoPlayerImplInternal.java @@ -356,14 +356,12 @@ import java.util.concurrent.atomic.AtomicInteger; TraceUtil.beginSection("doSomeWork"); - // Process resets. - for (TrackRenderer renderer : enabledRenderers) { - renderer.checkForReset(); - } - - updatePositionUs(); - updateBufferedPositionUs(); if (enabledRenderers.length > 0) { + // Process reset if there is one, else update the position. + if (!checkForSourceResetInternal()) { + updatePositionUs(); + } + updateBufferedPositionUs(); source.continueBuffering(positionUs); } @@ -435,14 +433,12 @@ import java.util.concurrent.atomic.AtomicInteger; return; } - if (enabledRenderers != null) { + if (enabledRenderers.length > 0) { for (TrackRenderer renderer : enabledRenderers) { ensureStopped(renderer); } source.seekToUs(positionUs); - for (TrackRenderer renderer : enabledRenderers) { - renderer.checkForReset(); - } + checkForSourceResetInternal(); } resumeInternal(); @@ -474,6 +470,19 @@ import java.util.concurrent.atomic.AtomicInteger; handler.sendEmptyMessage(MSG_DO_SOME_WORK); } + private boolean checkForSourceResetInternal() throws ExoPlaybackException { + long resetPositionUs = source.readReset(); + if (resetPositionUs == C.UNSET_TIME_US) { + return false; + } + positionUs = resetPositionUs; + standaloneMediaClock.setPositionUs(resetPositionUs); + for (TrackRenderer renderer : enabledRenderers) { + renderer.reset(resetPositionUs); + } + return true; + } + private void stopInternal() { resetInternal(); setState(ExoPlayer.STATE_IDLE); 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 2cd83bf294..8b6fa3d2eb 100644 --- a/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/FrameworkSampleSource.java @@ -73,11 +73,11 @@ public final class FrameworkSampleSource implements SampleSource { private final long fileDescriptorLength; private boolean prepared; + private boolean notifyReset; private long durationUs; private MediaExtractor extractor; private TrackGroupArray tracks; private int[] trackStates; - private boolean[] pendingResets; private int enabledTrackCount; private long lastSeekPositionUs; @@ -135,7 +135,6 @@ public final class FrameworkSampleSource implements SampleSource { extractor.setDataSource(fileDescriptor, fileDescriptorOffset, fileDescriptorLength); } trackStates = new int[extractor.getTrackCount()]; - pendingResets = new boolean[trackStates.length]; TrackGroup[] trackArray = new TrackGroup[trackStates.length]; for (int i = 0; i < trackStates.length; i++) { MediaFormat format = extractor.getTrackFormat(i); @@ -170,7 +169,6 @@ public final class FrameworkSampleSource implements SampleSource { enabledTrackCount--; trackStates[track] = TRACK_STATE_DISABLED; extractor.unselectTrack(track); - pendingResets[track] = false; } // Select new tracks. TrackStream[] newStreams = new TrackStream[newSelections.size()]; @@ -197,6 +195,15 @@ public final class FrameworkSampleSource implements SampleSource { // MediaExtractor takes care of buffering. Do nothing. } + @Override + public long readReset() { + if (notifyReset) { + notifyReset = false; + return lastSeekPositionUs; + } + return C.UNSET_TIME_US; + } + @Override public void seekToUs(long positionUs) { if (enabledTrackCount == 0) { @@ -230,17 +237,9 @@ public final class FrameworkSampleSource implements SampleSource { // TrackStream methods. - /* package */ long readReset(int track) { - if (pendingResets[track]) { - pendingResets[track] = false; - return lastSeekPositionUs; - } - return C.UNSET_TIME_US; - } - /* package */ int readData(int track, FormatHolder formatHolder, DecoderInputBuffer buffer) { Assertions.checkState(trackStates[track] != TRACK_STATE_DISABLED); - if (pendingResets[track]) { + if (notifyReset) { return TrackStream.NOTHING_READ; } if (trackStates[track] != TRACK_STATE_FORMAT_SENT) { @@ -302,11 +301,7 @@ public final class FrameworkSampleSource implements SampleSource { 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) { - pendingResets[i] = true; - } - } + notifyReset = true; } } @@ -382,11 +377,6 @@ public final class FrameworkSampleSource implements SampleSource { // Do nothing. } - @Override - public long readReset() { - return FrameworkSampleSource.this.readReset(track); - } - @Override public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { return FrameworkSampleSource.this.readData(track, formatHolder, buffer); diff --git a/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java b/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java index 7631c284c8..4a3b1ff94e 100644 --- a/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/MultiSampleSource.java @@ -123,6 +123,20 @@ public final class MultiSampleSource implements SampleSource { } } + @Override + public long readReset() { + long resetPositionUs = C.UNSET_TIME_US; + for (SampleSource source : enabledSources) { + long childResetPositionUs = source.readReset(); + if (resetPositionUs == C.UNSET_TIME_US) { + resetPositionUs = childResetPositionUs; + } else if (childResetPositionUs != C.UNSET_TIME_US) { + resetPositionUs = Math.min(resetPositionUs, childResetPositionUs); + } + } + return resetPositionUs; + } + @Override public long getBufferedPositionUs() { long bufferedPositionUs = durationUs != C.UNSET_TIME_US ? durationUs : Long.MAX_VALUE; 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 68a4f49eab..72202b58aa 100644 --- a/library/src/main/java/com/google/android/exoplayer/SampleSource.java +++ b/library/src/main/java/com/google/android/exoplayer/SampleSource.java @@ -84,6 +84,14 @@ public interface SampleSource { */ void continueBuffering(long positionUs); + /** + * Attempts to read a pending reset. + * + * @return If a reset was read then the playback position in microseconds after the reset. Else + * {@link C#UNSET_TIME_US}. + */ + long readReset(); + /** * Returns an estimate of the position up to which data is buffered for the enabled tracks. *

diff --git a/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java index f1db1798eb..30c0c7edcd 100644 --- a/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/TrackRenderer.java @@ -215,21 +215,6 @@ public abstract class TrackRenderer implements ExoPlayerComponent { // Do nothing. } - /** - * Attempts to read and process a pending reset from the {@link TrackStream}. - *

- * This method may be called when the renderer is in the following states: - * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. - * - * @throws ExoPlaybackException If an error occurs. - */ - /* package */ final void checkForReset() throws ExoPlaybackException { - long resetPositionUs = stream.readReset(); - if (resetPositionUs != C.UNSET_TIME_US) { - reset(resetPositionUs); - } - } - /** * Stops the renderer. * diff --git a/library/src/main/java/com/google/android/exoplayer/TrackStream.java b/library/src/main/java/com/google/android/exoplayer/TrackStream.java index 1901105cfa..ef77bc4f95 100644 --- a/library/src/main/java/com/google/android/exoplayer/TrackStream.java +++ b/library/src/main/java/com/google/android/exoplayer/TrackStream.java @@ -53,19 +53,8 @@ public interface TrackStream { */ void maybeThrowError() throws IOException; - /** - * Attempts to read a pending reset. - * - * @return If a reset was read then the playback position in microseconds after the reset. Else - * {@link C#UNSET_TIME_US}. - */ - long readReset(); - /** * Attempts to read from the stream. - *

- * This method will always return {@link #NOTHING_READ} in the case that there's a pending - * discontinuity to be read from {@link #readReset} for the specified track. * * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format. * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the 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 e0d8187a8e..862adc05de 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 @@ -207,22 +207,18 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu private boolean prepared; private boolean seenFirstTrackSelection; + private boolean notifyReset; private int enabledTrackCount; private DefaultTrackOutput[] sampleQueues; private TrackGroupArray tracks; private long durationUs; private boolean[] pendingMediaFormat; - private boolean[] pendingResets; private boolean[] trackEnabledStates; private long downstreamPositionUs; private long lastSeekPositionUs; private long pendingResetPositionUs; - private boolean havePendingNextSampleUs; - private long pendingNextSampleUs; - private long sampleTimeOffsetUs; - private ExtractingLoadable loadable; private IOException fatalException; private int extractedSamplesCountAtStartOfLoad; @@ -336,7 +332,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu int trackCount = sampleQueues.length; TrackGroup[] trackArray = new TrackGroup[trackCount]; trackEnabledStates = new boolean[trackCount]; - pendingResets = new boolean[trackCount]; pendingMediaFormat = new boolean[trackCount]; durationUs = seekMap.getDurationUs(); for (int i = 0; i < trackCount; i++) { @@ -390,7 +385,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu enabledTrackCount++; trackEnabledStates[track] = true; pendingMediaFormat[track] = true; - pendingResets[track] = false; newStreams[i] = new TrackStreamImpl(track); } // Cancel or start requests as necessary. @@ -415,6 +409,15 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu discardSamplesForDisabledTracks(); } + @Override + public long readReset() { + if (notifyReset) { + notifyReset = false; + return lastSeekPositionUs; + } + return C.UNSET_TIME_US; + } + @Override public long getBufferedPositionUs() { if (loadingFinished) { @@ -458,16 +461,8 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu loader.maybeThrowError(); } - /* package */ long readReset(int track) { - if (pendingResets[track]) { - pendingResets[track] = false; - return lastSeekPositionUs; - } - return C.UNSET_TIME_US; - } - /* package */ int readData(int track, FormatHolder formatHolder, DecoderInputBuffer buffer) { - if (pendingResets[track] || isPendingReset()) { + if (notifyReset || isPendingReset()) { return TrackStream.NOTHING_READ; } @@ -483,12 +478,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu if (buffer.timeUs < lastSeekPositionUs) { buffer.addFlag(C.BUFFER_FLAG_DECODE_ONLY); } - if (havePendingNextSampleUs) { - // Set the offset to make the timestamp of this sample equal to pendingNextSampleUs. - sampleTimeOffsetUs = pendingNextSampleUs - buffer.timeUs; - havePendingNextSampleUs = false; - } - buffer.timeUs += sampleTimeOffsetUs; return TrackStream.BUFFER_READ; } @@ -524,10 +513,10 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu fatalException = e; return Loader.DONT_RETRY; } - configureRetry(); int extractedSamplesCount = getExtractedSamplesCount(); boolean madeProgress = extractedSamplesCount > extractedSamplesCountAtStartOfLoad; - extractedSamplesCountAtStartOfLoad = extractedSamplesCount; + configureRetry(); // May clear the sample queues. + extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount(); return madeProgress ? Loader.RETRY_RESET_ERROR_COUNT : Loader.RETRY; } @@ -563,7 +552,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu positionUs = seekMap.isSeekable() ? positionUs : 0; lastSeekPositionUs = positionUs; downstreamPositionUs = positionUs; - Arrays.fill(pendingResets, true); + notifyReset = true; // If we're not pending a reset, see if we can seek within the sample queues. boolean seekInsideBuffer = !isPendingReset(); for (int i = 0; seekInsideBuffer && i < sampleQueues.length; i++) { @@ -589,8 +578,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu } private void startLoading() { - sampleTimeOffsetUs = 0; - havePendingNextSampleUs = false; loadable = new ExtractingLoadable(uri, dataSource, extractorHolder, allocator, requestedBufferSize); if (prepared) { @@ -620,12 +607,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu // therefore that the data at the uri is a continuously shifting window of the latest // available media. For this case there's no way to continue loading from where a previous // load finished, so it's necessary to load from the start whenever commencing a new load. + notifyReset = true; clearSampleQueues(); loadable.setLoadPosition(0); - // To avoid introducing a discontinuity, we shift the sample timestamps so that they will - // continue from the current downstream position. - pendingNextSampleUs = downstreamPositionUs; - havePendingNextSampleUs = true; } else { // We're playing a seekable on-demand stream. Resume the current loadable, which will // request data starting from the point it left off. @@ -706,11 +690,6 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ExtractorSampleSource.this.maybeThrowError(); } - @Override - public long readReset() { - return ExtractorSampleSource.this.readReset(track); - } - @Override public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { return ExtractorSampleSource.this.readData(track, formatHolder, buffer); 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 4134820003..ce01eb4ed0 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 @@ -39,7 +39,6 @@ import android.os.Handler; import android.os.SystemClock; import java.io.IOException; -import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -69,6 +68,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { private boolean prepared; private boolean seenFirstTrackSelection; + private boolean notifyReset; private int enabledTrackCount; private DefaultTrackOutput[] sampleQueues; private Format downstreamFormat; @@ -79,7 +79,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { private int primaryTrackGroupIndex; // Indexed by group. private boolean[] groupEnabledStates; - private boolean[] pendingResets; private Format[] downstreamSampleFormats; private long downstreamPositionUs; @@ -204,7 +203,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { int[] tracks = selection.getTracks(); setTrackGroupEnabledState(group, true); downstreamSampleFormats[group] = null; - pendingResets[group] = false; if (group == primaryTrackGroupIndex) { primaryTracksDeselected |= chunkSource.selectTracks(tracks); } @@ -245,6 +243,15 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { } } + @Override + public long readReset() { + if (notifyReset) { + notifyReset = false; + return lastSeekPositionUs; + } + return C.UNSET_TIME_US; + } + @Override public long getBufferedPositionUs() { if (loadingFinished) { @@ -293,16 +300,8 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { chunkSource.maybeThrowError(); } - /* package */ long readReset(int group) { - if (pendingResets[group]) { - pendingResets[group] = false; - return lastSeekPositionUs; - } - return C.UNSET_TIME_US; - } - /* package */ int readData(int group, FormatHolder formatHolder, DecoderInputBuffer buffer) { - if (pendingResets[group] || isPendingReset()) { + if (notifyReset || isPendingReset()) { return TrackStream.NOTHING_READ; } @@ -464,7 +463,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { // Instantiate the necessary internal data-structures. primaryTrackGroupIndex = -1; groupEnabledStates = new boolean[extractorTrackCount]; - pendingResets = new boolean[extractorTrackCount]; downstreamSampleFormats = new Format[extractorTrackCount]; // Construct the set of exposed track groups. @@ -531,7 +529,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { positionUs = chunkSource.isLive() ? 0 : positionUs; lastSeekPositionUs = positionUs; downstreamPositionUs = positionUs; - Arrays.fill(pendingResets, true); + notifyReset = true; boolean seekInsideBuffer = !isPendingReset(); // TODO[REFACTOR]: This will nearly always fail to seek inside all buffers due to sparse tracks // such as ID3 (probably EIA608 too). We need a way to not care if we can't seek to the keyframe @@ -670,11 +668,6 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback { HlsSampleSource.this.maybeThrowError(); } - @Override - public long readReset() { - return HlsSampleSource.this.readReset(group); - } - @Override public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer) { return HlsSampleSource.this.readData(group, formatHolder, buffer);