From 0dc0d703979d204433d3be63479c39c51451d56d Mon Sep 17 00:00:00 2001 From: olly Date: Fri, 18 Mar 2016 07:00:31 -0700 Subject: [PATCH] Use SimpleDecoder for subtitles. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=117543706 --- .../text/subrip/SubripParserTest.java | 14 +- .../exoplayer/text/ttml/TtmlParserTest.java | 2 +- .../text/webvtt/Mp4WebvttParserTest.java | 71 ++---- .../text/webvtt/WebvttParserTest.java | 14 +- .../exoplayer/text/SubtitleInputBuffer.java | 32 +++ ...ubtitle.java => SubtitleOutputBuffer.java} | 47 ++-- .../exoplayer/text/SubtitleParser.java | 57 +++-- .../exoplayer/text/SubtitleParserFactory.java | 99 +++++++++ .../exoplayer/text/SubtitleParserHelper.java | 189 ---------------- .../exoplayer/text/TextTrackRenderer.java | 209 +++++++----------- .../exoplayer/text/subrip/SubripParser.java | 13 +- .../exoplayer/text/ttml/TtmlParser.java | 12 +- .../exoplayer/text/tx3g/Tx3gParser.java | 12 +- .../text/webvtt/Mp4WebvttParser.java | 13 +- .../exoplayer/text/webvtt/WebvttParser.java | 13 +- .../util/extensions/InputBuffer.java | 6 +- 16 files changed, 329 insertions(+), 474 deletions(-) create mode 100644 library/src/main/java/com/google/android/exoplayer/text/SubtitleInputBuffer.java rename library/src/main/java/com/google/android/exoplayer/text/{PlayableSubtitle.java => SubtitleOutputBuffer.java} (56%) create mode 100644 library/src/main/java/com/google/android/exoplayer/text/SubtitleParserFactory.java delete mode 100644 library/src/main/java/com/google/android/exoplayer/text/SubtitleParserHelper.java diff --git a/library/src/androidTest/java/com/google/android/exoplayer/text/subrip/SubripParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer/text/subrip/SubripParserTest.java index eabdb5be17..121e9b8504 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/text/subrip/SubripParserTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/text/subrip/SubripParserTest.java @@ -37,7 +37,7 @@ public final class SubripParserTest extends InstrumentationTestCase { public void testParseEmpty() throws IOException { SubripParser parser = new SubripParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY_FILE); - SubripSubtitle subtitle = parser.parse(bytes, 0, bytes.length); + SubripSubtitle subtitle = parser.decode(bytes, bytes.length); // Assert that the subtitle is empty. assertEquals(0, subtitle.getEventTimeCount()); assertTrue(subtitle.getCues(0).isEmpty()); @@ -46,7 +46,7 @@ public final class SubripParserTest extends InstrumentationTestCase { public void testParseTypical() throws IOException { SubripParser parser = new SubripParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_FILE); - SubripSubtitle subtitle = parser.parse(bytes, 0, bytes.length); + SubripSubtitle subtitle = parser.decode(bytes, bytes.length); assertEquals(6, subtitle.getEventTimeCount()); assertTypicalCue1(subtitle, 0); assertTypicalCue2(subtitle, 2); @@ -56,7 +56,7 @@ public final class SubripParserTest extends InstrumentationTestCase { public void testParseTypicalWithByteOrderMark() throws IOException { SubripParser parser = new SubripParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_WITH_BYTE_ORDER_MARK); - SubripSubtitle subtitle = parser.parse(bytes, 0, bytes.length); + SubripSubtitle subtitle = parser.decode(bytes, bytes.length); assertEquals(6, subtitle.getEventTimeCount()); assertTypicalCue1(subtitle, 0); assertTypicalCue2(subtitle, 2); @@ -66,7 +66,7 @@ public final class SubripParserTest extends InstrumentationTestCase { public void testParseTypicalExtraBlankLine() throws IOException { SubripParser parser = new SubripParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_EXTRA_BLANK_LINE); - SubripSubtitle subtitle = parser.parse(bytes, 0, bytes.length); + SubripSubtitle subtitle = parser.decode(bytes, bytes.length); assertEquals(6, subtitle.getEventTimeCount()); assertTypicalCue1(subtitle, 0); assertTypicalCue2(subtitle, 2); @@ -77,7 +77,7 @@ public final class SubripParserTest extends InstrumentationTestCase { // Parsing should succeed, parsing the first and third cues only. SubripParser parser = new SubripParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_MISSING_TIMECODE); - SubripSubtitle subtitle = parser.parse(bytes, 0, bytes.length); + SubripSubtitle subtitle = parser.decode(bytes, bytes.length); assertEquals(4, subtitle.getEventTimeCount()); assertTypicalCue1(subtitle, 0); assertTypicalCue3(subtitle, 2); @@ -87,7 +87,7 @@ public final class SubripParserTest extends InstrumentationTestCase { // Parsing should succeed, parsing the first and third cues only. SubripParser parser = new SubripParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_MISSING_SEQUENCE); - SubripSubtitle subtitle = parser.parse(bytes, 0, bytes.length); + SubripSubtitle subtitle = parser.decode(bytes, bytes.length); assertEquals(4, subtitle.getEventTimeCount()); assertTypicalCue1(subtitle, 0); assertTypicalCue3(subtitle, 2); @@ -96,7 +96,7 @@ public final class SubripParserTest extends InstrumentationTestCase { public void testParseNoEndTimecodes() throws IOException { SubripParser parser = new SubripParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_END_TIMECODES_FILE); - SubripSubtitle subtitle = parser.parse(bytes, 0, bytes.length); + SubripSubtitle subtitle = parser.decode(bytes, bytes.length); // Test event count. assertEquals(3, subtitle.getEventTimeCount()); diff --git a/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlParserTest.java index 64423c33f8..f600ebc5e2 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlParserTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/text/ttml/TtmlParserTest.java @@ -472,6 +472,6 @@ public final class TtmlParserTest extends InstrumentationTestCase { private TtmlSubtitle getSubtitle(String file) throws IOException { TtmlParser ttmlParser = new TtmlParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), file); - return ttmlParser.parse(bytes, 0, bytes.length); + return ttmlParser.decode(bytes, bytes.length); } } diff --git a/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/Mp4WebvttParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/Mp4WebvttParserTest.java index 2444dcca6f..e6b4897b70 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/Mp4WebvttParserTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/Mp4WebvttParserTest.java @@ -18,16 +18,11 @@ package com.google.android.exoplayer.text.webvtt; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.Subtitle; -import com.google.android.exoplayer.util.Util; - -import android.util.ArraySet; import junit.framework.TestCase; import java.io.IOException; -import java.util.Arrays; import java.util.List; -import java.util.Set; /** * Unit test for {@link Mp4WebvttParser}. @@ -96,20 +91,20 @@ public final class Mp4WebvttParserTest extends TestCase { // Positive tests. public void testSingleCueSample() throws ParserException { - Subtitle result = parser.parse(SINGLE_CUE_SAMPLE, 0, SINGLE_CUE_SAMPLE.length); + Subtitle result = parser.decode(SINGLE_CUE_SAMPLE, SINGLE_CUE_SAMPLE.length); Cue expectedCue = new Cue("Hello World"); // Line feed must be trimmed by the parser assertMp4WebvttSubtitleEquals(result, expectedCue); } public void testTwoCuesSample() throws ParserException { - Subtitle result = parser.parse(DOUBLE_CUE_SAMPLE, 0, DOUBLE_CUE_SAMPLE.length); + Subtitle result = parser.decode(DOUBLE_CUE_SAMPLE, DOUBLE_CUE_SAMPLE.length); Cue firstExpectedCue = new Cue("Hello World"); Cue secondExpectedCue = new Cue("Bye Bye"); assertMp4WebvttSubtitleEquals(result, firstExpectedCue, secondExpectedCue); } public void testNoCueSample() throws IOException { - Subtitle result = parser.parse(NO_CUE_SAMPLE, 0, NO_CUE_SAMPLE.length); + Subtitle result = parser.decode(NO_CUE_SAMPLE, NO_CUE_SAMPLE.length); assertMp4WebvttSubtitleEquals(result, new Cue[] {}); } @@ -117,7 +112,7 @@ public final class Mp4WebvttParserTest extends TestCase { public void testSampleWithIncompleteHeader() { try { - parser.parse(INCOMPLETE_HEADER_SAMPLE, 0, INCOMPLETE_HEADER_SAMPLE.length); + parser.decode(INCOMPLETE_HEADER_SAMPLE, INCOMPLETE_HEADER_SAMPLE.length); } catch (ParserException e) { return; } @@ -130,55 +125,31 @@ public final class Mp4WebvttParserTest extends TestCase { * Asserts that the Subtitle's cues (which are all part of the event at t=0) are equal to the * expected Cues. * - * @param sub The parsed {@link Subtitle} to check. - * @param expectedCues Expected {@link Cue}s in order of appearance. + * @param subtitle The {@link Subtitle} to check. + * @param expectedCues The expected {@link Cue}s. */ - private static void assertMp4WebvttSubtitleEquals(Subtitle sub, Cue... expectedCues) { - assertEquals(1, sub.getEventTimeCount()); - assertEquals(0, sub.getEventTime(0)); - List subtitleCues = sub.getCues(0); + private static void assertMp4WebvttSubtitleEquals(Subtitle subtitle, Cue... expectedCues) { + assertEquals(1, subtitle.getEventTimeCount()); + assertEquals(0, subtitle.getEventTime(0)); + List subtitleCues = subtitle.getCues(0); assertEquals(expectedCues.length, subtitleCues.size()); for (int i = 0; i < subtitleCues.size(); i++) { - Set differences = getCueDifferences(subtitleCues.get(i), expectedCues[i]); - assertTrue("Cues at position " + i + " are not equal. Different fields are " - + Arrays.toString(differences.toArray()), differences.isEmpty()); + assertCueEquals(expectedCues[i], subtitleCues.get(i)); } } /** - * Checks whether two non null cues are equal. Check fails if any of the Cues are null. - * - * @return a set that contains the names of the different fields. + * Asserts that two cues are equal. */ - private static Set getCueDifferences(Cue aCue, Cue anotherCue) { - assertNotNull(aCue); - assertNotNull(anotherCue); - Set differences = new ArraySet<>(); - if (aCue.line != anotherCue.line) { - differences.add("line: " + aCue.line + " | " + anotherCue.line); - } - if (aCue.lineAnchor != anotherCue.lineAnchor) { - differences.add("lineAnchor: " + aCue.lineAnchor + " | " + anotherCue.lineAnchor); - } - if (aCue.lineType != anotherCue.lineType) { - differences.add("lineType: " + aCue.lineType + " | " + anotherCue.lineType); - } - if (aCue.position != anotherCue.position) { - differences.add("position: " + aCue.position + " | " + anotherCue.position); - } - if (aCue.positionAnchor != anotherCue.positionAnchor) { - differences.add("positionAnchor: " + aCue.positionAnchor + " | " + anotherCue.positionAnchor); - } - if (aCue.size != anotherCue.size) { - differences.add("size: " + aCue.size + " | " + anotherCue.size); - } - if (!Util.areEqual(aCue.text.toString(), anotherCue.text.toString())) { - differences.add("text: '" + aCue.text + "' | '" + anotherCue.text + '\''); - } - if (!Util.areEqual(aCue.textAlignment, anotherCue.textAlignment)) { - differences.add("textAlignment: " + aCue.textAlignment + " | " + anotherCue.textAlignment); - } - return differences; + private static void assertCueEquals(Cue expected, Cue actual) { + assertEquals(expected.line, actual.line); + assertEquals(expected.lineAnchor, actual.lineAnchor); + assertEquals(expected.lineType, actual.lineType); + assertEquals(expected.position, actual.position); + assertEquals(expected.positionAnchor, actual.positionAnchor); + assertEquals(expected.size, actual.size); + assertEquals(expected.text.toString(), actual.text.toString()); + assertEquals(expected.textAlignment, actual.textAlignment); } } diff --git a/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/WebvttParserTest.java b/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/WebvttParserTest.java index ad2e664e13..9c2ffe78ae 100644 --- a/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/WebvttParserTest.java +++ b/library/src/androidTest/java/com/google/android/exoplayer/text/webvtt/WebvttParserTest.java @@ -42,7 +42,7 @@ public class WebvttParserTest extends InstrumentationTestCase { WebvttParser parser = new WebvttParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY_FILE); try { - parser.parse(bytes, 0, bytes.length); + parser.decode(bytes, bytes.length); fail("Expected ParserException"); } catch (ParserException expected) { // Do nothing. @@ -52,7 +52,7 @@ public class WebvttParserTest extends InstrumentationTestCase { public void testParseTypical() throws IOException { WebvttParser parser = new WebvttParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_FILE); - WebvttSubtitle subtitle = parser.parse(bytes, 0, bytes.length); + WebvttSubtitle subtitle = parser.decode(bytes, bytes.length); // test event count assertEquals(4, subtitle.getEventTimeCount()); @@ -65,7 +65,7 @@ public class WebvttParserTest extends InstrumentationTestCase { public void testParseTypicalWithIds() throws IOException { WebvttParser parser = new WebvttParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_WITH_IDS_FILE); - WebvttSubtitle subtitle = parser.parse(bytes, 0, bytes.length); + WebvttSubtitle subtitle = parser.decode(bytes, bytes.length); // test event count assertEquals(4, subtitle.getEventTimeCount()); @@ -78,7 +78,7 @@ public class WebvttParserTest extends InstrumentationTestCase { public void testParseTypicalWithComments() throws IOException { WebvttParser parser = new WebvttParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_WITH_COMMENTS_FILE); - WebvttSubtitle subtitle = parser.parse(bytes, 0, bytes.length); + WebvttSubtitle subtitle = parser.decode(bytes, bytes.length); // test event count assertEquals(4, subtitle.getEventTimeCount()); @@ -91,7 +91,7 @@ public class WebvttParserTest extends InstrumentationTestCase { public void testParseWithTags() throws IOException { WebvttParser parser = new WebvttParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), WITH_TAGS_FILE); - WebvttSubtitle subtitle = parser.parse(bytes, 0, bytes.length); + WebvttSubtitle subtitle = parser.decode(bytes, bytes.length); // test event count assertEquals(8, subtitle.getEventTimeCount()); @@ -106,7 +106,7 @@ public class WebvttParserTest extends InstrumentationTestCase { public void testParseWithPositioning() throws IOException { WebvttParser parser = new WebvttParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), WITH_POSITIONING_FILE); - WebvttSubtitle subtitle = parser.parse(bytes, 0, bytes.length); + WebvttSubtitle subtitle = parser.decode(bytes, bytes.length); // test event count assertEquals(12, subtitle.getEventTimeCount()); @@ -134,7 +134,7 @@ public class WebvttParserTest extends InstrumentationTestCase { public void testParseWithBadCueHeader() throws IOException { WebvttParser parser = new WebvttParser(); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), WITH_BAD_CUE_HEADER_FILE); - WebvttSubtitle subtitle = parser.parse(bytes, 0, bytes.length); + WebvttSubtitle subtitle = parser.decode(bytes, bytes.length); // test event count assertEquals(4, subtitle.getEventTimeCount()); diff --git a/library/src/main/java/com/google/android/exoplayer/text/SubtitleInputBuffer.java b/library/src/main/java/com/google/android/exoplayer/text/SubtitleInputBuffer.java new file mode 100644 index 0000000000..4e931f8600 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/text/SubtitleInputBuffer.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer.text; + +import com.google.android.exoplayer.SampleHolder; +import com.google.android.exoplayer.util.extensions.InputBuffer; + +/** + * An input buffer for {@link SubtitleParser}. + */ +/* package */ final class SubtitleInputBuffer extends InputBuffer { + + public long subsampleOffsetUs; + + public SubtitleInputBuffer() { + super(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); + } + +} diff --git a/library/src/main/java/com/google/android/exoplayer/text/PlayableSubtitle.java b/library/src/main/java/com/google/android/exoplayer/text/SubtitleOutputBuffer.java similarity index 56% rename from library/src/main/java/com/google/android/exoplayer/text/PlayableSubtitle.java rename to library/src/main/java/com/google/android/exoplayer/text/SubtitleOutputBuffer.java index 9afcf1c9c5..ff5eee9288 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/PlayableSubtitle.java +++ b/library/src/main/java/com/google/android/exoplayer/text/SubtitleOutputBuffer.java @@ -1,5 +1,3 @@ -package com.google.android.exoplayer.text; - /* * Copyright (C) 2014 The Android Open Source Project * @@ -15,36 +13,32 @@ package com.google.android.exoplayer.text; * See the License for the specific language governing permissions and * limitations under the License. */ +package com.google.android.exoplayer.text; + +import com.google.android.exoplayer.Format; +import com.google.android.exoplayer.util.extensions.OutputBuffer; + import java.util.List; /** - * A subtitle that wraps another subtitle, making it playable by adjusting it to be correctly - * aligned with the playback timebase. + * A {@link Subtitle} output from a {@link SubtitleParser}. */ -/* package */ final class PlayableSubtitle implements Subtitle { +/* package */ final class SubtitleOutputBuffer extends OutputBuffer implements Subtitle { - /** - * The start time of the subtitle. - *

- * May be less than {@code getEventTime(0)}, since a subtitle may begin prior to the time of the - * first event. - */ - public final long startTimeUs; + private final SubtitleParser owner; - private final Subtitle subtitle; - private final long offsetUs; + private Subtitle subtitle; + private long offsetUs; - /** - * @param subtitle The subtitle to wrap. - * @param isRelative True if the wrapped subtitle's timestamps are relative to the start time. - * False if they are absolute. - * @param startTimeUs The start time of the subtitle. - * @param offsetUs An offset to add to the subtitle timestamps. - */ - public PlayableSubtitle(Subtitle subtitle, boolean isRelative, long startTimeUs, long offsetUs) { + public SubtitleOutputBuffer(SubtitleParser owner) { + this.owner = owner; + } + + public void setOutput(long timestampUs, Subtitle subtitle, long subsampleOffsetUs) { + this.timestampUs = timestampUs; this.subtitle = subtitle; - this.startTimeUs = startTimeUs; - this.offsetUs = (isRelative ? startTimeUs : 0) + offsetUs; + this.offsetUs = subsampleOffsetUs == Format.OFFSET_SAMPLE_RELATIVE ? timestampUs + : subsampleOffsetUs; } @Override @@ -72,4 +66,9 @@ import java.util.List; return subtitle.getCues(timeUs - offsetUs); } + @Override + public void release() { + owner.releaseOutputBuffer(this); + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/text/SubtitleParser.java b/library/src/main/java/com/google/android/exoplayer/text/SubtitleParser.java index 1daa5aa128..a01594625f 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/SubtitleParser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/SubtitleParser.java @@ -16,29 +16,48 @@ package com.google.android.exoplayer.text; import com.google.android.exoplayer.ParserException; +import com.google.android.exoplayer.util.extensions.SimpleDecoder; /** - * Parses {@link Subtitle}s from a byte array. + * Parses {@link Subtitle}s from {@link SubtitleInputBuffer}s. */ -public interface SubtitleParser { +public abstract class SubtitleParser extends + SimpleDecoder { - /** - * Checks whether the parser supports a given subtitle mime type. - * - * @param mimeType A subtitle mime type. - * @return Whether the mime type is supported. - */ - public boolean canParse(String mimeType); + protected SubtitleParser() { + super(new SubtitleInputBuffer[2], new SubtitleOutputBuffer[2]); + setInitialInputBufferSize(1024); + } - /** - * Parses a {@link Subtitle} from the provided {@code byte[]}. - * - * @param bytes The array holding the subtitle data. - * @param offset The offset of the subtitle data in bytes. - * @param length The length of the subtitle data in bytes. - * @return A parsed representation of the subtitle. - * @throws ParserException If a problem occurred parsing the subtitle data. - */ - public Subtitle parse(byte[] bytes, int offset, int length) throws ParserException; + @Override + protected final SubtitleInputBuffer createInputBuffer() { + return new SubtitleInputBuffer(); + } + + @Override + protected final SubtitleOutputBuffer createOutputBuffer() { + return new SubtitleOutputBuffer(this); + } + + @Override + protected final void releaseOutputBuffer(SubtitleOutputBuffer buffer) { + super.releaseOutputBuffer(buffer); + } + + @Override + protected final ParserException decode(SubtitleInputBuffer inputBuffer, + SubtitleOutputBuffer outputBuffer) { + try { + Subtitle subtitle = decode(inputBuffer.sampleHolder.data.array(), + inputBuffer.sampleHolder.size); + outputBuffer.setOutput(inputBuffer.sampleHolder.timeUs, subtitle, + inputBuffer.subsampleOffsetUs); + return null; + } catch (ParserException e) { + return e; + } + } + + protected abstract Subtitle decode(byte[] data, int size) throws ParserException; } diff --git a/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserFactory.java b/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserFactory.java new file mode 100644 index 0000000000..81b107cba2 --- /dev/null +++ b/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserFactory.java @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.android.exoplayer.text; + +import com.google.android.exoplayer.Format; +import com.google.android.exoplayer.util.MimeTypes; + +/** + * A factory for {@link SubtitleParser} instances. + */ +public interface SubtitleParserFactory { + + /** + * Returns whether the factory is able to instantiate a {@link SubtitleParser} for the given + * {@link Format}. + * + * @param format The {@link Format}. + * @return True if the factory can instantiate a suitable {@link SubtitleParser}. False otherwise. + */ + boolean supportsFormat(Format format); + + /** + * Creates a {@link SubtitleParser} for the given {@link Format}. + * + * @param format The {@link Format}. + * @return A new {@link SubtitleParser}. + * @throws IllegalArgumentException If the {@link Format} is not supported. + */ + SubtitleParser createParser(Format format); + + /** + * Default {@link SubtitleParserFactory} implementation. + *

+ * The formats supported by this factory are: + *

    + *
  • WebVTT ({@link com.google.android.exoplayer.text.webvtt.WebvttParser})
  • + *
  • WebVTT (MP4) ({@link com.google.android.exoplayer.text.webvtt.Mp4WebvttParser})
  • + *
  • TTML ({@link com.google.android.exoplayer.text.ttml.TtmlParser})
  • + *
  • SubRip ({@link com.google.android.exoplayer.text.subrip.SubripParser})
  • + *
  • TX3G ({@link com.google.android.exoplayer.text.tx3g.Tx3gParser})
  • + *
+ */ + final SubtitleParserFactory DEFAULT = new SubtitleParserFactory() { + + @Override + public boolean supportsFormat(Format format) { + return getParserClass(format.sampleMimeType) != null; + } + + @Override + public SubtitleParser createParser(Format format) { + try { + Class clazz = getParserClass(format.sampleMimeType); + if (clazz == null) { + throw new IllegalArgumentException("Attempted to create parser for unsupported format"); + } + return clazz.asSubclass(SubtitleParser.class).newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + throw new IllegalStateException("Unexpected error instantiating parser", e); + } + } + + private Class getParserClass(String mimeType) { + try { + switch (mimeType) { + case MimeTypes.TEXT_VTT: + return Class.forName("com.google.android.exoplayer.text.webvtt.WebvttParser"); + case MimeTypes.APPLICATION_TTML: + return Class.forName("com.google.android.exoplayer.text.ttml.TtmlParser"); + case MimeTypes.APPLICATION_MP4VTT: + return Class.forName("com.google.android.exoplayer.text.webvtt.Mp4WebvttParser"); + case MimeTypes.APPLICATION_SUBRIP: + return Class.forName("com.google.android.exoplayer.text.subrip.SubripParser"); + case MimeTypes.APPLICATION_TX3G: + return Class.forName("com.google.android.exoplayer.text.tx3g.Tx3gParser"); + default: + return null; + } + } catch (ClassNotFoundException e) { + return null; + } + } + + }; + +} diff --git a/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserHelper.java b/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserHelper.java deleted file mode 100644 index 0aa1acbcd6..0000000000 --- a/library/src/main/java/com/google/android/exoplayer/text/SubtitleParserHelper.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer.text; - -import com.google.android.exoplayer.Format; -import com.google.android.exoplayer.ParserException; -import com.google.android.exoplayer.SampleHolder; -import com.google.android.exoplayer.util.Assertions; -import com.google.android.exoplayer.util.Util; - -import android.media.MediaCodec; -import android.os.Handler; -import android.os.Looper; -import android.os.Message; - -import java.io.IOException; - -/** - * Wraps a {@link SubtitleParser}, exposing an interface similar to {@link MediaCodec} for - * asynchronous parsing of subtitles. - */ -/* package */ final class SubtitleParserHelper implements Handler.Callback { - - private static final int MSG_FORMAT = 0; - private static final int MSG_SAMPLE = 1; - - private final SubtitleParser parser; - private final Handler handler; - - private SampleHolder sampleHolder; - private boolean parsing; - private PlayableSubtitle result; - private IOException error; - private RuntimeException runtimeError; - - private boolean subtitlesAreRelative; - private long subtitleOffsetUs; - - /** - * @param looper The {@link Looper} associated with the thread on which parsing should occur. - * @param parser The parser that should be used to parse the raw data. - */ - public SubtitleParserHelper(Looper looper, SubtitleParser parser) { - this.handler = new Handler(looper, this); - this.parser = parser; - flush(); - } - - /** - * Flushes the helper, canceling the current parsing operation, if there is one. - */ - public synchronized void flush() { - sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_NORMAL); - parsing = false; - result = null; - error = null; - runtimeError = null; - } - - /** - * Whether the helper is currently performing a parsing operation. - * - * @return True if the helper is currently performing a parsing operation. False otherwise. - */ - public synchronized boolean isParsing() { - return parsing; - } - - /** - * Gets the holder that should be populated with data to be parsed. - *

- * The returned holder will remain valid unless {@link #flush()} is called. If {@link #flush()} - * is called the holder is replaced, and this method should be called again to obtain the new - * holder. - * - * @return The holder that should be populated with data to be parsed. - */ - public synchronized SampleHolder getSampleHolder() { - return sampleHolder; - } - - /** - * Sets the format of subsequent samples. - * - * @param format The format. - */ - public void setFormat(Format format) { - handler.obtainMessage(MSG_FORMAT, format).sendToTarget(); - } - - /** - * Start a parsing operation. - *

- * The holder returned by {@link #getSampleHolder()} should be populated with the data to be - * parsed prior to calling this method. - */ - public synchronized void startParseOperation() { - Assertions.checkState(!parsing); - parsing = true; - result = null; - error = null; - runtimeError = null; - handler.obtainMessage(MSG_SAMPLE, Util.getTopInt(sampleHolder.timeUs), - Util.getBottomInt(sampleHolder.timeUs), sampleHolder).sendToTarget(); - } - - /** - * Gets the result of the most recent parsing operation. - *

- * The result is cleared as a result of calling this method, and so subsequent calls will return - * null until a subsequent parsing operation has finished. - * - * @return The result of the parsing operation, or null. - * @throws IOException If the parsing operation failed. - */ - public synchronized PlayableSubtitle getAndClearResult() throws IOException { - try { - if (error != null) { - throw error; - } else if (runtimeError != null) { - throw runtimeError; - } else { - return result; - } - } finally { - result = null; - error = null; - runtimeError = null; - } - } - - @Override - public boolean handleMessage(Message msg) { - switch (msg.what) { - case MSG_FORMAT: - handleFormat((Format) msg.obj); - break; - case MSG_SAMPLE: - long sampleTimeUs = Util.getLong(msg.arg1, msg.arg2); - SampleHolder holder = (SampleHolder) msg.obj; - handleSample(sampleTimeUs, holder); - break; - } - return true; - } - - private void handleFormat(Format format) { - subtitlesAreRelative = format.subsampleOffsetUs == Format.OFFSET_SAMPLE_RELATIVE; - subtitleOffsetUs = subtitlesAreRelative ? 0 : format.subsampleOffsetUs; - } - - private void handleSample(long sampleTimeUs, SampleHolder holder) { - Subtitle parsedSubtitle = null; - ParserException error = null; - RuntimeException runtimeError = null; - try { - parsedSubtitle = parser.parse(holder.data.array(), 0, holder.size); - } catch (ParserException e) { - error = e; - } catch (RuntimeException e) { - runtimeError = e; - } - synchronized (this) { - if (sampleHolder != holder) { - // A flush has occurred since this holder was posted. Do nothing. - } else { - this.result = new PlayableSubtitle(parsedSubtitle, subtitlesAreRelative, sampleTimeUs, - subtitleOffsetUs); - this.error = error; - this.runtimeError = runtimeError; - this.parsing = false; - } - } - } - -} diff --git a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java index ad1fa33edc..0d761b1d5c 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java +++ b/library/src/main/java/com/google/android/exoplayer/text/TextTrackRenderer.java @@ -18,6 +18,7 @@ package com.google.android.exoplayer.text; import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.Format; import com.google.android.exoplayer.FormatHolder; +import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleSourceTrackRenderer; import com.google.android.exoplayer.TrackRenderer; @@ -28,158 +29,97 @@ import com.google.android.exoplayer.util.MimeTypes; import android.annotation.TargetApi; import android.os.Handler; import android.os.Handler.Callback; -import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import java.io.IOException; -import java.util.ArrayList; import java.util.Collections; import java.util.List; /** - * A {@link TrackRenderer} for subtitles. Text is parsed from sample data using a - * {@link SubtitleParser}. The actual rendering of each line of text is delegated to a - * {@link TextRenderer}. + * A {@link TrackRenderer} for subtitles. *

- * If no {@link SubtitleParser} instances are passed to the constructor, the subtitle type will be - * detected automatically for the following supported formats: - * - *

    - *
  • WebVTT ({@link com.google.android.exoplayer.text.webvtt.WebvttParser})
  • - *
  • TTML - * ({@link com.google.android.exoplayer.text.ttml.TtmlParser})
  • - *
  • SubRip - * ({@link com.google.android.exoplayer.text.subrip.SubripParser})
  • - *
  • TX3G - * ({@link com.google.android.exoplayer.text.tx3g.Tx3gParser})
  • - *
- * - *

To override the default parsers, pass one or more {@link SubtitleParser} instances to the - * constructor. The first {@link SubtitleParser} that returns {@code true} from - * {@link SubtitleParser#canParse(String)} will be used. + * Text is parsed from sample data using {@link SubtitleParser} instances obtained from a + * {@link SubtitleParserFactory}. The actual rendering of each line of text is delegated to a + * {@link TextRenderer}. */ @TargetApi(16) public final class TextTrackRenderer extends SampleSourceTrackRenderer implements Callback { private static final int MSG_UPDATE_OVERLAY = 0; - /** - * Default parser classes in priority order. They are referred to indirectly so that it is - * possible to remove unused parsers. - */ - private static final List> DEFAULT_PARSER_CLASSES; - static { - DEFAULT_PARSER_CLASSES = new ArrayList<>(); - // Load parsers using reflection so that they can be deleted cleanly. - // Class.forName() appears for each parser so that automated tools like proguard - // can detect the use of reflection (see http://proguard.sourceforge.net/FAQ.html#forname). - try { - DEFAULT_PARSER_CLASSES.add( - Class.forName("com.google.android.exoplayer.text.webvtt.WebvttParser") - .asSubclass(SubtitleParser.class)); - } catch (ClassNotFoundException e) { - // Parser not found. - } - try { - DEFAULT_PARSER_CLASSES.add( - Class.forName("com.google.android.exoplayer.text.ttml.TtmlParser") - .asSubclass(SubtitleParser.class)); - } catch (ClassNotFoundException e) { - // Parser not found. - } - try { - DEFAULT_PARSER_CLASSES.add( - Class.forName("com.google.android.exoplayer.text.webvtt.Mp4WebvttParser") - .asSubclass(SubtitleParser.class)); - } catch (ClassNotFoundException e) { - // Parser not found. - } - try { - DEFAULT_PARSER_CLASSES.add( - Class.forName("com.google.android.exoplayer.text.subrip.SubripParser") - .asSubclass(SubtitleParser.class)); - } catch (ClassNotFoundException e) { - // Parser not found. - } - try { - DEFAULT_PARSER_CLASSES.add( - Class.forName("com.google.android.exoplayer.text.tx3g.Tx3gParser") - .asSubclass(SubtitleParser.class)); - } catch (ClassNotFoundException e) { - // Parser not found. - } - } - private final Handler textRendererHandler; private final TextRenderer textRenderer; + private final SubtitleParserFactory parserFactory; private final FormatHolder formatHolder; - private final SubtitleParser[] subtitleParsers; - private int parserIndex; private boolean inputStreamEnded; - private PlayableSubtitle subtitle; - private PlayableSubtitle nextSubtitle; - private SubtitleParserHelper parserHelper; - private HandlerThread parserThread; + private SubtitleParser parser; + private SubtitleInputBuffer nextInputBuffer; + private SubtitleOutputBuffer subtitle; + private SubtitleOutputBuffer nextSubtitle; private int nextSubtitleEventIndex; /** * @param textRenderer The text renderer. * @param textRendererLooper The looper associated with the thread on which textRenderer should be * invoked. If the renderer makes use of standard Android UI components, then this should - * normally be the looper associated with the applications' main thread, which can be - * obtained using {@link android.app.Activity#getMainLooper()}. Null may be passed if the - * renderer should be invoked directly on the player's internal rendering thread. - * @param subtitleParsers {@link SubtitleParser}s to parse text samples, in order of decreasing - * priority. If omitted, the default parsers will be used. + * normally be the looper associated with the application's main thread, which can be obtained + * using {@link android.app.Activity#getMainLooper()}. Null may be passed if the renderer + * should be invoked directly on the player's internal rendering thread. + */ + public TextTrackRenderer(TextRenderer textRenderer, Looper textRendererLooper) { + this(textRenderer, textRendererLooper, SubtitleParserFactory.DEFAULT); + } + + /** + * @param textRenderer The text renderer. + * @param textRendererLooper The looper associated with the thread on which textRenderer should be + * invoked. If the renderer makes use of standard Android UI components, then this should + * normally be the looper associated with the application's main thread, which can be obtained + * using {@link android.app.Activity#getMainLooper()}. Null may be passed if the renderer + * should be invoked directly on the player's internal rendering thread. + * @param parserFactory A factory from which to obtain {@link SubtitleParser} instances. */ public TextTrackRenderer(TextRenderer textRenderer, Looper textRendererLooper, - SubtitleParser... subtitleParsers) { + SubtitleParserFactory parserFactory) { this.textRenderer = Assertions.checkNotNull(textRenderer); this.textRendererHandler = textRendererLooper == null ? null : new Handler(textRendererLooper, this); - if (subtitleParsers == null || subtitleParsers.length == 0) { - subtitleParsers = new SubtitleParser[DEFAULT_PARSER_CLASSES.size()]; - for (int i = 0; i < subtitleParsers.length; i++) { - try { - subtitleParsers[i] = DEFAULT_PARSER_CLASSES.get(i).newInstance(); - } catch (InstantiationException e) { - throw new IllegalStateException("Unexpected error creating default parser", e); - } catch (IllegalAccessException e) { - throw new IllegalStateException("Unexpected error creating default parser", e); - } - } - } - this.subtitleParsers = subtitleParsers; + this.parserFactory = parserFactory; formatHolder = new FormatHolder(); } @Override protected int supportsFormat(Format format) { - return getParserIndex(format.sampleMimeType) != -1 ? TrackRenderer.FORMAT_HANDLED + return parserFactory.supportsFormat(format) ? TrackRenderer.FORMAT_HANDLED : (MimeTypes.isText(format.sampleMimeType) ? FORMAT_UNSUPPORTED_SUBTYPE - : FORMAT_UNSUPPORTED_TYPE); + : FORMAT_UNSUPPORTED_TYPE); } @Override protected void onEnabled(Format[] formats, TrackStream trackStream, long positionUs, boolean joining) throws ExoPlaybackException { super.onEnabled(formats, trackStream, positionUs, joining); - parserIndex = getParserIndex(formats[0].sampleMimeType); - parserThread = new HandlerThread("textParser"); - parserThread.start(); - parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParsers[parserIndex]); + parser = parserFactory.createParser(formats[0]); + parser.start(); } @Override protected void reset(long positionUs) { inputStreamEnded = false; - subtitle = null; - nextSubtitle = null; + if (subtitle != null) { + subtitle.release(); + subtitle = null; + } + if (nextSubtitle != null) { + nextSubtitle.release(); + nextSubtitle = null; + } + nextInputBuffer = null; clearTextRenderer(); - if (parserHelper != null) { - parserHelper.flush(); + if (parser != null) { + parser.flush(); } } @@ -188,7 +128,7 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement throws ExoPlaybackException { if (nextSubtitle == null) { try { - nextSubtitle = parserHelper.getAndClearResult(); + nextSubtitle = parser.dequeueOutputBuffer(); } catch (IOException e) { throw ExoPlaybackException.createForRenderer(e, getIndex()); } @@ -211,8 +151,11 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement } } - if (nextSubtitle != null && nextSubtitle.startTimeUs <= positionUs) { + if (nextSubtitle != null && nextSubtitle.timestampUs <= positionUs) { // Advance to the next subtitle. Sync the next event index and trigger an update. + if (subtitle != null) { + subtitle.release(); + } subtitle = nextSubtitle; nextSubtitle = null; nextSubtitleEventIndex = subtitle.getNextEventTimeIndex(positionUs); @@ -224,26 +167,45 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement updateTextRenderer(subtitle.getCues(positionUs)); } - if (!inputStreamEnded && nextSubtitle == null && !parserHelper.isParsing()) { - // Try and read the next subtitle from the source. - SampleHolder sampleHolder = parserHelper.getSampleHolder(); - sampleHolder.clearData(); - int result = readSource(formatHolder, sampleHolder); - if (result == TrackStream.SAMPLE_READ) { - parserHelper.startParseOperation(); - } else if (result == TrackStream.END_OF_STREAM) { - inputStreamEnded = true; + try { + if (!inputStreamEnded && nextSubtitle == null) { + if (nextInputBuffer == null) { + nextInputBuffer = parser.dequeueInputBuffer(); + if (nextInputBuffer == null) { + return; + } + } + // Try and read the next subtitle from the source. + SampleHolder sampleHolder = nextInputBuffer.sampleHolder; + sampleHolder.clearData(); + int result = readSource(formatHolder, sampleHolder); + if (result == TrackStream.SAMPLE_READ) { + nextInputBuffer.subsampleOffsetUs = formatHolder.format.subsampleOffsetUs; + parser.queueInputBuffer(nextInputBuffer); + } else if (result == TrackStream.END_OF_STREAM) { + inputStreamEnded = true; + } } + } catch (ParserException e) { + throw ExoPlaybackException.createForRenderer(e, getIndex()); } } @Override protected void onDisabled() throws ExoPlaybackException { - subtitle = null; - nextSubtitle = null; - parserThread.quit(); - parserThread = null; - parserHelper = null; + if (subtitle != null) { + subtitle.release(); + subtitle = null; + } + if (nextSubtitle != null) { + nextSubtitle.release(); + nextSubtitle = null; + } + if (parser != null) { + parser.release(); + parser = null; + } + nextInputBuffer = null; clearTextRenderer(); super.onDisabled(); } @@ -293,13 +255,4 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement textRenderer.onCues(cues); } - private int getParserIndex(String sampleMimeType) { - for (int i = 0; i < subtitleParsers.length; i++) { - if (subtitleParsers[i].canParse(sampleMimeType)) { - return i; - } - } - return -1; - } - } diff --git a/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripParser.java b/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripParser.java index 81de172d13..39c80949f8 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripParser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripParser.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer.text.subrip; import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.SubtitleParser; import com.google.android.exoplayer.util.LongArray; -import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.ParsableByteArray; import android.text.Html; @@ -33,7 +32,7 @@ import java.util.regex.Pattern; /** * A simple SubRip parser. */ -public final class SubripParser implements SubtitleParser { +public final class SubripParser extends SubtitleParser { private static final String TAG = "SubripParser"; @@ -48,16 +47,10 @@ public final class SubripParser implements SubtitleParser { } @Override - public boolean canParse(String mimeType) { - return MimeTypes.APPLICATION_SUBRIP.equals(mimeType); - } - - @Override - public SubripSubtitle parse(byte[] bytes, int offset, int length) { + protected SubripSubtitle decode(byte[] bytes, int length) { ArrayList cues = new ArrayList<>(); LongArray cueTimesUs = new LongArray(); - ParsableByteArray subripData = new ParsableByteArray(bytes, offset + length); - subripData.setPosition(offset); + ParsableByteArray subripData = new ParsableByteArray(bytes, length); boolean haveEndTimecode; String currentLine; diff --git a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java index 2b089f258a..4756c04c8f 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/ttml/TtmlParser.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer.text.ttml; import com.google.android.exoplayer.C; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.text.SubtitleParser; -import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.ParserUtil; import com.google.android.exoplayer.util.Util; @@ -58,7 +57,7 @@ import java.util.regex.Pattern; *

* @see TTML specification */ -public final class TtmlParser implements SubtitleParser { +public final class TtmlParser extends SubtitleParser { private static final String TAG = "TtmlParser"; @@ -91,16 +90,11 @@ public final class TtmlParser implements SubtitleParser { } @Override - public boolean canParse(String mimeType) { - return MimeTypes.APPLICATION_TTML.equals(mimeType); - } - - @Override - public TtmlSubtitle parse(byte[] bytes, int offset, int length) throws ParserException { + protected TtmlSubtitle decode(byte[] bytes, int length) throws ParserException { try { XmlPullParser xmlParser = xmlParserFactory.newPullParser(); Map globalStyles = new HashMap<>(); - ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, offset, length); + ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length); xmlParser.setInput(inputStream, null); TtmlSubtitle ttmlSubtitle = null; LinkedList nodeStack = new LinkedList<>(); diff --git a/library/src/main/java/com/google/android/exoplayer/text/tx3g/Tx3gParser.java b/library/src/main/java/com/google/android/exoplayer/text/tx3g/Tx3gParser.java index 8851d5e057..1a23ab3cdf 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/tx3g/Tx3gParser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/tx3g/Tx3gParser.java @@ -18,23 +18,17 @@ package com.google.android.exoplayer.text.tx3g; import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.Subtitle; import com.google.android.exoplayer.text.SubtitleParser; -import com.google.android.exoplayer.util.MimeTypes; /** * A {@link SubtitleParser} for tx3g. *

* Currently only supports parsing of a single text track. */ -public final class Tx3gParser implements SubtitleParser { +public final class Tx3gParser extends SubtitleParser { @Override - public boolean canParse(String mimeType) { - return MimeTypes.APPLICATION_TX3G.equals(mimeType); - } - - @Override - public Subtitle parse(byte[] bytes, int offset, int length) { - String cueText = new String(bytes, offset, length); + protected Subtitle decode(byte[] bytes, int length) { + String cueText = new String(bytes, 0, length); return new Tx3gSubtitle(new Cue(cueText)); } diff --git a/library/src/main/java/com/google/android/exoplayer/text/webvtt/Mp4WebvttParser.java b/library/src/main/java/com/google/android/exoplayer/text/webvtt/Mp4WebvttParser.java index 41533d9df6..a8e49070ee 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/webvtt/Mp4WebvttParser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/webvtt/Mp4WebvttParser.java @@ -18,7 +18,6 @@ package com.google.android.exoplayer.text.webvtt; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.SubtitleParser; -import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.Util; @@ -28,7 +27,7 @@ import java.util.List; /** * A {@link SubtitleParser} for Webvtt embedded in a Mp4 container file. */ -public final class Mp4WebvttParser implements SubtitleParser { +public final class Mp4WebvttParser extends SubtitleParser { private static final int BOX_HEADER_SIZE = 8; @@ -45,16 +44,10 @@ public final class Mp4WebvttParser implements SubtitleParser { } @Override - public boolean canParse(String mimeType) { - return MimeTypes.APPLICATION_MP4VTT.equals(mimeType); - } - - @Override - public Mp4WebvttSubtitle parse(byte[] bytes, int offset, int length) throws ParserException { + protected Mp4WebvttSubtitle decode(byte[] bytes, int length) throws ParserException { // Webvtt in Mp4 samples have boxes inside of them, so we have to do a traditional box parsing: // first 4 bytes size and then 4 bytes type. - sampleData.reset(bytes, offset + length); - sampleData.setPosition(offset); + sampleData.reset(bytes, length); List resultingCueList = new ArrayList<>(); while (sampleData.bytesLeft() > 0) { if (sampleData.bytesLeft() < BOX_HEADER_SIZE) { diff --git a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java index 3484259724..cad5060cc4 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java @@ -17,7 +17,6 @@ package com.google.android.exoplayer.text.webvtt; import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.text.SubtitleParser; -import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.ParsableByteArray; import android.text.TextUtils; @@ -29,7 +28,7 @@ import java.util.ArrayList; *

* @see WebVTT specification */ -public final class WebvttParser implements SubtitleParser { +public final class WebvttParser extends SubtitleParser { private final WebvttCueParser cueParser; private final ParsableByteArray parsableWebvttData; @@ -42,14 +41,8 @@ public final class WebvttParser implements SubtitleParser { } @Override - public final boolean canParse(String mimeType) { - return MimeTypes.TEXT_VTT.equals(mimeType); - } - - @Override - public final WebvttSubtitle parse(byte[] bytes, int offset, int length) throws ParserException { - parsableWebvttData.reset(bytes, offset + length); - parsableWebvttData.setPosition(offset); + protected final WebvttSubtitle decode(byte[] bytes, int length) throws ParserException { + parsableWebvttData.reset(bytes, length); webvttCueBuilder.reset(); // In case a previous parse run failed with a ParserException. // Validate the first line of the header, and skip the remainder. diff --git a/library/src/main/java/com/google/android/exoplayer/util/extensions/InputBuffer.java b/library/src/main/java/com/google/android/exoplayer/util/extensions/InputBuffer.java index 917e90bad7..0fb8eaaa8b 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/extensions/InputBuffer.java +++ b/library/src/main/java/com/google/android/exoplayer/util/extensions/InputBuffer.java @@ -25,7 +25,11 @@ public class InputBuffer extends Buffer { public final SampleHolder sampleHolder; public InputBuffer() { - sampleHolder = new SampleHolder(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT); + this(SampleHolder.BUFFER_REPLACEMENT_MODE_DIRECT); + } + + public InputBuffer(int bufferReplacementMode) { + sampleHolder = new SampleHolder(bufferReplacementMode); } @Override