From 17f8ac8adf6a22a177040bd2dcc77efff7371d3d Mon Sep 17 00:00:00 2001 From: eguven Date: Thu, 26 May 2016 08:16:16 -0700 Subject: [PATCH] Validate Extractor behavior when load position is reset to 0 following an error. Added a new method TestUtil.consumeTestData() to emulate the exact behaviour and modified OggExtractorFileTests to use it. Rest of the test will be fixed in follow up CLs. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=123320538 --- .../exoplayer/ext/flac/FlacExtractor.java | 2 +- .../androidTest/assets/ogg/bear_vorbis.ogg | Bin 0 -> 30517 bytes .../extractor/mkv/MatroskaExtractorTest.java | 309 +++++++++--------- .../extractor/mp4/Mp4ExtractorTest.java | 33 +- .../extractor/ogg/OggExtractorFileTests.java | 12 +- .../exoplayer/testutil/FakeTrackOutput.java | 9 + .../android/exoplayer/testutil/TestUtil.java | 30 +- .../chunk/ChunkExtractorWrapper.java | 2 +- .../exoplayer/extractor/Extractor.java | 11 +- .../extractor/ExtractorSampleSource.java | 49 +-- .../exoplayer/extractor/flv/FlvExtractor.java | 2 +- .../extractor/mkv/MatroskaExtractor.java | 2 +- .../exoplayer/extractor/mp3/Mp3Extractor.java | 2 +- .../extractor/mp4/FragmentedMp4Extractor.java | 2 +- .../exoplayer/extractor/mp4/Mp4Extractor.java | 2 +- .../exoplayer/extractor/ogg/FlacReader.java | 11 +- .../exoplayer/extractor/ogg/OggExtractor.java | 4 +- .../exoplayer/extractor/ogg/OpusReader.java | 19 +- .../exoplayer/extractor/ogg/StreamReader.java | 55 +++- .../exoplayer/extractor/ogg/VorbisReader.java | 12 + .../exoplayer/extractor/ts/AdtsExtractor.java | 2 +- .../exoplayer/extractor/ts/PsExtractor.java | 2 +- .../exoplayer/extractor/ts/TsExtractor.java | 2 +- .../exoplayer/extractor/wav/WavExtractor.java | 2 +- .../exoplayer/hls/WebvttExtractor.java | 2 +- 25 files changed, 324 insertions(+), 254 deletions(-) create mode 100644 library/src/androidTest/assets/ogg/bear_vorbis.ogg diff --git a/extensions/flac/src/main/java/com/google/android/exoplayer/ext/flac/FlacExtractor.java b/extensions/flac/src/main/java/com/google/android/exoplayer/ext/flac/FlacExtractor.java index b4b5931e06..1887cef26c 100644 --- a/extensions/flac/src/main/java/com/google/android/exoplayer/ext/flac/FlacExtractor.java +++ b/extensions/flac/src/main/java/com/google/android/exoplayer/ext/flac/FlacExtractor.java @@ -127,7 +127,7 @@ public final class FlacExtractor implements Extractor { } @Override - public void seek() { + public void seek(long position) { decoder.flush(); } diff --git a/library/src/androidTest/assets/ogg/bear_vorbis.ogg b/library/src/androidTest/assets/ogg/bear_vorbis.ogg new file mode 100644 index 0000000000000000000000000000000000000000..106402b87177434b28179581d11f21ada7f600e0 GIT binary patch literal 30517 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 } + public void clear() { + sampleData = new byte[0]; + sampleTimesUs.clear(); + sampleFlags.clear(); + sampleStartOffsets.clear(); + sampleEndOffsets.clear(); + sampleEncryptionKeys.clear(); + } + @Override public void format(Format format) { this.format = format; diff --git a/library/src/androidTest/java/com/google/android/exoplayer/testutil/TestUtil.java b/library/src/androidTest/java/com/google/android/exoplayer/testutil/TestUtil.java index 143783e513..d9a104362f 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/testutil/TestUtil.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/testutil/TestUtil.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer.testutil; +import com.google.android.exoplayer.C; import com.google.android.exoplayer.extractor.Extractor; import com.google.android.exoplayer.extractor.PositionHolder; import com.google.android.exoplayer.testutil.FakeExtractorInput.SimulatedIOException; @@ -52,13 +53,21 @@ public class TestUtil { } } - public static void consumeTestData(Extractor extractor, byte[] data) + public static FakeExtractorOutput consumeTestData(Extractor extractor, byte[] data) throws IOException, InterruptedException { - consumeTestData(extractor, newExtractorInput(data)); + return consumeTestData(extractor, newExtractorInput(data)); } - public static void consumeTestData(Extractor extractor, FakeExtractorInput input) + public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input) throws IOException, InterruptedException { + return consumeTestData(extractor, input, false); + } + + public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input, + boolean retryFromStartIfLive) throws IOException, InterruptedException { + FakeExtractorOutput output = new FakeExtractorOutput(); + extractor.init(output); + PositionHolder seekPositionHolder = new PositionHolder(); int readResult = Extractor.RESULT_CONTINUE; while (readResult != Extractor.RESULT_END_OF_INPUT) { @@ -70,9 +79,22 @@ public class TestUtil { input.setPosition((int) seekPosition); } } catch (SimulatedIOException e) { - // Ignore. + if (!retryFromStartIfLive) { + continue; + } + boolean isOnDemand = input.getLength() != C.LENGTH_UNBOUNDED + || (output.seekMap != null && output.seekMap.getDurationUs() != C.UNSET_TIME_US); + if (isOnDemand) { + continue; + } + input.setPosition(0); + for (int i = 0; i < output.numberOfTracks; i++) { + output.trackOutputs.valueAt(i).clear(); + } + extractor.seek(0); } } + return output; } public static byte[] buildTestData(int length) { diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java index e1a4223d80..ad82d41e3a 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/ChunkExtractorWrapper.java @@ -81,7 +81,7 @@ public final class ChunkExtractorWrapper implements ExtractorOutput, TrackOutput extractor.init(this); extractorInitialized = true; } else { - extractor.seek(); + extractor.seek(0); } } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/Extractor.java index 6f68099df4..2bd8f3819e 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/Extractor.java @@ -90,12 +90,13 @@ public interface Extractor { * Notifies the extractor that a seek has occurred. *

* Following a call to this method, the {@link ExtractorInput} passed to the next invocation of - * {@link #read(ExtractorInput, PositionHolder)} is required to provide data starting from a - * random access 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}. + * {@link #read(ExtractorInput, PositionHolder)} is required to provide data starting from {@code + * 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. */ - void seek(); + void seek(long position); /** * Releases all kept resources. 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 3a701690d2..a0fe9a2464 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 @@ -134,6 +134,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu private TrackGroupArray tracks; private long durationUs; private boolean[] trackEnabledStates; + private long length; private long downstreamPositionUs; private long lastSeekPositionUs; @@ -237,6 +238,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu extractorHolder = new ExtractorHolder(extractors, this); pendingResetPositionUs = C.UNSET_TIME_US; sampleQueues = new DefaultTrackOutput[0]; + length = C.LENGTH_UNBOUNDED; } /** @@ -491,11 +493,13 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu @Override public void onLoadCompleted(Loadable loadable, long elapsedMs) { + copyLengthFromLoader(); loadingFinished = true; } @Override public void onLoadCanceled(Loadable loadable, long elapsedMs) { + copyLengthFromLoader(); if (enabledTrackCount > 0) { restartFrom(pendingResetPositionUs); } else { @@ -506,6 +510,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu @Override public int onLoadError(Loadable loadable, long elapsedMs, IOException e) { + copyLengthFromLoader(); notifyLoadError(e); if (isLoadableExceptionFatal(e)) { return Loader.DONT_RETRY_FATAL; @@ -539,6 +544,12 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu // Internal methods. + private void copyLengthFromLoader() { + if (length == C.LENGTH_UNBOUNDED) { + length = loadable.length; + } + } + private void seekToInternal(long positionUs) { // Treat all seeks into non-seekable media as being to t=0. positionUs = seekMap.isSeekable() ? positionUs : 0; @@ -588,23 +599,19 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu private void configureRetry() { Assertions.checkState(loadable != null); - if (!prepared) { - // We don't know whether we're playing an on-demand or a live stream. For a live stream we - // need to load from the start, as outlined below. Since we might be playing a live stream, - // play it safe and load from the start. - clearSampleQueues(); - loadable.setLoadPosition(0); - } else if (!seekMap.isSeekable() && durationUs == C.UNSET_TIME_US) { - // We're playing a non-seekable stream with unknown duration. Assume it's live, and - // 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); - } else { - // We're playing a seekable on-demand stream. Resume the current loadable, which will + if (length != C.LENGTH_UNBOUNDED + || (seekMap != null && seekMap.getDurationUs() != C.UNSET_TIME_US)) { + // We're playing an on-demand stream. Resume the current loadable, which will // request data starting from the point it left off. + } else { + // We're playing a stream of unknown length and duration. Assume it's live, and + // 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 = prepared; + clearSampleQueues(); + loadable.setLoadPosition(0); } } @@ -703,6 +710,7 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu private volatile boolean loadCanceled; private boolean pendingExtractorSeek; + private long length; public ExtractingLoadable(Uri uri, DataSource dataSource, ExtractorHolder extractorHolder, Allocator allocator, int requestedBufferSize) { @@ -711,8 +719,9 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu this.extractorHolder = Assertions.checkNotNull(extractorHolder); this.allocator = Assertions.checkNotNull(allocator); this.requestedBufferSize = requestedBufferSize; - positionHolder = new PositionHolder(); - pendingExtractorSeek = true; + this.positionHolder = new PositionHolder(); + this.pendingExtractorSeek = true; + this.length = C.LENGTH_UNBOUNDED; } public void setLoadPosition(long position) { @@ -737,14 +746,14 @@ public final class ExtractorSampleSource implements SampleSource, ExtractorOutpu ExtractorInput input = null; try { long position = positionHolder.position; - long length = dataSource.open(new DataSpec(uri, position, C.LENGTH_UNBOUNDED, null)); + length = dataSource.open(new DataSpec(uri, position, C.LENGTH_UNBOUNDED, null)); if (length != C.LENGTH_UNBOUNDED) { length += position; } input = new DefaultExtractorInput(dataSource, position, length); Extractor extractor = extractorHolder.selectExtractor(input); if (pendingExtractorSeek) { - extractor.seek(); + extractor.seek(position); pendingExtractorSeek = false; } while (result == Extractor.RESULT_CONTINUE && !loadCanceled) { diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/flv/FlvExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/flv/FlvExtractor.java index 2447bcacf6..b5d7f5c16d 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/flv/FlvExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/flv/FlvExtractor.java @@ -114,7 +114,7 @@ public final class FlvExtractor implements Extractor, SeekMap { } @Override - public void seek() { + public void seek(long position) { parserState = STATE_READING_FLV_HEADER; bytesToNextTagHeader = 0; } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractor.java index a1d779b14b..45970c7550 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractor.java @@ -289,7 +289,7 @@ public final class MatroskaExtractor implements Extractor { } @Override - public void seek() { + public void seek(long position) { clusterTimecodeUs = UNKNOWN; blockState = BLOCK_STATE_START; reader.reset(); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java index c778c7ae33..6a9092ea7b 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp3/Mp3Extractor.java @@ -104,7 +104,7 @@ public final class Mp3Extractor implements Extractor { } @Override - public void seek() { + public void seek(long position) { synchronizedHeaderData = 0; samplesRead = 0; basisTimeUs = -1; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java index 55bbec5c60..f09c80d475 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/FragmentedMp4Extractor.java @@ -165,7 +165,7 @@ public final class FragmentedMp4Extractor implements Extractor { } @Override - public void seek() { + public void seek(long position) { 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/exoplayer/extractor/mp4/Mp4Extractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java index 03c2f18efb..889e99fdfc 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/mp4/Mp4Extractor.java @@ -97,7 +97,7 @@ public final class Mp4Extractor implements Extractor, SeekMap { } @Override - public void seek() { + public void seek(long position) { containerAtoms.clear(); atomHeaderBytesRead = 0; sampleBytesWritten = 0; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/FlacReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/FlacReader.java index 9508aedfc1..7964429964 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/FlacReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/FlacReader.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer.extractor.ogg; import com.google.android.exoplayer.Format; import com.google.android.exoplayer.extractor.ExtractorInput; import com.google.android.exoplayer.extractor.SeekMap; -import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.FlacStreamInfo; import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.ParsableByteArray; @@ -47,6 +46,15 @@ import java.util.List; data.readUnsignedInt() == 0x464C4143; // ASCII signature "FLAC" } + @Override + protected void reset(boolean headerData) { + super.reset(headerData); + if (headerData) { + streamInfo = null; + flacOggSeeker = null; + } + } + //@VisibleForTesting public static boolean isAudioPacket(byte[] data) { return data[0] == AUDIO_PACKET_TYPE; @@ -73,7 +81,6 @@ import java.util.List; streamInfo.bitRate(), streamInfo.channels, streamInfo.sampleRate, initializationData, null, 0, null); } else if ((data[0] & 0x7F) == SEEKTABLE_PACKET_TYPE) { - Assertions.checkArgument(flacOggSeeker == null); flacOggSeeker = new FlacOggSeeker(); flacOggSeeker.parseSeekTable(packet); } else if (isAudioPacket(data)) { diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OggExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OggExtractor.java index b3ef3675fe..f7b9e065f6 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OggExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OggExtractor.java @@ -70,8 +70,8 @@ public class OggExtractor implements Extractor { } @Override - public void seek() { - streamReader.seek(); + public void seek(long position) { + streamReader.seek(position); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OpusReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OpusReader.java index b5659ce3e0..b75308d7f3 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OpusReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/OpusReader.java @@ -19,6 +19,7 @@ import com.google.android.exoplayer.C; import com.google.android.exoplayer.Format; import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.ParsableByteArray; +import com.google.android.exoplayer.util.Util; import java.io.IOException; import java.nio.ByteBuffer; @@ -39,9 +40,10 @@ import java.util.List; */ private static final int SAMPLE_RATE = 48000; + private static final int OPUS_CODE = Util.getIntegerCodeForString("Opus"); private static final byte[] OPUS_SIGNATURE = {'O', 'p', 'u', 's', 'H', 'e', 'a', 'd'}; + private boolean headerRead; - private boolean tagsSkipped; public static boolean verifyBitstreamType(ParsableByteArray data) { if (data.bytesLeft() < OPUS_SIGNATURE.length) { @@ -52,6 +54,14 @@ import java.util.List; return Arrays.equals(header, OPUS_SIGNATURE); } + @Override + protected void reset(boolean headerData) { + super.reset(headerData); + if (headerData) { + headerRead = false; + } + } + @Override protected long preparePayload(ParsableByteArray packet) { return convertTimeToGranule(getPacketDurationUs(packet.data)); @@ -73,11 +83,10 @@ import java.util.List; setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_OPUS, Format.NO_VALUE, Format.NO_VALUE, channelCount, SAMPLE_RATE, initializationData, null, 0, "und"); headerRead = true; - } else if (!tagsSkipped) { - // Skip tags packet - tagsSkipped = true; } else { - return false; + boolean headerPacket = packet.readInt() == OPUS_CODE; + packet.setPosition(0); + return headerPacket; } return true; } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/StreamReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/StreamReader.java index d77076e290..f76c800e82 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/StreamReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/StreamReader.java @@ -18,8 +18,9 @@ import java.io.IOException; /* package */ abstract class StreamReader { private static final int STATE_READ_HEADERS = 0; - private static final int STATE_READ_PAYLOAD = 1; - private static final int STATE_END_OF_INPUT = 2; + private static final int STATE_SKIP_HEADERS = 1; + private static final int STATE_READ_PAYLOAD = 2; + private static final int STATE_END_OF_INPUT = 3; static class SetupData { Format format; @@ -38,27 +39,44 @@ import java.io.IOException; private SetupData setupData; private long lengthOfReadPacket; private boolean seekMapSet; + private boolean formatSet; void init(ExtractorOutput output, TrackOutput trackOutput) { this.extractorOutput = output; this.trackOutput = trackOutput; this.oggPacket = new OggPacket(); - this.setupData = new SetupData(); - this.state = STATE_READ_HEADERS; - this.targetGranule = -1; - this.payloadStartPosition = 0; + reset(true); } /** - * @see Extractor#seek() + * Resets the state of the {@link StreamReader}. + * @param headerData Resets parsed header data too. */ - final void seek() { - oggPacket.reset(); + protected void reset(boolean headerData) { + if (headerData) { + setupData = new SetupData(); + payloadStartPosition = 0; + state = STATE_READ_HEADERS; + } else { + state = STATE_SKIP_HEADERS; + } + targetGranule = -1; + currentGranule = 0; + } - if (state != STATE_READ_HEADERS) { - targetGranule = oggSeeker.startSeek(); - state = STATE_READ_PAYLOAD; + /** + * @see Extractor#seek(long) + */ + final void seek(long position) { + oggPacket.reset(); + if (position == 0) { + reset(!seekMapSet); + } else { + if (state != STATE_READ_HEADERS) { + targetGranule = oggSeeker.startSeek(); + state = STATE_READ_PAYLOAD; + } } } @@ -71,6 +89,11 @@ import java.io.IOException; case STATE_READ_HEADERS: return readHeaders(input); + case STATE_SKIP_HEADERS: + input.skipFully((int) payloadStartPosition); + state = STATE_READ_PAYLOAD; + return Extractor.RESULT_CONTINUE; + case STATE_READ_PAYLOAD: return readPayload(input, seekPosition); @@ -80,8 +103,7 @@ import java.io.IOException; } } - private int readHeaders(ExtractorInput input) - throws IOException, InterruptedException { + private int readHeaders(ExtractorInput input) throws IOException, InterruptedException { boolean readingHeaders = true; while (readingHeaders) { if (!oggPacket.populate(input)) { @@ -97,7 +119,10 @@ import java.io.IOException; } sampleRate = setupData.format.sampleRate; - trackOutput.format(setupData.format); + if (!formatSet) { + trackOutput.format(setupData.format); + formatSet = true; + } if (setupData.oggSeeker != null) { oggSeeker = setupData.oggSeeker; diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/VorbisReader.java b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/VorbisReader.java index dca9053bfa..9c448dcf53 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ogg/VorbisReader.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ogg/VorbisReader.java @@ -44,6 +44,18 @@ import java.util.ArrayList; } } + @Override + protected void reset(boolean headerData) { + super.reset(headerData); + if (headerData) { + vorbisSetup = null; + vorbisIdHeader = null; + commentHeader = null; + } + previousPacketBlockSize = 0; + seenFirstAudioPacket = false; + } + @Override protected void onSeekEnd(long currentGranule) { super.onSeekEnd(currentGranule); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java index 2bacf5d0c5..7a4020d2d2 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/AdtsExtractor.java @@ -120,7 +120,7 @@ public final class AdtsExtractor implements Extractor { } @Override - public void seek() { + public void seek(long position) { startedPacket = false; adtsReader.seek(); } diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/PsExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/PsExtractor.java index 95a328a78a..b110a2bf7a 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/PsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/PsExtractor.java @@ -113,7 +113,7 @@ public final class PsExtractor implements Extractor { } @Override - public void seek() { + public void seek(long position) { ptsTimestampAdjuster.reset(); for (int i = 0; i < psPayloadReaders.size(); i++) { psPayloadReaders.valueAt(i).seek(); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java index 61aee6bf43..4dcc9605c7 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/ts/TsExtractor.java @@ -116,7 +116,7 @@ public final class TsExtractor implements Extractor { } @Override - public void seek() { + public void seek(long position) { ptsTimestampAdjuster.reset(); for (int i = 0; i < tsPayloadReaders.size(); i++) { tsPayloadReaders.valueAt(i).seek(); diff --git a/library/src/main/java/com/google/android/exoplayer/extractor/wav/WavExtractor.java b/library/src/main/java/com/google/android/exoplayer/extractor/wav/WavExtractor.java index 873bf36326..6a27c8fd03 100644 --- a/library/src/main/java/com/google/android/exoplayer/extractor/wav/WavExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/extractor/wav/WavExtractor.java @@ -54,7 +54,7 @@ public final class WavExtractor implements Extractor, SeekMap { } @Override - public void seek() { + public void seek(long position) { pendingBytes = 0; } diff --git a/library/src/main/java/com/google/android/exoplayer/hls/WebvttExtractor.java b/library/src/main/java/com/google/android/exoplayer/hls/WebvttExtractor.java index 3fcef77085..088b785523 100644 --- a/library/src/main/java/com/google/android/exoplayer/hls/WebvttExtractor.java +++ b/library/src/main/java/com/google/android/exoplayer/hls/WebvttExtractor.java @@ -80,7 +80,7 @@ import java.util.regex.Pattern; } @Override - public void seek() { + public void seek(long position) { // This extractor is only used for the HLS use case, which should not call this method. throw new IllegalStateException(); }