提交 0dc0d703 编写于 作者: O olly 提交者: Oliver Woodman

Use SimpleDecoder for subtitles.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=117543706
上级 ed4f8397
...@@ -37,7 +37,7 @@ public final class SubripParserTest extends InstrumentationTestCase { ...@@ -37,7 +37,7 @@ public final class SubripParserTest extends InstrumentationTestCase {
public void testParseEmpty() throws IOException { public void testParseEmpty() throws IOException {
SubripParser parser = new SubripParser(); SubripParser parser = new SubripParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY_FILE); 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. // Assert that the subtitle is empty.
assertEquals(0, subtitle.getEventTimeCount()); assertEquals(0, subtitle.getEventTimeCount());
assertTrue(subtitle.getCues(0).isEmpty()); assertTrue(subtitle.getCues(0).isEmpty());
...@@ -46,7 +46,7 @@ public final class SubripParserTest extends InstrumentationTestCase { ...@@ -46,7 +46,7 @@ public final class SubripParserTest extends InstrumentationTestCase {
public void testParseTypical() throws IOException { public void testParseTypical() throws IOException {
SubripParser parser = new SubripParser(); SubripParser parser = new SubripParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_FILE); 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()); assertEquals(6, subtitle.getEventTimeCount());
assertTypicalCue1(subtitle, 0); assertTypicalCue1(subtitle, 0);
assertTypicalCue2(subtitle, 2); assertTypicalCue2(subtitle, 2);
...@@ -56,7 +56,7 @@ public final class SubripParserTest extends InstrumentationTestCase { ...@@ -56,7 +56,7 @@ public final class SubripParserTest extends InstrumentationTestCase {
public void testParseTypicalWithByteOrderMark() throws IOException { public void testParseTypicalWithByteOrderMark() throws IOException {
SubripParser parser = new SubripParser(); SubripParser parser = new SubripParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_WITH_BYTE_ORDER_MARK); 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()); assertEquals(6, subtitle.getEventTimeCount());
assertTypicalCue1(subtitle, 0); assertTypicalCue1(subtitle, 0);
assertTypicalCue2(subtitle, 2); assertTypicalCue2(subtitle, 2);
...@@ -66,7 +66,7 @@ public final class SubripParserTest extends InstrumentationTestCase { ...@@ -66,7 +66,7 @@ public final class SubripParserTest extends InstrumentationTestCase {
public void testParseTypicalExtraBlankLine() throws IOException { public void testParseTypicalExtraBlankLine() throws IOException {
SubripParser parser = new SubripParser(); SubripParser parser = new SubripParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_EXTRA_BLANK_LINE); 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()); assertEquals(6, subtitle.getEventTimeCount());
assertTypicalCue1(subtitle, 0); assertTypicalCue1(subtitle, 0);
assertTypicalCue2(subtitle, 2); assertTypicalCue2(subtitle, 2);
...@@ -77,7 +77,7 @@ public final class SubripParserTest extends InstrumentationTestCase { ...@@ -77,7 +77,7 @@ public final class SubripParserTest extends InstrumentationTestCase {
// Parsing should succeed, parsing the first and third cues only. // Parsing should succeed, parsing the first and third cues only.
SubripParser parser = new SubripParser(); SubripParser parser = new SubripParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_MISSING_TIMECODE); 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()); assertEquals(4, subtitle.getEventTimeCount());
assertTypicalCue1(subtitle, 0); assertTypicalCue1(subtitle, 0);
assertTypicalCue3(subtitle, 2); assertTypicalCue3(subtitle, 2);
...@@ -87,7 +87,7 @@ public final class SubripParserTest extends InstrumentationTestCase { ...@@ -87,7 +87,7 @@ public final class SubripParserTest extends InstrumentationTestCase {
// Parsing should succeed, parsing the first and third cues only. // Parsing should succeed, parsing the first and third cues only.
SubripParser parser = new SubripParser(); SubripParser parser = new SubripParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_MISSING_SEQUENCE); 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()); assertEquals(4, subtitle.getEventTimeCount());
assertTypicalCue1(subtitle, 0); assertTypicalCue1(subtitle, 0);
assertTypicalCue3(subtitle, 2); assertTypicalCue3(subtitle, 2);
...@@ -96,7 +96,7 @@ public final class SubripParserTest extends InstrumentationTestCase { ...@@ -96,7 +96,7 @@ public final class SubripParserTest extends InstrumentationTestCase {
public void testParseNoEndTimecodes() throws IOException { public void testParseNoEndTimecodes() throws IOException {
SubripParser parser = new SubripParser(); SubripParser parser = new SubripParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_END_TIMECODES_FILE); 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. // Test event count.
assertEquals(3, subtitle.getEventTimeCount()); assertEquals(3, subtitle.getEventTimeCount());
......
...@@ -472,6 +472,6 @@ public final class TtmlParserTest extends InstrumentationTestCase { ...@@ -472,6 +472,6 @@ public final class TtmlParserTest extends InstrumentationTestCase {
private TtmlSubtitle getSubtitle(String file) throws IOException { private TtmlSubtitle getSubtitle(String file) throws IOException {
TtmlParser ttmlParser = new TtmlParser(); TtmlParser ttmlParser = new TtmlParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), file); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), file);
return ttmlParser.parse(bytes, 0, bytes.length); return ttmlParser.decode(bytes, bytes.length);
} }
} }
...@@ -18,16 +18,11 @@ package com.google.android.exoplayer.text.webvtt; ...@@ -18,16 +18,11 @@ package com.google.android.exoplayer.text.webvtt;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.Subtitle; import com.google.android.exoplayer.text.Subtitle;
import com.google.android.exoplayer.util.Util;
import android.util.ArraySet;
import junit.framework.TestCase; import junit.framework.TestCase;
import java.io.IOException; import java.io.IOException;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Set;
/** /**
* Unit test for {@link Mp4WebvttParser}. * Unit test for {@link Mp4WebvttParser}.
...@@ -96,20 +91,20 @@ public final class Mp4WebvttParserTest extends TestCase { ...@@ -96,20 +91,20 @@ public final class Mp4WebvttParserTest extends TestCase {
// Positive tests. // Positive tests.
public void testSingleCueSample() throws ParserException { 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 Cue expectedCue = new Cue("Hello World"); // Line feed must be trimmed by the parser
assertMp4WebvttSubtitleEquals(result, expectedCue); assertMp4WebvttSubtitleEquals(result, expectedCue);
} }
public void testTwoCuesSample() throws ParserException { 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 firstExpectedCue = new Cue("Hello World");
Cue secondExpectedCue = new Cue("Bye Bye"); Cue secondExpectedCue = new Cue("Bye Bye");
assertMp4WebvttSubtitleEquals(result, firstExpectedCue, secondExpectedCue); assertMp4WebvttSubtitleEquals(result, firstExpectedCue, secondExpectedCue);
} }
public void testNoCueSample() throws IOException { 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[] {}); assertMp4WebvttSubtitleEquals(result, new Cue[] {});
} }
...@@ -117,7 +112,7 @@ public final class Mp4WebvttParserTest extends TestCase { ...@@ -117,7 +112,7 @@ public final class Mp4WebvttParserTest extends TestCase {
public void testSampleWithIncompleteHeader() { public void testSampleWithIncompleteHeader() {
try { try {
parser.parse(INCOMPLETE_HEADER_SAMPLE, 0, INCOMPLETE_HEADER_SAMPLE.length); parser.decode(INCOMPLETE_HEADER_SAMPLE, INCOMPLETE_HEADER_SAMPLE.length);
} catch (ParserException e) { } catch (ParserException e) {
return; return;
} }
...@@ -130,55 +125,31 @@ public final class Mp4WebvttParserTest extends TestCase { ...@@ -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 * Asserts that the Subtitle's cues (which are all part of the event at t=0) are equal to the
* expected Cues. * expected Cues.
* *
* @param sub The parsed {@link Subtitle} to check. * @param subtitle The {@link Subtitle} to check.
* @param expectedCues Expected {@link Cue}s in order of appearance. * @param expectedCues The expected {@link Cue}s.
*/ */
private static void assertMp4WebvttSubtitleEquals(Subtitle sub, Cue... expectedCues) { private static void assertMp4WebvttSubtitleEquals(Subtitle subtitle, Cue... expectedCues) {
assertEquals(1, sub.getEventTimeCount()); assertEquals(1, subtitle.getEventTimeCount());
assertEquals(0, sub.getEventTime(0)); assertEquals(0, subtitle.getEventTime(0));
List<Cue> subtitleCues = sub.getCues(0); List<Cue> subtitleCues = subtitle.getCues(0);
assertEquals(expectedCues.length, subtitleCues.size()); assertEquals(expectedCues.length, subtitleCues.size());
for (int i = 0; i < subtitleCues.size(); i++) { for (int i = 0; i < subtitleCues.size(); i++) {
Set<String> differences = getCueDifferences(subtitleCues.get(i), expectedCues[i]); assertCueEquals(expectedCues[i], subtitleCues.get(i));
assertTrue("Cues at position " + i + " are not equal. Different fields are "
+ Arrays.toString(differences.toArray()), differences.isEmpty());
} }
} }
/** /**
* Checks whether two non null cues are equal. Check fails if any of the Cues are null. * Asserts that two cues are equal.
*
* @return a set that contains the names of the different fields.
*/ */
private static Set<String> getCueDifferences(Cue aCue, Cue anotherCue) { private static void assertCueEquals(Cue expected, Cue actual) {
assertNotNull(aCue); assertEquals(expected.line, actual.line);
assertNotNull(anotherCue); assertEquals(expected.lineAnchor, actual.lineAnchor);
Set<String> differences = new ArraySet<>(); assertEquals(expected.lineType, actual.lineType);
if (aCue.line != anotherCue.line) { assertEquals(expected.position, actual.position);
differences.add("line: " + aCue.line + " | " + anotherCue.line); assertEquals(expected.positionAnchor, actual.positionAnchor);
} assertEquals(expected.size, actual.size);
if (aCue.lineAnchor != anotherCue.lineAnchor) { assertEquals(expected.text.toString(), actual.text.toString());
differences.add("lineAnchor: " + aCue.lineAnchor + " | " + anotherCue.lineAnchor); assertEquals(expected.textAlignment, actual.textAlignment);
}
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;
} }
} }
...@@ -42,7 +42,7 @@ public class WebvttParserTest extends InstrumentationTestCase { ...@@ -42,7 +42,7 @@ public class WebvttParserTest extends InstrumentationTestCase {
WebvttParser parser = new WebvttParser(); WebvttParser parser = new WebvttParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY_FILE); byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY_FILE);
try { try {
parser.parse(bytes, 0, bytes.length); parser.decode(bytes, bytes.length);
fail("Expected ParserException"); fail("Expected ParserException");
} catch (ParserException expected) { } catch (ParserException expected) {
// Do nothing. // Do nothing.
...@@ -52,7 +52,7 @@ public class WebvttParserTest extends InstrumentationTestCase { ...@@ -52,7 +52,7 @@ public class WebvttParserTest extends InstrumentationTestCase {
public void testParseTypical() throws IOException { public void testParseTypical() throws IOException {
WebvttParser parser = new WebvttParser(); WebvttParser parser = new WebvttParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_FILE); 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 // test event count
assertEquals(4, subtitle.getEventTimeCount()); assertEquals(4, subtitle.getEventTimeCount());
...@@ -65,7 +65,7 @@ public class WebvttParserTest extends InstrumentationTestCase { ...@@ -65,7 +65,7 @@ public class WebvttParserTest extends InstrumentationTestCase {
public void testParseTypicalWithIds() throws IOException { public void testParseTypicalWithIds() throws IOException {
WebvttParser parser = new WebvttParser(); WebvttParser parser = new WebvttParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_WITH_IDS_FILE); 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 // test event count
assertEquals(4, subtitle.getEventTimeCount()); assertEquals(4, subtitle.getEventTimeCount());
...@@ -78,7 +78,7 @@ public class WebvttParserTest extends InstrumentationTestCase { ...@@ -78,7 +78,7 @@ public class WebvttParserTest extends InstrumentationTestCase {
public void testParseTypicalWithComments() throws IOException { public void testParseTypicalWithComments() throws IOException {
WebvttParser parser = new WebvttParser(); WebvttParser parser = new WebvttParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_WITH_COMMENTS_FILE); 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 // test event count
assertEquals(4, subtitle.getEventTimeCount()); assertEquals(4, subtitle.getEventTimeCount());
...@@ -91,7 +91,7 @@ public class WebvttParserTest extends InstrumentationTestCase { ...@@ -91,7 +91,7 @@ public class WebvttParserTest extends InstrumentationTestCase {
public void testParseWithTags() throws IOException { public void testParseWithTags() throws IOException {
WebvttParser parser = new WebvttParser(); WebvttParser parser = new WebvttParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), WITH_TAGS_FILE); 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 // test event count
assertEquals(8, subtitle.getEventTimeCount()); assertEquals(8, subtitle.getEventTimeCount());
...@@ -106,7 +106,7 @@ public class WebvttParserTest extends InstrumentationTestCase { ...@@ -106,7 +106,7 @@ public class WebvttParserTest extends InstrumentationTestCase {
public void testParseWithPositioning() throws IOException { public void testParseWithPositioning() throws IOException {
WebvttParser parser = new WebvttParser(); WebvttParser parser = new WebvttParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), WITH_POSITIONING_FILE); 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 // test event count
assertEquals(12, subtitle.getEventTimeCount()); assertEquals(12, subtitle.getEventTimeCount());
...@@ -134,7 +134,7 @@ public class WebvttParserTest extends InstrumentationTestCase { ...@@ -134,7 +134,7 @@ public class WebvttParserTest extends InstrumentationTestCase {
public void testParseWithBadCueHeader() throws IOException { public void testParseWithBadCueHeader() throws IOException {
WebvttParser parser = new WebvttParser(); WebvttParser parser = new WebvttParser();
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), WITH_BAD_CUE_HEADER_FILE); 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 // test event count
assertEquals(4, subtitle.getEventTimeCount()); assertEquals(4, subtitle.getEventTimeCount());
......
/*
* 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);
}
}
package com.google.android.exoplayer.text;
/* /*
* Copyright (C) 2014 The Android Open Source Project * Copyright (C) 2014 The Android Open Source Project
* *
...@@ -15,36 +13,32 @@ package com.google.android.exoplayer.text; ...@@ -15,36 +13,32 @@ package com.google.android.exoplayer.text;
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * 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; import java.util.List;
/** /**
* A subtitle that wraps another subtitle, making it playable by adjusting it to be correctly * A {@link Subtitle} output from a {@link SubtitleParser}.
* aligned with the playback timebase.
*/ */
/* package */ final class PlayableSubtitle implements Subtitle { /* package */ final class SubtitleOutputBuffer extends OutputBuffer implements Subtitle {
/** private final SubtitleParser owner;
* The start time of the subtitle.
* <p>
* 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 Subtitle subtitle; private Subtitle subtitle;
private final long offsetUs; private long offsetUs;
/** public SubtitleOutputBuffer(SubtitleParser owner) {
* @param subtitle The subtitle to wrap. this.owner = owner;
* @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. public void setOutput(long timestampUs, Subtitle subtitle, long subsampleOffsetUs) {
* @param offsetUs An offset to add to the subtitle timestamps. this.timestampUs = timestampUs;
*/
public PlayableSubtitle(Subtitle subtitle, boolean isRelative, long startTimeUs, long offsetUs) {
this.subtitle = subtitle; this.subtitle = subtitle;
this.startTimeUs = startTimeUs; this.offsetUs = subsampleOffsetUs == Format.OFFSET_SAMPLE_RELATIVE ? timestampUs
this.offsetUs = (isRelative ? startTimeUs : 0) + offsetUs; : subsampleOffsetUs;
} }
@Override @Override
...@@ -72,4 +66,9 @@ import java.util.List; ...@@ -72,4 +66,9 @@ import java.util.List;
return subtitle.getCues(timeUs - offsetUs); return subtitle.getCues(timeUs - offsetUs);
} }
@Override
public void release() {
owner.releaseOutputBuffer(this);
}
} }
...@@ -16,29 +16,48 @@ ...@@ -16,29 +16,48 @@
package com.google.android.exoplayer.text; package com.google.android.exoplayer.text;
import com.google.android.exoplayer.ParserException; 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<SubtitleInputBuffer, SubtitleOutputBuffer, ParserException> {
/**
* Checks whether the parser supports a given subtitle mime type. protected SubtitleParser() {
* super(new SubtitleInputBuffer[2], new SubtitleOutputBuffer[2]);
* @param mimeType A subtitle mime type. setInitialInputBufferSize(1024);
* @return Whether the mime type is supported. }
*/
public boolean canParse(String mimeType); @Override
protected final SubtitleInputBuffer createInputBuffer() {
/** return new SubtitleInputBuffer();
* Parses a {@link Subtitle} from the provided {@code byte[]}. }
*
* @param bytes The array holding the subtitle data. @Override
* @param offset The offset of the subtitle data in bytes. protected final SubtitleOutputBuffer createOutputBuffer() {
* @param length The length of the subtitle data in bytes. return new SubtitleOutputBuffer(this);
* @return A parsed representation of the subtitle. }
* @throws ParserException If a problem occurred parsing the subtitle data.
*/ @Override
public Subtitle parse(byte[] bytes, int offset, int length) throws ParserException; 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;
} }
/*
* 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.
* <p>
* The formats supported by this factory are:
* <ul>
* <li>WebVTT ({@link com.google.android.exoplayer.text.webvtt.WebvttParser})</li>
* <li>WebVTT (MP4) ({@link com.google.android.exoplayer.text.webvtt.Mp4WebvttParser})</li>
* <li>TTML ({@link com.google.android.exoplayer.text.ttml.TtmlParser})</li>
* <li>SubRip ({@link com.google.android.exoplayer.text.subrip.SubripParser})</li>
* <li>TX3G ({@link com.google.android.exoplayer.text.tx3g.Tx3gParser})</li>
* </ul>
*/
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;
}
}
};
}
/*
* 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.
* <p>
* 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.
* <p>
* 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.
* <p>
* 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;
}
}
}
}
...@@ -18,6 +18,7 @@ package com.google.android.exoplayer.text; ...@@ -18,6 +18,7 @@ package com.google.android.exoplayer.text;
import com.google.android.exoplayer.ExoPlaybackException; import com.google.android.exoplayer.ExoPlaybackException;
import com.google.android.exoplayer.Format; import com.google.android.exoplayer.Format;
import com.google.android.exoplayer.FormatHolder; import com.google.android.exoplayer.FormatHolder;
import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.SampleHolder; import com.google.android.exoplayer.SampleHolder;
import com.google.android.exoplayer.SampleSourceTrackRenderer; import com.google.android.exoplayer.SampleSourceTrackRenderer;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
...@@ -28,158 +29,97 @@ import com.google.android.exoplayer.util.MimeTypes; ...@@ -28,158 +29,97 @@ import com.google.android.exoplayer.util.MimeTypes;
import android.annotation.TargetApi; import android.annotation.TargetApi;
import android.os.Handler; import android.os.Handler;
import android.os.Handler.Callback; import android.os.Handler.Callback;
import android.os.HandlerThread;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
/** /**
* A {@link TrackRenderer} for subtitles. Text is parsed from sample data using a * A {@link TrackRenderer} for subtitles.
* {@link SubtitleParser}. The actual rendering of each line of text is delegated to a
* {@link TextRenderer}.
* <p> * <p>
* If no {@link SubtitleParser} instances are passed to the constructor, the subtitle type will be * Text is parsed from sample data using {@link SubtitleParser} instances obtained from a
* detected automatically for the following supported formats: * {@link SubtitleParserFactory}. The actual rendering of each line of text is delegated to a
* * {@link TextRenderer}.
* <ul>
* <li>WebVTT ({@link com.google.android.exoplayer.text.webvtt.WebvttParser})</li>
* <li>TTML
* ({@link com.google.android.exoplayer.text.ttml.TtmlParser})</li>
* <li>SubRip
* ({@link com.google.android.exoplayer.text.subrip.SubripParser})</li>
* <li>TX3G
* ({@link com.google.android.exoplayer.text.tx3g.Tx3gParser})</li>
* </ul>
*
* <p>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.
*/ */
@TargetApi(16) @TargetApi(16)
public final class TextTrackRenderer extends SampleSourceTrackRenderer implements Callback { public final class TextTrackRenderer extends SampleSourceTrackRenderer implements Callback {
private static final int MSG_UPDATE_OVERLAY = 0; 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<Class<? extends SubtitleParser>> DEFAULT_PARSER_CLASSES;
static {
DEFAULT_PARSER_CLASSES = new ArrayList<>();
// Load parsers using reflection so that they can be deleted cleanly.
// Class.forName(<class name>) 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 Handler textRendererHandler;
private final TextRenderer textRenderer; private final TextRenderer textRenderer;
private final SubtitleParserFactory parserFactory;
private final FormatHolder formatHolder; private final FormatHolder formatHolder;
private final SubtitleParser[] subtitleParsers;
private int parserIndex;
private boolean inputStreamEnded; private boolean inputStreamEnded;
private PlayableSubtitle subtitle; private SubtitleParser parser;
private PlayableSubtitle nextSubtitle; private SubtitleInputBuffer nextInputBuffer;
private SubtitleParserHelper parserHelper; private SubtitleOutputBuffer subtitle;
private HandlerThread parserThread; private SubtitleOutputBuffer nextSubtitle;
private int nextSubtitleEventIndex; private int nextSubtitleEventIndex;
/** /**
* @param textRenderer The text renderer. * @param textRenderer The text renderer.
* @param textRendererLooper The looper associated with the thread on which textRenderer should be * @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 * 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 * normally be the looper associated with the application's main thread, which can be obtained
* obtained using {@link android.app.Activity#getMainLooper()}. Null may be passed if the * using {@link android.app.Activity#getMainLooper()}. Null may be passed if the renderer
* renderer should be invoked directly on the player's internal rendering thread. * 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. 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, public TextTrackRenderer(TextRenderer textRenderer, Looper textRendererLooper,
SubtitleParser... subtitleParsers) { SubtitleParserFactory parserFactory) {
this.textRenderer = Assertions.checkNotNull(textRenderer); this.textRenderer = Assertions.checkNotNull(textRenderer);
this.textRendererHandler = textRendererLooper == null ? null this.textRendererHandler = textRendererLooper == null ? null
: new Handler(textRendererLooper, this); : new Handler(textRendererLooper, this);
if (subtitleParsers == null || subtitleParsers.length == 0) { this.parserFactory = parserFactory;
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;
formatHolder = new FormatHolder(); formatHolder = new FormatHolder();
} }
@Override @Override
protected int supportsFormat(Format format) { 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 : (MimeTypes.isText(format.sampleMimeType) ? FORMAT_UNSUPPORTED_SUBTYPE
: FORMAT_UNSUPPORTED_TYPE); : FORMAT_UNSUPPORTED_TYPE);
} }
@Override @Override
protected void onEnabled(Format[] formats, TrackStream trackStream, long positionUs, protected void onEnabled(Format[] formats, TrackStream trackStream, long positionUs,
boolean joining) throws ExoPlaybackException { boolean joining) throws ExoPlaybackException {
super.onEnabled(formats, trackStream, positionUs, joining); super.onEnabled(formats, trackStream, positionUs, joining);
parserIndex = getParserIndex(formats[0].sampleMimeType); parser = parserFactory.createParser(formats[0]);
parserThread = new HandlerThread("textParser"); parser.start();
parserThread.start();
parserHelper = new SubtitleParserHelper(parserThread.getLooper(), subtitleParsers[parserIndex]);
} }
@Override @Override
protected void reset(long positionUs) { protected void reset(long positionUs) {
inputStreamEnded = false; inputStreamEnded = false;
subtitle = null; if (subtitle != null) {
nextSubtitle = null; subtitle.release();
subtitle = null;
}
if (nextSubtitle != null) {
nextSubtitle.release();
nextSubtitle = null;
}
nextInputBuffer = null;
clearTextRenderer(); clearTextRenderer();
if (parserHelper != null) { if (parser != null) {
parserHelper.flush(); parser.flush();
} }
} }
...@@ -188,7 +128,7 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement ...@@ -188,7 +128,7 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
throws ExoPlaybackException { throws ExoPlaybackException {
if (nextSubtitle == null) { if (nextSubtitle == null) {
try { try {
nextSubtitle = parserHelper.getAndClearResult(); nextSubtitle = parser.dequeueOutputBuffer();
} catch (IOException e) { } catch (IOException e) {
throw ExoPlaybackException.createForRenderer(e, getIndex()); throw ExoPlaybackException.createForRenderer(e, getIndex());
} }
...@@ -211,8 +151,11 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement ...@@ -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. // Advance to the next subtitle. Sync the next event index and trigger an update.
if (subtitle != null) {
subtitle.release();
}
subtitle = nextSubtitle; subtitle = nextSubtitle;
nextSubtitle = null; nextSubtitle = null;
nextSubtitleEventIndex = subtitle.getNextEventTimeIndex(positionUs); nextSubtitleEventIndex = subtitle.getNextEventTimeIndex(positionUs);
...@@ -224,26 +167,45 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement ...@@ -224,26 +167,45 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
updateTextRenderer(subtitle.getCues(positionUs)); updateTextRenderer(subtitle.getCues(positionUs));
} }
if (!inputStreamEnded && nextSubtitle == null && !parserHelper.isParsing()) { try {
// Try and read the next subtitle from the source. if (!inputStreamEnded && nextSubtitle == null) {
SampleHolder sampleHolder = parserHelper.getSampleHolder(); if (nextInputBuffer == null) {
sampleHolder.clearData(); nextInputBuffer = parser.dequeueInputBuffer();
int result = readSource(formatHolder, sampleHolder); if (nextInputBuffer == null) {
if (result == TrackStream.SAMPLE_READ) { return;
parserHelper.startParseOperation(); }
} else if (result == TrackStream.END_OF_STREAM) { }
inputStreamEnded = true; // 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 @Override
protected void onDisabled() throws ExoPlaybackException { protected void onDisabled() throws ExoPlaybackException {
subtitle = null; if (subtitle != null) {
nextSubtitle = null; subtitle.release();
parserThread.quit(); subtitle = null;
parserThread = null; }
parserHelper = null; if (nextSubtitle != null) {
nextSubtitle.release();
nextSubtitle = null;
}
if (parser != null) {
parser.release();
parser = null;
}
nextInputBuffer = null;
clearTextRenderer(); clearTextRenderer();
super.onDisabled(); super.onDisabled();
} }
...@@ -293,13 +255,4 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement ...@@ -293,13 +255,4 @@ public final class TextTrackRenderer extends SampleSourceTrackRenderer implement
textRenderer.onCues(cues); 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;
}
} }
...@@ -18,7 +18,6 @@ package com.google.android.exoplayer.text.subrip; ...@@ -18,7 +18,6 @@ package com.google.android.exoplayer.text.subrip;
import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.SubtitleParser; import com.google.android.exoplayer.text.SubtitleParser;
import com.google.android.exoplayer.util.LongArray; import com.google.android.exoplayer.util.LongArray;
import com.google.android.exoplayer.util.MimeTypes;
import com.google.android.exoplayer.util.ParsableByteArray; import com.google.android.exoplayer.util.ParsableByteArray;
import android.text.Html; import android.text.Html;
...@@ -33,7 +32,7 @@ import java.util.regex.Pattern; ...@@ -33,7 +32,7 @@ import java.util.regex.Pattern;
/** /**
* A simple SubRip parser. * A simple SubRip parser.
*/ */
public final class SubripParser implements SubtitleParser { public final class SubripParser extends SubtitleParser {
private static final String TAG = "SubripParser"; private static final String TAG = "SubripParser";
...@@ -48,16 +47,10 @@ public final class SubripParser implements SubtitleParser { ...@@ -48,16 +47,10 @@ public final class SubripParser implements SubtitleParser {
} }
@Override @Override
public boolean canParse(String mimeType) { protected SubripSubtitle decode(byte[] bytes, int length) {
return MimeTypes.APPLICATION_SUBRIP.equals(mimeType);
}
@Override
public SubripSubtitle parse(byte[] bytes, int offset, int length) {
ArrayList<Cue> cues = new ArrayList<>(); ArrayList<Cue> cues = new ArrayList<>();
LongArray cueTimesUs = new LongArray(); LongArray cueTimesUs = new LongArray();
ParsableByteArray subripData = new ParsableByteArray(bytes, offset + length); ParsableByteArray subripData = new ParsableByteArray(bytes, length);
subripData.setPosition(offset);
boolean haveEndTimecode; boolean haveEndTimecode;
String currentLine; String currentLine;
......
...@@ -18,7 +18,6 @@ package com.google.android.exoplayer.text.ttml; ...@@ -18,7 +18,6 @@ package com.google.android.exoplayer.text.ttml;
import com.google.android.exoplayer.C; import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.text.SubtitleParser; 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.ParserUtil;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
...@@ -58,7 +57,7 @@ import java.util.regex.Pattern; ...@@ -58,7 +57,7 @@ import java.util.regex.Pattern;
* </p> * </p>
* @see <a href="http://www.w3.org/TR/ttaf1-dfxp/">TTML specification</a> * @see <a href="http://www.w3.org/TR/ttaf1-dfxp/">TTML specification</a>
*/ */
public final class TtmlParser implements SubtitleParser { public final class TtmlParser extends SubtitleParser {
private static final String TAG = "TtmlParser"; private static final String TAG = "TtmlParser";
...@@ -91,16 +90,11 @@ public final class TtmlParser implements SubtitleParser { ...@@ -91,16 +90,11 @@ public final class TtmlParser implements SubtitleParser {
} }
@Override @Override
public boolean canParse(String mimeType) { protected TtmlSubtitle decode(byte[] bytes, int length) throws ParserException {
return MimeTypes.APPLICATION_TTML.equals(mimeType);
}
@Override
public TtmlSubtitle parse(byte[] bytes, int offset, int length) throws ParserException {
try { try {
XmlPullParser xmlParser = xmlParserFactory.newPullParser(); XmlPullParser xmlParser = xmlParserFactory.newPullParser();
Map<String, TtmlStyle> globalStyles = new HashMap<>(); Map<String, TtmlStyle> globalStyles = new HashMap<>();
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, offset, length); ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length);
xmlParser.setInput(inputStream, null); xmlParser.setInput(inputStream, null);
TtmlSubtitle ttmlSubtitle = null; TtmlSubtitle ttmlSubtitle = null;
LinkedList<TtmlNode> nodeStack = new LinkedList<>(); LinkedList<TtmlNode> nodeStack = new LinkedList<>();
......
...@@ -18,23 +18,17 @@ package com.google.android.exoplayer.text.tx3g; ...@@ -18,23 +18,17 @@ package com.google.android.exoplayer.text.tx3g;
import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.Subtitle; import com.google.android.exoplayer.text.Subtitle;
import com.google.android.exoplayer.text.SubtitleParser; import com.google.android.exoplayer.text.SubtitleParser;
import com.google.android.exoplayer.util.MimeTypes;
/** /**
* A {@link SubtitleParser} for tx3g. * A {@link SubtitleParser} for tx3g.
* <p> * <p>
* Currently only supports parsing of a single text track. * Currently only supports parsing of a single text track.
*/ */
public final class Tx3gParser implements SubtitleParser { public final class Tx3gParser extends SubtitleParser {
@Override @Override
public boolean canParse(String mimeType) { protected Subtitle decode(byte[] bytes, int length) {
return MimeTypes.APPLICATION_TX3G.equals(mimeType); String cueText = new String(bytes, 0, length);
}
@Override
public Subtitle parse(byte[] bytes, int offset, int length) {
String cueText = new String(bytes, offset, length);
return new Tx3gSubtitle(new Cue(cueText)); return new Tx3gSubtitle(new Cue(cueText));
} }
......
...@@ -18,7 +18,6 @@ package com.google.android.exoplayer.text.webvtt; ...@@ -18,7 +18,6 @@ package com.google.android.exoplayer.text.webvtt;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.text.Cue; import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.SubtitleParser; 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.ParsableByteArray;
import com.google.android.exoplayer.util.Util; import com.google.android.exoplayer.util.Util;
...@@ -28,7 +27,7 @@ import java.util.List; ...@@ -28,7 +27,7 @@ import java.util.List;
/** /**
* A {@link SubtitleParser} for Webvtt embedded in a Mp4 container file. * 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; private static final int BOX_HEADER_SIZE = 8;
...@@ -45,16 +44,10 @@ public final class Mp4WebvttParser implements SubtitleParser { ...@@ -45,16 +44,10 @@ public final class Mp4WebvttParser implements SubtitleParser {
} }
@Override @Override
public boolean canParse(String mimeType) { protected Mp4WebvttSubtitle decode(byte[] bytes, int length) throws ParserException {
return MimeTypes.APPLICATION_MP4VTT.equals(mimeType);
}
@Override
public Mp4WebvttSubtitle parse(byte[] bytes, int offset, int length) throws ParserException {
// Webvtt in Mp4 samples have boxes inside of them, so we have to do a traditional box parsing: // 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. // first 4 bytes size and then 4 bytes type.
sampleData.reset(bytes, offset + length); sampleData.reset(bytes, length);
sampleData.setPosition(offset);
List<Cue> resultingCueList = new ArrayList<>(); List<Cue> resultingCueList = new ArrayList<>();
while (sampleData.bytesLeft() > 0) { while (sampleData.bytesLeft() > 0) {
if (sampleData.bytesLeft() < BOX_HEADER_SIZE) { if (sampleData.bytesLeft() < BOX_HEADER_SIZE) {
......
...@@ -17,7 +17,6 @@ package com.google.android.exoplayer.text.webvtt; ...@@ -17,7 +17,6 @@ package com.google.android.exoplayer.text.webvtt;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.text.SubtitleParser; 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.ParsableByteArray;
import android.text.TextUtils; import android.text.TextUtils;
...@@ -29,7 +28,7 @@ import java.util.ArrayList; ...@@ -29,7 +28,7 @@ import java.util.ArrayList;
* <p> * <p>
* @see <a href="http://dev.w3.org/html5/webvtt">WebVTT specification</a> * @see <a href="http://dev.w3.org/html5/webvtt">WebVTT specification</a>
*/ */
public final class WebvttParser implements SubtitleParser { public final class WebvttParser extends SubtitleParser {
private final WebvttCueParser cueParser; private final WebvttCueParser cueParser;
private final ParsableByteArray parsableWebvttData; private final ParsableByteArray parsableWebvttData;
...@@ -42,14 +41,8 @@ public final class WebvttParser implements SubtitleParser { ...@@ -42,14 +41,8 @@ public final class WebvttParser implements SubtitleParser {
} }
@Override @Override
public final boolean canParse(String mimeType) { protected final WebvttSubtitle decode(byte[] bytes, int length) throws ParserException {
return MimeTypes.TEXT_VTT.equals(mimeType); parsableWebvttData.reset(bytes, length);
}
@Override
public final WebvttSubtitle parse(byte[] bytes, int offset, int length) throws ParserException {
parsableWebvttData.reset(bytes, offset + length);
parsableWebvttData.setPosition(offset);
webvttCueBuilder.reset(); // In case a previous parse run failed with a ParserException. webvttCueBuilder.reset(); // In case a previous parse run failed with a ParserException.
// Validate the first line of the header, and skip the remainder. // Validate the first line of the header, and skip the remainder.
......
...@@ -25,7 +25,11 @@ public class InputBuffer extends Buffer { ...@@ -25,7 +25,11 @@ public class InputBuffer extends Buffer {
public final SampleHolder sampleHolder; public final SampleHolder sampleHolder;
public InputBuffer() { 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 @Override
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册