mirror of
https://github.com/androidx/media.git
synced 2025-05-12 18:19:50 +08:00
Use SimpleDecoder for subtitles.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=117543706
This commit is contained in:
parent
ed4f83979e
commit
0dc0d70397
@ -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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
// 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 {
|
|||||||
// 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 {
|
|||||||
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 {
|
|||||||
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;
|
|||||||
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 {
|
|||||||
// 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 {
|
|||||||
|
|
||||||
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 {
|
|||||||
* 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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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 {
|
|||||||
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());
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,5 +1,3 @@
|
|||||||
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;
|
|||||||
* 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;
|
|||||||
return subtitle.getCues(timeUs - offsetUs);
|
return subtitle.getCues(timeUs - offsetUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
owner.releaseOutputBuffer(this);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -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> {
|
||||||
|
|
||||||
/**
|
protected SubtitleParser() {
|
||||||
* Checks whether the parser supports a given subtitle mime type.
|
super(new SubtitleInputBuffer[2], new SubtitleOutputBuffer[2]);
|
||||||
*
|
setInitialInputBufferSize(1024);
|
||||||
* @param mimeType A subtitle mime type.
|
}
|
||||||
* @return Whether the mime type is supported.
|
|
||||||
*/
|
|
||||||
public boolean canParse(String mimeType);
|
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Parses a {@link Subtitle} from the provided {@code byte[]}.
|
protected final SubtitleInputBuffer createInputBuffer() {
|
||||||
*
|
return new SubtitleInputBuffer();
|
||||||
* @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.
|
@Override
|
||||||
* @return A parsed representation of the subtitle.
|
protected final SubtitleOutputBuffer createOutputBuffer() {
|
||||||
* @throws ParserException If a problem occurred parsing the subtitle data.
|
return new SubtitleOutputBuffer(this);
|
||||||
*/
|
}
|
||||||
public Subtitle parse(byte[] bytes, int offset, int length) throws ParserException;
|
|
||||||
|
@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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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.
|
||||||
|
* <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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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.
|
|
||||||
* <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;
|
|||||||
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;
|
|||||||
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
|
|||||||
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
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|||||||
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
|
|||||||
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;
|
|||||||
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;
|
|||||||
/**
|
/**
|
||||||
* 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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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;
|
|||||||
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;
|
|||||||
* </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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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;
|
|||||||
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;
|
|||||||
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;
|
|||||||
/**
|
/**
|
||||||
* 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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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;
|
|||||||
|
|
||||||
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;
|
|||||||
* <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 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
|||||||
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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user