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 0000000000..106402b871 Binary files /dev/null and b/library/src/androidTest/assets/ogg/bear_vorbis.ogg differ diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractorTest.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractorTest.java index 8140a6634a..adbf7eb726 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractorTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/mkv/MatroskaExtractorTest.java @@ -70,20 +70,9 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { private static final String MATROSKA_DOC_TYPE = "matroska"; private static final String WEBM_DOC_TYPE = "webm"; - private MatroskaExtractor extractor; - private FakeExtractorOutput extractorOutput; - - @Override - public void setUp() { - extractor = new MatroskaExtractor(); - extractorOutput = new FakeExtractorOutput(); - extractor.init(extractorOutput); - } - - @Override - public void tearDown() { - extractor = null; - extractorOutput = null; + private FakeExtractorOutput consumeTestData(byte[] data) + throws IOException, InterruptedException { + return TestUtil.consumeTestData(new MatroskaExtractor(), data); } public void testReadInitializationSegment() throws IOException, InterruptedException { @@ -93,27 +82,11 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { .addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertSeekMap(DEFAULT_TIMECODE_SCALE, 1); - } - - public void testReadSegmentTwice() throws IOException, InterruptedException { - byte[] data = new StreamBuilder() - .setHeader(WEBM_DOC_TYPE) - .setInfo(DEFAULT_TIMECODE_SCALE, TEST_DURATION_TIMECODE) - .addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null) - .build(1); - - TestUtil.consumeTestData(extractor, data); - extractor.seek(); - TestUtil.consumeTestData(extractor, data); - - assertTracksEnded(); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertSeekMap(DEFAULT_TIMECODE_SCALE, 1); + assertTracksEnded(extractorOutput); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1); } public void testPrepareOpus() throws IOException, InterruptedException { @@ -124,11 +97,11 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertAudioFormat(AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); - assertSeekMap(DEFAULT_TIMECODE_SCALE, 1); + assertTracksEnded(extractorOutput); + assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); + assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1); } public void testPrepareVorbis() throws IOException, InterruptedException { @@ -139,11 +112,11 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { getVorbisCodecPrivate()) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertAudioFormat(AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_VORBIS); - assertSeekMap(DEFAULT_TIMECODE_SCALE, 1); + assertTracksEnded(extractorOutput); + assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_VORBIS); + assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1); } public void testPrepareH264() throws IOException, InterruptedException { @@ -153,11 +126,11 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { .addH264Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, TEST_H264_CODEC_PRIVATE) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertH264VideoFormat(VIDEO_TRACK_NUMBER); - assertSeekMap(DEFAULT_TIMECODE_SCALE, 1); + assertTracksEnded(extractorOutput); + assertH264VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1); } public void testPrepareTwoTracks() throws IOException, InterruptedException { @@ -169,13 +142,13 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); + assertTracksEnded(extractorOutput); assertEquals(2, extractorOutput.numberOfTracks); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertAudioFormat(AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); - assertSeekMap(DEFAULT_TIMECODE_SCALE, 1); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); + assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1); } public void testPrepareThreeTracks() throws IOException, InterruptedException { @@ -188,14 +161,14 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); + assertTracksEnded(extractorOutput); // Even though the input stream has 3 tracks, only 2 of them are supported and will be reported. assertEquals(2, extractorOutput.numberOfTracks); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertAudioFormat(AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); - assertSeekMap(DEFAULT_TIMECODE_SCALE, 1); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); + assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1); } public void testPrepareFourTracks() throws IOException, InterruptedException { @@ -210,15 +183,15 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { TEST_CODEC_DELAY, TEST_SEEK_PRE_ROLL, TEST_OPUS_CODEC_PRIVATE) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); + assertTracksEnded(extractorOutput); assertEquals(4, extractorOutput.numberOfTracks); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertAudioFormat(AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_VORBIS); - assertVp9VideoFormat(SECOND_VIDEO_TRACK_NUMBER); - assertAudioFormat(SECOND_AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); - assertSeekMap(DEFAULT_TIMECODE_SCALE, 1); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_VORBIS); + assertVp9VideoFormat(extractorOutput, SECOND_VIDEO_TRACK_NUMBER); + assertAudioFormat(extractorOutput, SECOND_AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); + assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1); } public void testPrepareContentEncodingEncryption() throws IOException, InterruptedException { @@ -229,12 +202,12 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { .addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertDrmInitData(VIDEO_TRACK_NUMBER); - assertSeekMap(DEFAULT_TIMECODE_SCALE, 1); + assertTracksEnded(extractorOutput); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertDrmInitData(extractorOutput, VIDEO_TRACK_NUMBER); + assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 1); } public void testPrepareThreeCuePoints() throws IOException, InterruptedException { @@ -244,11 +217,11 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { .addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null) .build(3); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertSeekMap(DEFAULT_TIMECODE_SCALE, 3); + assertTracksEnded(extractorOutput); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertSeekMap(extractorOutput, DEFAULT_TIMECODE_SCALE, 3); } public void testPrepareCustomTimecodeScaleBeforeDuration() @@ -274,11 +247,11 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { .addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null) .build(3); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertSeekMap(timecodeScale, 3); + assertTracksEnded(extractorOutput); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertSeekMap(extractorOutput, timecodeScale, 3); } public void testPrepareNoCuesElement() throws IOException, InterruptedException { @@ -301,10 +274,10 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { true /* keyframe */, false /* invisible */, media) .build(0); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertSeekMapUnseekable(timecodeScale); + assertTracksEnded(extractorOutput); + assertSeekMapUnseekable(extractorOutput, timecodeScale); } public void testAcceptsWebmDocType() throws IOException, InterruptedException { @@ -314,9 +287,9 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { .addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); + assertTracksEnded(extractorOutput); } public void testAcceptsMatroskaDocType() throws IOException, InterruptedException { @@ -326,9 +299,9 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { .addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); + assertTracksEnded(extractorOutput); } public void testPrepareInvalidDocType() throws IOException, InterruptedException { @@ -338,7 +311,7 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { .addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, null) .build(1); try { - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); fail(); } catch (ParserException exception) { assertEquals("DocType webB not supported", exception.getMessage()); @@ -353,7 +326,7 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { .addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings) .build(1); try { - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); fail(); } catch (ParserException exception) { assertEquals("ContentEncodingOrder 1 not supported", exception.getMessage()); @@ -368,7 +341,7 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { .addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings) .build(1); try { - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); fail(); } catch (ParserException exception) { assertEquals("ContentEncodingScope 0 not supported", exception.getMessage()); @@ -384,7 +357,7 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { .addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings) .build(1); try { - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); fail(); } catch (ParserException exception) { assertEquals("ContentCompAlgo 0 not supported", exception.getMessage()); @@ -399,7 +372,7 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { .addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings) .build(1); try { - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); fail(); } catch (ParserException exception) { assertEquals("ContentEncAlgo 4 not supported", exception.getMessage()); @@ -414,7 +387,7 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { .addVp9Track(VIDEO_TRACK_NUMBER, TEST_WIDTH, TEST_HEIGHT, settings) .build(1); try { - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); fail(); } catch (ParserException exception) { assertEquals("AESSettingsCipherMode 0 not supported", exception.getMessage()); @@ -431,11 +404,12 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { true /* keyframe */, false /* invisible */, media) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertSample(0, media, 0, true, false, null, getTrackOutput(VIDEO_TRACK_NUMBER)); + assertTracksEnded(extractorOutput); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertSample(0, media, 0, true, false, null, + getTrackOutput(extractorOutput, VIDEO_TRACK_NUMBER)); } public void testReadSampleKeyframeStripped() throws IOException, InterruptedException { @@ -451,12 +425,12 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { true /* keyframe */, false /* invisible */, sampleBytes) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); + assertTracksEnded(extractorOutput); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); assertSample(0, unstrippedSampleBytes, 0, true, false, null, - getTrackOutput(VIDEO_TRACK_NUMBER)); + getTrackOutput(extractorOutput, VIDEO_TRACK_NUMBER)); } public void testReadSampleKeyframeManyBytesStripped() throws IOException, InterruptedException { @@ -472,12 +446,12 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { true /* keyframe */, false /* invisible */, sampleBytes) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); + assertTracksEnded(extractorOutput); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); assertSample(0, unstrippedSampleBytes, 0, true, false, null, - getTrackOutput(VIDEO_TRACK_NUMBER)); + getTrackOutput(extractorOutput, VIDEO_TRACK_NUMBER)); } public void testReadTwoTrackSamples() throws IOException, InterruptedException { @@ -494,14 +468,16 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { true /* keyframe */, false /* invisible */, media) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); + assertTracksEnded(extractorOutput); assertEquals(2, extractorOutput.numberOfTracks); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertAudioFormat(AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); - assertSample(0, media, 0, true, false, null, getTrackOutput(VIDEO_TRACK_NUMBER)); - assertSample(0, media, 0, true, false, null, getTrackOutput(AUDIO_TRACK_NUMBER)); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); + assertSample(0, media, 0, true, false, null, + getTrackOutput(extractorOutput, VIDEO_TRACK_NUMBER)); + assertSample(0, media, 0, true, false, null, + getTrackOutput(extractorOutput, AUDIO_TRACK_NUMBER)); } public void testReadTwoTrackSamplesWithSkippedTrack() throws IOException, InterruptedException { @@ -521,14 +497,16 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { true /* keyframe */, false /* invisible */, media) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); + assertTracksEnded(extractorOutput); assertEquals(2, extractorOutput.numberOfTracks); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertAudioFormat(AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); - assertSample(0, media, 0, true, false, null, getTrackOutput(VIDEO_TRACK_NUMBER)); - assertSample(0, media, 0, true, false, null, getTrackOutput(AUDIO_TRACK_NUMBER)); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); + assertSample(0, media, 0, true, false, null, + getTrackOutput(extractorOutput, VIDEO_TRACK_NUMBER)); + assertSample(0, media, 0, true, false, null, + getTrackOutput(extractorOutput, AUDIO_TRACK_NUMBER)); } public void testReadBlock() throws IOException, InterruptedException { @@ -542,11 +520,12 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { true /* keyframe */, false /* invisible */, media) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertAudioFormat(AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); - assertSample(0, media, 0, true, false, null, getTrackOutput(AUDIO_TRACK_NUMBER)); + assertTracksEnded(extractorOutput); + assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); + assertSample(0, media, 0, true, false, null, + getTrackOutput(extractorOutput, AUDIO_TRACK_NUMBER)); } public void testReadBlockNonKeyframe() throws IOException, InterruptedException { @@ -559,11 +538,12 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { false /* keyframe */, false /* invisible */, media) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertSample(0, media, 0, false, false, null, getTrackOutput(VIDEO_TRACK_NUMBER)); + assertTracksEnded(extractorOutput); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertSample(0, media, 0, false, false, null, getTrackOutput(extractorOutput, VIDEO_TRACK_NUMBER + )); } public void testReadEncryptedFrame() throws IOException, InterruptedException { @@ -578,12 +558,12 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { true /* validSignalByte */, media) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); + assertTracksEnded(extractorOutput); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); assertSample(0, media, 0, true, false, TEST_ENCRYPTION_KEY_ID, - getTrackOutput(VIDEO_TRACK_NUMBER)); + getTrackOutput(extractorOutput, VIDEO_TRACK_NUMBER)); } public void testReadEncryptedFrameWithInvalidSignalByte() @@ -600,10 +580,9 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { .build(1); try { - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); fail(); } catch (ParserException exception) { - assertTracksEnded(); assertEquals("Extension bit is set in signal byte", exception.getMessage()); } } @@ -618,11 +597,13 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { false /* keyframe */, true /* invisible */, media) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertSample(0, media, 25000, false, true, null, getTrackOutput(VIDEO_TRACK_NUMBER)); + assertTracksEnded(extractorOutput); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertSample(0, media, 25000, false, true, null, getTrackOutput(extractorOutput, + VIDEO_TRACK_NUMBER + )); } public void testReadSampleCustomTimecodeScale() throws IOException, InterruptedException { @@ -636,11 +617,13 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { false /* keyframe */, false /* invisible */, media) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertSample(0, media, 25, false, false, null, getTrackOutput(VIDEO_TRACK_NUMBER)); + assertTracksEnded(extractorOutput); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertSample(0, media, 25, false, false, null, getTrackOutput(extractorOutput, + VIDEO_TRACK_NUMBER + )); } public void testReadSampleNegativeSimpleBlockTimecode() throws IOException, InterruptedException { @@ -653,11 +636,13 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { true /* keyframe */, true /* invisible */, media) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertVp9VideoFormat(VIDEO_TRACK_NUMBER); - assertSample(0, media, 1000, true, true, null, getTrackOutput(VIDEO_TRACK_NUMBER)); + assertTracksEnded(extractorOutput); + assertVp9VideoFormat(extractorOutput, VIDEO_TRACK_NUMBER); + assertSample(0, media, 1000, true, true, null, getTrackOutput(extractorOutput, + VIDEO_TRACK_NUMBER + )); } public void testReadSampleWithFixedSizeLacing() throws IOException, InterruptedException { @@ -671,14 +656,14 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { 0 /* blockTimecode */, 20 /* lacingFrameCount */, media) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertAudioFormat(AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); + assertTracksEnded(extractorOutput); + assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); for (int i = 0; i < 20; i++) { long expectedTimeUs = i * TEST_DEFAULT_DURATION_NS / 1000; assertSample(i, Arrays.copyOfRange(media, i * 5, i * 5 + 5), expectedTimeUs, true, false, - null, getTrackOutput(AUDIO_TRACK_NUMBER)); + null, getTrackOutput(extractorOutput, AUDIO_TRACK_NUMBER)); } } @@ -693,42 +678,43 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { 0 /* blockTimecode */, media, 256, 1, 243) .build(1); - TestUtil.consumeTestData(extractor, data); + FakeExtractorOutput extractorOutput = consumeTestData(data); - assertTracksEnded(); - assertAudioFormat(AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); + assertTracksEnded(extractorOutput); + assertAudioFormat(extractorOutput, AUDIO_TRACK_NUMBER, MimeTypes.AUDIO_OPUS); assertSample(0, Arrays.copyOfRange(media, 0, 256), 0 * TEST_DEFAULT_DURATION_NS / 1000, true, - false, null, getTrackOutput(AUDIO_TRACK_NUMBER)); + false, null, getTrackOutput(extractorOutput, AUDIO_TRACK_NUMBER)); assertSample(1, Arrays.copyOfRange(media, 256, 257), 1 * TEST_DEFAULT_DURATION_NS / 1000, true, - false, null, getTrackOutput(AUDIO_TRACK_NUMBER)); + false, null, getTrackOutput(extractorOutput, AUDIO_TRACK_NUMBER)); assertSample(2, Arrays.copyOfRange(media, 257, 300), 2 * TEST_DEFAULT_DURATION_NS / 1000, true, - false, null, getTrackOutput(AUDIO_TRACK_NUMBER)); + false, null, getTrackOutput(extractorOutput, AUDIO_TRACK_NUMBER)); } - private FakeTrackOutput getTrackOutput(int trackNumber) { + private FakeTrackOutput getTrackOutput(FakeExtractorOutput extractorOutput, int trackNumber) { return extractorOutput.trackOutputs.get(trackNumber); } - private void assertTracksEnded() { + private void assertTracksEnded(FakeExtractorOutput extractorOutput) { assertTrue(extractorOutput.tracksEnded); } - private void assertVp9VideoFormat(int trackNumber) { - Format format = getTrackOutput(trackNumber).format; + private void assertVp9VideoFormat(FakeExtractorOutput extractorOutput, int trackNumber) { + Format format = getTrackOutput(extractorOutput, trackNumber).format; assertEquals(TEST_WIDTH, format.width); assertEquals(TEST_HEIGHT, format.height); assertEquals(MimeTypes.VIDEO_VP9, format.sampleMimeType); } - private void assertH264VideoFormat(int trackNumber) { - Format format = getTrackOutput(trackNumber).format; + private void assertH264VideoFormat(FakeExtractorOutput extractorOutput, int trackNumber) { + Format format = getTrackOutput(extractorOutput, trackNumber).format; assertEquals(TEST_WIDTH, format.width); assertEquals(TEST_HEIGHT, format.height); assertEquals(MimeTypes.VIDEO_H264, format.sampleMimeType); } - private void assertAudioFormat(int trackNumber, String expectedMimeType) { - Format format = getTrackOutput(trackNumber).format; + private void assertAudioFormat(FakeExtractorOutput extractorOutput, int trackNumber, + String expectedMimeType) { + Format format = getTrackOutput(extractorOutput, trackNumber).format; assertEquals(TEST_CHANNEL_COUNT, format.channelCount); assertEquals(TEST_SAMPLE_RATE, format.sampleRate); assertEquals(expectedMimeType, format.sampleMimeType); @@ -747,8 +733,8 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { } } - private void assertDrmInitData(int trackNumber) { - DrmInitData drmInitData = getTrackOutput(trackNumber).format.drmInitData; + private void assertDrmInitData(FakeExtractorOutput extractorOutput, int trackNumber) { + DrmInitData drmInitData = getTrackOutput(extractorOutput, trackNumber).format.drmInitData; assertNotNull(drmInitData); SchemeData widevineInitData = drmInitData.get(WIDEVINE_UUID); assertEquals(MimeTypes.VIDEO_WEBM, widevineInitData.mimeType); @@ -758,7 +744,8 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { android.test.MoreAsserts.assertEquals(TEST_ENCRYPTION_KEY_ID, zeroInitData.data); } - private void assertSeekMap(int timecodeScale, int cuePointCount) { + private void assertSeekMap(FakeExtractorOutput extractorOutput, int timecodeScale, + int cuePointCount) { ChunkIndex index = (ChunkIndex) extractorOutput.seekMap; assertEquals(cuePointCount, index.length); for (int i = 0; i < cuePointCount - 1; i++) { @@ -777,7 +764,7 @@ public final class MatroskaExtractorTest extends InstrumentationTestCase { extractorOutput.seekMap.getDurationUs()); } - private void assertSeekMapUnseekable(long timecodeScale) { + private void assertSeekMapUnseekable(FakeExtractorOutput extractorOutput, long timecodeScale) { assertFalse(extractorOutput.seekMap.isSeekable()); long expectedDurationUs = Util.scaleLargeTimestamp(TEST_DURATION_TIMECODE, timecodeScale, 1000); assertEquals(expectedDurationUs, extractorOutput.seekMap.getDurationUs()); diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/mp4/Mp4ExtractorTest.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/mp4/Mp4ExtractorTest.java index 0a843cb778..24255c28ea 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/extractor/mp4/Mp4ExtractorTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/mp4/Mp4ExtractorTest.java @@ -25,9 +25,9 @@ import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.Util; import android.annotation.TargetApi; - import junit.framework.TestCase; +import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; @@ -100,25 +100,8 @@ public final class Mp4ExtractorTest extends TestCase { /** Empty byte array. */ private static final byte[] EMPTY = new byte[0]; - private Mp4Extractor extractor; - private FakeExtractorOutput extractorOutput; - - @Override - public void setUp() { - extractor = new Mp4Extractor(); - extractorOutput = new FakeExtractorOutput(); - extractor.init(extractorOutput); - } - - @Override - public void tearDown() { - extractor = null; - extractorOutput = null; - } - public void testParsesValidMp4File() throws Exception { - TestUtil.consumeTestData(extractor, - getTestInputData(true /* includeStss */, false /* mp4vFormat */)); + FakeExtractorOutput extractorOutput = consumeTestData(true, false); // The seek map is correct. assertSeekMap(extractorOutput.seekMap, true); @@ -144,8 +127,7 @@ public final class Mp4ExtractorTest extends TestCase { } public void testParsesValidMp4FileWithoutStss() throws Exception { - TestUtil.consumeTestData(extractor, - getTestInputData(false /* includeStss */, false /* mp4vFormat */)); + FakeExtractorOutput extractorOutput = consumeTestData(false, false); // The seek map is correct. assertSeekMap(extractorOutput.seekMap, false); @@ -162,8 +144,7 @@ public final class Mp4ExtractorTest extends TestCase { } public void testParsesValidMp4vFile() throws Exception { - TestUtil.consumeTestData(extractor, - getTestInputData(true /* includeStss */, true /* mp4vFormat */)); + FakeExtractorOutput extractorOutput = consumeTestData(true, true); // The seek map is correct. assertSeekMap(extractorOutput.seekMap, true); @@ -364,9 +345,11 @@ public final class Mp4ExtractorTest extends TestCase { return CHUNK_OFFSETS[chunkIndex] + offsetInChunk; } - private static byte[] getTestInputData(boolean includeStss, boolean mp4vFormat) { - return includeStss ? getTestMp4File(mp4vFormat) + private static FakeExtractorOutput consumeTestData(boolean includeStss, boolean mp4vFormat) + throws IOException, InterruptedException { + byte[] testInputData = includeStss ? getTestMp4File(mp4vFormat) : getTestMp4FileWithoutSynchronizationData(mp4vFormat); + return TestUtil.consumeTestData(new Mp4Extractor(), testInputData); } /** Gets a valid MP4 file with audio/video tracks and synchronization data. */ diff --git a/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/OggExtractorFileTests.java b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/OggExtractorFileTests.java index 74361cd65b..9bc645414f 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/OggExtractorFileTests.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/extractor/ogg/OggExtractorFileTests.java @@ -34,6 +34,7 @@ public final class OggExtractorFileTests extends InstrumentationTestCase { public static final String OPUS_TEST_FILE = "ogg/bear.opus"; public static final String FLAC_TEST_FILE = "ogg/bear_flac.ogg"; public static final String FLAC_NS_TEST_FILE = "ogg/bear_flac_noseektable.ogg"; + public static final String VORBIS_TEST_FILE = "ogg/bear_vorbis.ogg"; public void testOpus() throws Exception { parseFile(OPUS_TEST_FILE, false, false, false, MimeTypes.AUDIO_OPUS, 2747500, 275); @@ -66,6 +67,13 @@ public final class OggExtractorFileTests extends InstrumentationTestCase { parseFile(FLAC_NS_TEST_FILE, true, true, true, MimeTypes.AUDIO_FLAC, C.UNSET_TIME_US, 33); } + public void testVorbis() throws Exception { + parseFile(VORBIS_TEST_FILE, false, false, false, MimeTypes.AUDIO_VORBIS, 2741000, 180); + parseFile(VORBIS_TEST_FILE, false, true, false, MimeTypes.AUDIO_VORBIS, C.UNSET_TIME_US, 180); + parseFile(VORBIS_TEST_FILE, true, false, true, MimeTypes.AUDIO_VORBIS, 2741000, 180); + parseFile(VORBIS_TEST_FILE, true, true, true, MimeTypes.AUDIO_VORBIS, C.UNSET_TIME_US, 180); + } + private FakeTrackOutput parseFile(String testFile, boolean simulateIOErrors, boolean simulateUnknownLength, boolean simulatePartialReads, String expectedMimeType, long expectedDuration, int expectedSampleCount) throws Exception { @@ -78,9 +86,7 @@ public final class OggExtractorFileTests extends InstrumentationTestCase { OggExtractor extractor = new OggExtractor(); assertTrue(TestUtil.sniffTestData(extractor, input)); input.resetPeekPosition(); - FakeExtractorOutput extractorOutput = new FakeExtractorOutput(); - extractor.init(extractorOutput); - TestUtil.consumeTestData(extractor, input); + FakeExtractorOutput extractorOutput = TestUtil.consumeTestData(extractor, input, true); assertEquals(1, extractorOutput.trackOutputs.size()); FakeTrackOutput trackOutput = extractorOutput.trackOutputs.get(0); diff --git a/library/src/androidTest/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java b/library/src/androidTest/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java index d2a83d3741..119896c228 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/testutil/FakeTrackOutput.java @@ -50,6 +50,15 @@ public final class FakeTrackOutput implements TrackOutput { sampleEncryptionKeys = new ArrayList<>(); } + 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(); }