diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java index 9e85620dcd..42c5908619 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer2/ext/flac/FlacExtractor.java @@ -155,7 +155,7 @@ public final class FlacExtractor implements Extractor { } @Override - public void seek(long position) { + public void seek(long position, long timeUs) { if (position == 0) { metadataParsed = false; } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java index 4120110afb..38b0325cba 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/Extractor.java @@ -93,9 +93,10 @@ public interface Extractor { * position} in the stream. Valid random access positions are the start of the stream and * positions that can be obtained from any {@link SeekMap} passed to the {@link ExtractorOutput}. * - * @param position The seek position. + * @param position The byte offset in the stream from which data will be provided. + * @param timeUs The seek time in microseconds. */ - void seek(long position); + void seek(long position, long timeUs); /** * Releases all kept resources. diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java index 9c3721d8fe..5b396749ac 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/flv/FlvExtractor.java @@ -126,7 +126,7 @@ public final class FlvExtractor implements Extractor, SeekMap { } @Override - public void seek(long position) { + public void seek(long position, long timeUs) { parserState = STATE_READING_FLV_HEADER; bytesToNextTagHeader = 0; } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java index c1a2599a12..ccf78e6bc6 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mkv/MatroskaExtractor.java @@ -318,7 +318,7 @@ public final class MatroskaExtractor implements Extractor { } @Override - public void seek(long position) { + public void seek(long position, long timeUs) { clusterTimecodeUs = C.TIME_UNSET; blockState = BLOCK_STATE_START; reader.reset(); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java index 02b92f2077..9bdefeceaf 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp3/Mp3Extractor.java @@ -123,7 +123,7 @@ public final class Mp3Extractor implements Extractor { } @Override - public void seek(long position) { + public void seek(long position, long timeUs) { synchronizedHeaderData = 0; basisTimeUs = C.TIME_UNSET; samplesRead = 0; diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java index 9966125c46..c718cd7111 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/FragmentedMp4Extractor.java @@ -194,7 +194,7 @@ public final class FragmentedMp4Extractor implements Extractor { } @Override - public void seek(long position) { + public void seek(long position, long timeUs) { int trackCount = trackBundles.size(); for (int i = 0; i < trackCount; i++) { trackBundles.valueAt(i).reset(); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java index 4c52622c78..3759a80fd4 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/mp4/Mp4Extractor.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.extractor.mp4; +import android.support.annotation.IntDef; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.ParserException; @@ -33,6 +34,8 @@ import com.google.android.exoplayer2.util.NalUnitUtil; import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.Util; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.Stack; @@ -54,11 +57,15 @@ public final class Mp4Extractor implements Extractor, SeekMap { }; - // Parser states. - private static final int STATE_AFTER_SEEK = 0; - private static final int STATE_READING_ATOM_HEADER = 1; - private static final int STATE_READING_ATOM_PAYLOAD = 2; - private static final int STATE_READING_SAMPLE = 3; + /** + * Parser states. + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({STATE_READING_ATOM_HEADER, STATE_READING_ATOM_PAYLOAD, STATE_READING_SAMPLE}) + private @interface State {} + private static final int STATE_READING_ATOM_HEADER = 0; + private static final int STATE_READING_ATOM_PAYLOAD = 1; + private static final int STATE_READING_SAMPLE = 2; // Brand stored in the ftyp atom for QuickTime media. private static final int BRAND_QUICKTIME = Util.getIntegerCodeForString("qt "); @@ -76,6 +83,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { private final ParsableByteArray atomHeader; private final Stack containerAtoms; + @State private int parserState; private int atomType; private long atomSize; @@ -96,7 +104,6 @@ public final class Mp4Extractor implements Extractor, SeekMap { containerAtoms = new Stack<>(); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalLength = new ParsableByteArray(4); - enterReadingAtomHeaderState(); } @Override @@ -110,12 +117,16 @@ public final class Mp4Extractor implements Extractor, SeekMap { } @Override - public void seek(long position) { + public void seek(long position, long timeUs) { containerAtoms.clear(); atomHeaderBytesRead = 0; sampleBytesWritten = 0; sampleCurrentNalBytesRemaining = 0; - parserState = STATE_AFTER_SEEK; + if (position == 0) { + enterReadingAtomHeaderState(); + } else if (tracks != null) { + updateSampleIndices(timeUs); + } } @Override @@ -128,13 +139,6 @@ public final class Mp4Extractor implements Extractor, SeekMap { throws IOException, InterruptedException { while (true) { switch (parserState) { - case STATE_AFTER_SEEK: - if (input.getPosition() == 0) { - enterReadingAtomHeaderState(); - } else { - parserState = STATE_READING_SAMPLE; - } - break; case STATE_READING_ATOM_HEADER: if (!readAtomHeader(input)) { return RESULT_END_OF_INPUT; @@ -145,8 +149,10 @@ public final class Mp4Extractor implements Extractor, SeekMap { return RESULT_SEEK; } break; - default: + case STATE_READING_SAMPLE: return readSample(input, seekPosition); + default: + throw new IllegalStateException(); } } } @@ -173,8 +179,6 @@ public final class Mp4Extractor implements Extractor, SeekMap { // Handle the case where the requested time is before the first synchronization sample. sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs); } - track.sampleIndex = sampleIndex; - long offset = sampleTable.offsets[sampleIndex]; if (offset < earliestSamplePosition) { earliestSamplePosition = offset; @@ -478,6 +482,21 @@ public final class Mp4Extractor implements Extractor, SeekMap { return earliestSampleTrackIndex; } + /** + * Updates every track's sample index to point its latest sync sample before/at {@code timeUs}. + */ + private void updateSampleIndices(long timeUs) { + for (Mp4Track track : tracks) { + TrackSampleTable sampleTable = track.sampleTable; + int sampleIndex = sampleTable.getIndexOfEarlierOrEqualSynchronizationSample(timeUs); + if (sampleIndex == C.INDEX_UNSET) { + // Handle the case where the requested time is before the first synchronization sample. + sampleIndex = sampleTable.getIndexOfLaterOrEqualSynchronizationSample(timeUs); + } + track.sampleIndex = sampleIndex; + } + } + /** * Returns whether the extractor should decode a leaf atom with type {@code atom}. */ diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java index 116dedf1ce..8ee6c4dfe0 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ogg/OggExtractor.java @@ -82,7 +82,7 @@ public class OggExtractor implements Extractor { } @Override - public void seek(long position) { + public void seek(long position, long timeUs) { streamReader.seek(position); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java index 8405f21756..f6cd29aff2 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/rawcc/RawCcExtractor.java @@ -105,7 +105,7 @@ public final class RawCcExtractor implements Extractor { } @Override - public void seek(long position) { + public void seek(long position, long timeUs) { parserState = STATE_READING_HEADER; } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java index dad8214efa..e714928c20 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/Ac3Extractor.java @@ -125,7 +125,7 @@ public final class Ac3Extractor implements Extractor { } @Override - public void seek(long position) { + public void seek(long position, long timeUs) { startedPacket = false; reader.seek(); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java index 76bc4ce66e..f7dadd51b2 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/AdtsExtractor.java @@ -134,7 +134,7 @@ public final class AdtsExtractor implements Extractor { } @Override - public void seek(long position) { + public void seek(long position, long timeUs) { startedPacket = false; reader.seek(); } diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java index 6e80f4c49f..5c50ca7bf3 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/PsExtractor.java @@ -127,7 +127,7 @@ public final class PsExtractor implements Extractor { } @Override - public void seek(long position) { + public void seek(long position, long timeUs) { timestampAdjuster.reset(); for (int i = 0; i < psPayloadReaders.size(); i++) { psPayloadReaders.valueAt(i).seek(); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java index 4c5401f28b..0eec5d9675 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/ts/TsExtractor.java @@ -149,7 +149,7 @@ public final class TsExtractor implements Extractor { } @Override - public void seek(long position) { + public void seek(long position, long timeUs) { timestampAdjuster.reset(); tsPacketBuffer.reset(); continuityCounters.clear(); diff --git a/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java b/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java index 017a26f0af..3d9f8166ab 100644 --- a/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/extractor/wav/WavExtractor.java @@ -66,7 +66,7 @@ public final class WavExtractor implements Extractor, SeekMap { } @Override - public void seek(long position) { + public void seek(long position, long timeUs) { pendingBytes = 0; } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java index 13f33465d1..0b7190d382 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/ExtractorMediaPeriod.java @@ -453,7 +453,7 @@ import java.io.IOException; pendingResetPositionUs = C.TIME_UNSET; return; } - loadable.setLoadPosition(seekMap.getPosition(pendingResetPositionUs)); + loadable.setLoadPosition(seekMap.getPosition(pendingResetPositionUs), pendingResetPositionUs); pendingResetPositionUs = C.TIME_UNSET; } extractedSamplesCountAtStartOfLoad = getExtractedSamplesCount(); @@ -486,7 +486,7 @@ import java.io.IOException; for (int i = 0; i < trackCount; i++) { sampleQueues.valueAt(i).reset(!prepared || trackEnabledStates[i]); } - loadable.setLoadPosition(0); + loadable.setLoadPosition(0, 0); } } @@ -578,6 +578,7 @@ import java.io.IOException; private volatile boolean loadCanceled; private boolean pendingExtractorSeek; + private long seekTimeUs; private long length; public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder, @@ -591,8 +592,9 @@ import java.io.IOException; this.length = C.LENGTH_UNSET; } - public void setLoadPosition(long position) { + public void setLoadPosition(long position, long timeUs) { positionHolder.position = position; + seekTimeUs = timeUs; pendingExtractorSeek = true; } @@ -620,7 +622,7 @@ import java.io.IOException; input = new DefaultExtractorInput(dataSource, position, length); Extractor extractor = extractorHolder.selectExtractor(input); if (pendingExtractorSeek) { - extractor.seek(position); + extractor.seek(position, seekTimeUs); pendingExtractorSeek = false; } while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { diff --git a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java b/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java index b9aa098b9d..ed76a505ea 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkExtractorWrapper.java @@ -92,7 +92,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput extractor.init(this); extractorInitialized = true; } else { - extractor.seek(0); + extractor.seek(0, 0); if (resendFormatOnInit && sentFormat != null) { trackOutput.format(sentFormat); } diff --git a/library/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java b/library/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java index f565fdf9ea..498dd55004 100644 --- a/library/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer2/source/hls/WebvttExtractor.java @@ -79,7 +79,7 @@ import java.util.regex.Pattern; } @Override - public void seek(long position) { + public void seek(long position, long timeUs) { // This extractor is only used for the HLS use case, which should not call this method. throw new IllegalStateException(); } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java index 5962bf9911..7f6187f16b 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/TestUtil.java @@ -66,28 +66,23 @@ public class TestUtil { } } - public static FakeExtractorOutput consumeTestData(Extractor extractor, byte[] data) - throws IOException, InterruptedException { - return consumeTestData(extractor, newExtractorInput(data)); - } - - public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input) - throws IOException, InterruptedException { - return consumeTestData(extractor, input, false); + public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input, + long timeUs) throws IOException, InterruptedException { + return consumeTestData(extractor, input, timeUs, false); } public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input, - boolean retryFromStartIfLive) throws IOException, InterruptedException { + long timeUs, boolean retryFromStartIfLive) throws IOException, InterruptedException { FakeExtractorOutput output = new FakeExtractorOutput(); extractor.init(output); - consumeTestData(extractor, input, output, retryFromStartIfLive); + consumeTestData(extractor, input, timeUs, output, retryFromStartIfLive); return output; } - private static void consumeTestData(Extractor extractor, FakeExtractorInput input, + private static void consumeTestData(Extractor extractor, FakeExtractorInput input, long timeUs, FakeExtractorOutput output, boolean retryFromStartIfLive) throws IOException, InterruptedException { - extractor.seek(input.getPosition()); + extractor.seek(input.getPosition(), timeUs); PositionHolder seekPositionHolder = new PositionHolder(); int readResult = Extractor.RESULT_CONTINUE; while (readResult != Extractor.RESULT_END_OF_INPUT) { @@ -114,7 +109,7 @@ public class TestUtil { for (int i = 0; i < output.numberOfTracks; i++) { output.trackOutputs.valueAt(i).clear(); } - extractor.seek(0); + extractor.seek(0, 0); } } } @@ -277,7 +272,7 @@ public class TestUtil { Assert.assertTrue(sniffTestData(extractor, input)); input.resetPeekPosition(); - FakeExtractorOutput extractorOutput = consumeTestData(extractor, input, true); + FakeExtractorOutput extractorOutput = consumeTestData(extractor, input, 0, true); if (simulateUnknownLength && assetExists(instrumentation, sampleFile + UNKNOWN_LENGTH_EXTENSION)) { @@ -297,7 +292,7 @@ public class TestUtil { extractorOutput.trackOutputs.valueAt(i).clear(); } - consumeTestData(extractor, input, extractorOutput, false); + consumeTestData(extractor, input, timeUs, extractorOutput, false); extractorOutput.assertOutput(instrumentation, sampleFile + '.' + j + DUMP_EXTENSION); } }