mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +00:00
Merge branch 'jcable-dev-v2' into dev-v2
This commit is contained in:
commit
5ebbb6ef45
14 changed files with 579 additions and 46 deletions
0
library/core/src/androidTest/assets/ssa/empty
Normal file
0
library/core/src/androidTest/assets/ssa/empty
Normal file
12
library/core/src/androidTest/assets/ssa/invalid_timecodes
Normal file
12
library/core/src/androidTest/assets/ssa/invalid_timecodes
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[Script Info]
|
||||
Title: SomeTitle
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||
Style: Default,Open Sans Semibold,36,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,-1,0,0,0,100,100,0,0,1,1.7,0,2,0,0,28,1
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, Text
|
||||
Dialogue: 0,Invalid,0:00:01.23,Default,Olly,This is the first subtitle{ignored}.
|
||||
Dialogue: 0,0:00:02.34,Invalid,Default,Olly,This is the second subtitle \nwith a newline \Nand another.
|
||||
Dialogue: 0,0:00:04:56,0:00:08:90,Default,Olly,This is the third subtitle, with a comma.
|
||||
12
library/core/src/androidTest/assets/ssa/no_end_timecodes
Normal file
12
library/core/src/androidTest/assets/ssa/no_end_timecodes
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[Script Info]
|
||||
Title: SomeTitle
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||
Style: Default,Open Sans Semibold,36,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,-1,0,0,0,100,100,0,0,1,1.7,0,2,0,0,28,1
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, Text
|
||||
Dialogue: 0,0:00:00.00, ,Default,Olly,This is the first subtitle.
|
||||
Dialogue: 0,0:00:02.34, ,Default,Olly,This is the second subtitle \nwith a newline \Nand another.
|
||||
Dialogue: 0,0:00:04.56, ,Default,Olly,This is the third subtitle, with a comma.
|
||||
12
library/core/src/androidTest/assets/ssa/typical
Normal file
12
library/core/src/androidTest/assets/ssa/typical
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[Script Info]
|
||||
Title: SomeTitle
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||
Style: Default,Open Sans Semibold,36,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,-1,0,0,0,100,100,0,0,1,1.7,0,2,0,0,28,1
|
||||
|
||||
[Events]
|
||||
Format: Layer, Start, End, Style, Name, Text
|
||||
Dialogue: 0,0:00:00.00,0:00:01.23,Default,Olly,This is the first subtitle{ignored}.
|
||||
Dialogue: 0,0:00:02.34,0:00:03.45,Default,Olly,This is the second subtitle \nwith a newline \Nand another.
|
||||
Dialogue: 0,0:00:04:56,0:00:08:90,Default,Olly,This is the third subtitle, with a comma.
|
||||
3
library/core/src/androidTest/assets/ssa/typical_dialogue
Normal file
3
library/core/src/androidTest/assets/ssa/typical_dialogue
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
Dialogue: 0,0:00:00.00,0:00:01.23,Default,Olly,This is the first subtitle{ignored}.
|
||||
Dialogue: 0,0:00:02.34,0:00:03.45,Default,Olly,This is the second subtitle \nwith a newline \Nand another.
|
||||
Dialogue: 0,0:00:04:56,0:00:08:90,Default,Olly,This is the third subtitle, with a comma.
|
||||
1
library/core/src/androidTest/assets/ssa/typical_format
Normal file
1
library/core/src/androidTest/assets/ssa/typical_format
Normal file
|
|
@ -0,0 +1 @@
|
|||
Format: Layer, Start, End, Style, Name, Text
|
||||
6
library/core/src/androidTest/assets/ssa/typical_header
Normal file
6
library/core/src/androidTest/assets/ssa/typical_header
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
[Script Info]
|
||||
Title: SomeTitle
|
||||
|
||||
[V4+ Styles]
|
||||
Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
|
||||
Style: Default,Open Sans Semibold,36,&H00FFFFFF,&H000000FF,&H00020713,&H00000000,-1,0,0,0,100,100,0,0,1,1.7,0,2,0,0,28,1
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* Copyright (C) 2016 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.exoplayer2.text.ssa;
|
||||
|
||||
import android.test.InstrumentationTestCase;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Unit test for {@link SsaDecoder}.
|
||||
*/
|
||||
public final class SsaDecoderTest extends InstrumentationTestCase {
|
||||
|
||||
private static final String EMPTY = "ssa/empty";
|
||||
private static final String TYPICAL = "ssa/typical";
|
||||
private static final String TYPICAL_HEADER_ONLY = "ssa/typical_header";
|
||||
private static final String TYPICAL_DIALOGUE_ONLY = "ssa/typical_dialogue";
|
||||
private static final String TYPICAL_FORMAT_ONLY = "ssa/typical_format";
|
||||
private static final String INVALID_TIMECODES = "ssa/invalid_timecodes";
|
||||
private static final String NO_END_TIMECODES = "ssa/no_end_timecodes";
|
||||
|
||||
public void testDecodeEmpty() throws IOException {
|
||||
SsaDecoder decoder = new SsaDecoder();
|
||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY);
|
||||
SsaSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||
|
||||
assertEquals(0, subtitle.getEventTimeCount());
|
||||
assertTrue(subtitle.getCues(0).isEmpty());
|
||||
}
|
||||
|
||||
public void testDecodeTypical() throws IOException {
|
||||
SsaDecoder decoder = new SsaDecoder();
|
||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL);
|
||||
SsaSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||
|
||||
assertEquals(6, subtitle.getEventTimeCount());
|
||||
assertTypicalCue1(subtitle, 0);
|
||||
assertTypicalCue2(subtitle, 2);
|
||||
assertTypicalCue3(subtitle, 4);
|
||||
}
|
||||
|
||||
public void testDecodeTypicalWithInitializationData() throws IOException {
|
||||
byte[] headerBytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_HEADER_ONLY);
|
||||
byte[] formatBytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_FORMAT_ONLY);
|
||||
ArrayList<byte[]> initializationData = new ArrayList<>();
|
||||
initializationData.add(formatBytes);
|
||||
initializationData.add(headerBytes);
|
||||
SsaDecoder decoder = new SsaDecoder(initializationData);
|
||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_DIALOGUE_ONLY);
|
||||
SsaSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||
|
||||
assertEquals(6, subtitle.getEventTimeCount());
|
||||
assertTypicalCue1(subtitle, 0);
|
||||
assertTypicalCue2(subtitle, 2);
|
||||
assertTypicalCue3(subtitle, 4);
|
||||
}
|
||||
|
||||
public void testDecodeInvalidTimecodes() throws IOException {
|
||||
// Parsing should succeed, parsing the third cue only.
|
||||
SsaDecoder decoder = new SsaDecoder();
|
||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), INVALID_TIMECODES);
|
||||
SsaSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||
|
||||
assertEquals(2, subtitle.getEventTimeCount());
|
||||
assertTypicalCue3(subtitle, 0);
|
||||
}
|
||||
|
||||
public void testDecodeNoEndTimecodes() throws IOException {
|
||||
SsaDecoder decoder = new SsaDecoder();
|
||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_END_TIMECODES);
|
||||
SsaSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||
|
||||
assertEquals(3, subtitle.getEventTimeCount());
|
||||
|
||||
assertEquals(0, subtitle.getEventTime(0));
|
||||
assertEquals("This is the first subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString());
|
||||
|
||||
assertEquals(2340000, subtitle.getEventTime(1));
|
||||
assertEquals("This is the second subtitle \nwith a newline \nand another.",
|
||||
subtitle.getCues(subtitle.getEventTime(1)).get(0).text.toString());
|
||||
|
||||
assertEquals(4560000, subtitle.getEventTime(2));
|
||||
assertEquals("This is the third subtitle, with a comma.",
|
||||
subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString());
|
||||
}
|
||||
|
||||
private static void assertTypicalCue1(SsaSubtitle subtitle, int eventIndex) {
|
||||
assertEquals(0, subtitle.getEventTime(eventIndex));
|
||||
assertEquals("This is the first subtitle.",
|
||||
subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString());
|
||||
assertEquals(1230000, subtitle.getEventTime(eventIndex + 1));
|
||||
}
|
||||
|
||||
private static void assertTypicalCue2(SsaSubtitle subtitle, int eventIndex) {
|
||||
assertEquals(2340000, subtitle.getEventTime(eventIndex));
|
||||
assertEquals("This is the second subtitle \nwith a newline \nand another.",
|
||||
subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString());
|
||||
assertEquals(3450000, subtitle.getEventTime(eventIndex + 1));
|
||||
}
|
||||
|
||||
private static void assertTypicalCue3(SsaSubtitle subtitle, int eventIndex) {
|
||||
assertEquals(4560000, subtitle.getEventTime(eventIndex));
|
||||
assertEquals("This is the third subtitle, with a comma.",
|
||||
subtitle.getCues(subtitle.getEventTime(eventIndex)).get(0).text.toString());
|
||||
assertEquals(8900000, subtitle.getEventTime(eventIndex + 1));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
|
|||
SubripDecoder decoder = new SubripDecoder();
|
||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), EMPTY_FILE);
|
||||
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||
// Assert that the subtitle is empty.
|
||||
|
||||
assertEquals(0, subtitle.getEventTimeCount());
|
||||
assertTrue(subtitle.getCues(0).isEmpty());
|
||||
}
|
||||
|
|
@ -46,6 +46,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
|
|||
SubripDecoder decoder = new SubripDecoder();
|
||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_FILE);
|
||||
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||
|
||||
assertEquals(6, subtitle.getEventTimeCount());
|
||||
assertTypicalCue1(subtitle, 0);
|
||||
assertTypicalCue2(subtitle, 2);
|
||||
|
|
@ -56,6 +57,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
|
|||
SubripDecoder decoder = new SubripDecoder();
|
||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_WITH_BYTE_ORDER_MARK);
|
||||
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||
|
||||
assertEquals(6, subtitle.getEventTimeCount());
|
||||
assertTypicalCue1(subtitle, 0);
|
||||
assertTypicalCue2(subtitle, 2);
|
||||
|
|
@ -66,6 +68,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
|
|||
SubripDecoder decoder = new SubripDecoder();
|
||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_EXTRA_BLANK_LINE);
|
||||
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||
|
||||
assertEquals(6, subtitle.getEventTimeCount());
|
||||
assertTypicalCue1(subtitle, 0);
|
||||
assertTypicalCue2(subtitle, 2);
|
||||
|
|
@ -77,6 +80,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
|
|||
SubripDecoder decoder = new SubripDecoder();
|
||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_MISSING_TIMECODE);
|
||||
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||
|
||||
assertEquals(4, subtitle.getEventTimeCount());
|
||||
assertTypicalCue1(subtitle, 0);
|
||||
assertTypicalCue3(subtitle, 2);
|
||||
|
|
@ -87,6 +91,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
|
|||
SubripDecoder decoder = new SubripDecoder();
|
||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_MISSING_SEQUENCE);
|
||||
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||
|
||||
assertEquals(4, subtitle.getEventTimeCount());
|
||||
assertTypicalCue1(subtitle, 0);
|
||||
assertTypicalCue3(subtitle, 2);
|
||||
|
|
@ -97,6 +102,7 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
|
|||
SubripDecoder decoder = new SubripDecoder();
|
||||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), TYPICAL_NEGATIVE_TIMESTAMPS);
|
||||
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||
|
||||
assertEquals(2, subtitle.getEventTimeCount());
|
||||
assertTypicalCue3(subtitle, 0);
|
||||
}
|
||||
|
|
@ -106,20 +112,16 @@ public final class SubripDecoderTest extends InstrumentationTestCase {
|
|||
byte[] bytes = TestUtil.getByteArray(getInstrumentation(), NO_END_TIMECODES_FILE);
|
||||
SubripSubtitle subtitle = decoder.decode(bytes, bytes.length, false);
|
||||
|
||||
// Test event count.
|
||||
assertEquals(3, subtitle.getEventTimeCount());
|
||||
|
||||
// Test first cue.
|
||||
assertEquals(0, subtitle.getEventTime(0));
|
||||
assertEquals("SubRip doesn't technically allow missing end timecodes.",
|
||||
subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString());
|
||||
|
||||
// Test second cue.
|
||||
assertEquals(2345000, subtitle.getEventTime(1));
|
||||
assertEquals("We interpret it to mean that a subtitle extends to the start of the next one.",
|
||||
subtitle.getCues(subtitle.getEventTime(1)).get(0).text.toString());
|
||||
|
||||
// Test third cue.
|
||||
assertEquals(3456000, subtitle.getEventTime(2));
|
||||
assertEquals("Or to the end of the media.",
|
||||
subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString());
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ public final class MatroskaExtractor implements Extractor {
|
|||
private static final String CODEC_ID_ACM = "A_MS/ACM";
|
||||
private static final String CODEC_ID_PCM_INT_LIT = "A_PCM/INT/LIT";
|
||||
private static final String CODEC_ID_SUBRIP = "S_TEXT/UTF8";
|
||||
private static final String CODEC_ID_ASS = "S_TEXT/ASS";
|
||||
private static final String CODEC_ID_VOBSUB = "S_VOBSUB";
|
||||
private static final String CODEC_ID_PGS = "S_HDMV/PGS";
|
||||
private static final String CODEC_ID_DVBSUB = "S_DVBSUB";
|
||||
|
|
@ -226,21 +227,62 @@ public final class MatroskaExtractor implements Extractor {
|
|||
private static final byte[] SUBRIP_PREFIX = new byte[] {49, 10, 48, 48, 58, 48, 48, 58, 48, 48,
|
||||
44, 48, 48, 48, 32, 45, 45, 62, 32, 48, 48, 58, 48, 48, 58, 48, 48, 44, 48, 48, 48, 10};
|
||||
/**
|
||||
* A special end timecode indicating that a subtitle should be displayed until the next subtitle,
|
||||
* or until the end of the media in the case of the last subtitle.
|
||||
* The byte offset of the end timecode in {@link #SUBRIP_PREFIX}.
|
||||
*/
|
||||
private static final int SUBRIP_PREFIX_END_TIMECODE_OFFSET = 19;
|
||||
/**
|
||||
* A special end timecode indicating that a subrip subtitle should be displayed until the next
|
||||
* subtitle, or until the end of the media in the case of the last subtitle.
|
||||
* <p>
|
||||
* Equivalent to the UTF-8 string: " ".
|
||||
*/
|
||||
private static final byte[] SUBRIP_TIMECODE_EMPTY =
|
||||
new byte[] {32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32};
|
||||
/**
|
||||
* The byte offset of the end timecode in {@link #SUBRIP_PREFIX}.
|
||||
* The value by which to divide a time in microseconds to convert it to the unit of the last value
|
||||
* in a subrip timecode (milliseconds).
|
||||
*/
|
||||
private static final int SUBRIP_PREFIX_END_TIMECODE_OFFSET = 19;
|
||||
private static long SUBRIP_TIMECODE_LAST_VALUE_SCALING_FACTOR = 1000;
|
||||
/**
|
||||
* The length in bytes of a timecode in a subrip prefix.
|
||||
* The format of a subrip timecode.
|
||||
*/
|
||||
private static final int SUBRIP_TIMECODE_LENGTH = 12;
|
||||
private static final String SUBRIP_TIMECODE_FORMAT = "%02d:%02d:%02d,%03d";
|
||||
|
||||
/**
|
||||
* Matroska specific format line for SSA subtitles.
|
||||
*/
|
||||
private static final byte[] SSA_DIALOGUE_FORMAT = Util.getUtf8Bytes("Format: Start, End, "
|
||||
+ "ReadOrder, Layer, Style, Name, MarginL, MarginR, MarginV, Effect, Text");
|
||||
/**
|
||||
* A template for the prefix that must be added to each SSA sample. The 10 byte end timecode
|
||||
* starting at {@link #SSA_PREFIX_END_TIMECODE_OFFSET} is set to a dummy value, and must be
|
||||
* replaced with the duration of the subtitle.
|
||||
* <p>
|
||||
* Equivalent to the UTF-8 string: "Dialogue: 0:00:00:00,0:00:00:00,".
|
||||
*/
|
||||
private static final byte[] SSA_PREFIX = new byte[] {68, 105, 97, 108, 111, 103, 117, 101, 58, 32,
|
||||
48, 58, 48, 48, 58, 48, 48, 58, 48, 48, 44, 48, 58, 48, 48, 58, 48, 48, 58, 48, 48, 44};
|
||||
/**
|
||||
* The byte offset of the end timecode in {@link #SSA_PREFIX}.
|
||||
*/
|
||||
private static final int SSA_PREFIX_END_TIMECODE_OFFSET = 21;
|
||||
/**
|
||||
* The value by which to divide a time in microseconds to convert it to the unit of the last value
|
||||
* in an SSA timecode (1/100ths of a second).
|
||||
*/
|
||||
private static long SSA_TIMECODE_LAST_VALUE_SCALING_FACTOR = 10000;
|
||||
/**
|
||||
* A special end timecode indicating that an SSA subtitle should be displayed until the next
|
||||
* subtitle, or until the end of the media in the case of the last subtitle.
|
||||
* <p>
|
||||
* Equivalent to the UTF-8 string: " ".
|
||||
*/
|
||||
private static final byte[] SSA_TIMECODE_EMPTY =
|
||||
new byte[] {32, 32, 32, 32, 32, 32, 32, 32, 32, 32};
|
||||
/**
|
||||
* The format of an SSA timecode.
|
||||
*/
|
||||
private static final String SSA_TIMECODE_FORMAT = "%01d:%02d:%02d:%02d";
|
||||
|
||||
/**
|
||||
* The length in bytes of a WAVEFORMATEX structure.
|
||||
|
|
@ -271,7 +313,7 @@ public final class MatroskaExtractor implements Extractor {
|
|||
private final ParsableByteArray vorbisNumPageSamples;
|
||||
private final ParsableByteArray seekEntryIdBytes;
|
||||
private final ParsableByteArray sampleStrippedBytes;
|
||||
private final ParsableByteArray subripSample;
|
||||
private final ParsableByteArray subtitleSample;
|
||||
private final ParsableByteArray encryptionInitializationVector;
|
||||
private final ParsableByteArray encryptionSubsampleData;
|
||||
private ByteBuffer encryptionSubsampleDataBuffer;
|
||||
|
|
@ -349,7 +391,7 @@ public final class MatroskaExtractor implements Extractor {
|
|||
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
|
||||
nalLength = new ParsableByteArray(4);
|
||||
sampleStrippedBytes = new ParsableByteArray();
|
||||
subripSample = new ParsableByteArray();
|
||||
subtitleSample = new ParsableByteArray();
|
||||
encryptionInitializationVector = new ParsableByteArray(ENCRYPTION_IV_SIZE);
|
||||
encryptionSubsampleData = new ParsableByteArray();
|
||||
}
|
||||
|
|
@ -1016,7 +1058,7 @@ public final class MatroskaExtractor implements Extractor {
|
|||
// For SimpleBlock, we have metadata for each sample here.
|
||||
while (blockLacingSampleIndex < blockLacingSampleCount) {
|
||||
writeSampleData(input, track, blockLacingSampleSizes[blockLacingSampleIndex]);
|
||||
long sampleTimeUs = this.blockTimeUs
|
||||
long sampleTimeUs = blockTimeUs
|
||||
+ (blockLacingSampleIndex * track.defaultSampleDurationNs) / 1000;
|
||||
commitSampleToOutput(track, sampleTimeUs);
|
||||
blockLacingSampleIndex++;
|
||||
|
|
@ -1036,7 +1078,11 @@ public final class MatroskaExtractor implements Extractor {
|
|||
|
||||
private void commitSampleToOutput(Track track, long timeUs) {
|
||||
if (CODEC_ID_SUBRIP.equals(track.codecId)) {
|
||||
writeSubripSample(track);
|
||||
commitSubtitleSample(track, SUBRIP_TIMECODE_FORMAT, SUBRIP_PREFIX_END_TIMECODE_OFFSET,
|
||||
SUBRIP_TIMECODE_LAST_VALUE_SCALING_FACTOR, SUBRIP_TIMECODE_EMPTY);
|
||||
} else if (CODEC_ID_ASS.equals(track.codecId)) {
|
||||
commitSubtitleSample(track, SSA_TIMECODE_FORMAT, SSA_PREFIX_END_TIMECODE_OFFSET,
|
||||
SSA_TIMECODE_LAST_VALUE_SCALING_FACTOR, SSA_TIMECODE_EMPTY);
|
||||
}
|
||||
track.output.sampleMetadata(timeUs, blockFlags, sampleBytesWritten, 0, track.cryptoData);
|
||||
sampleRead = true;
|
||||
|
|
@ -1076,17 +1122,10 @@ public final class MatroskaExtractor implements Extractor {
|
|||
private void writeSampleData(ExtractorInput input, Track track, int size)
|
||||
throws IOException, InterruptedException {
|
||||
if (CODEC_ID_SUBRIP.equals(track.codecId)) {
|
||||
int sizeWithPrefix = SUBRIP_PREFIX.length + size;
|
||||
if (subripSample.capacity() < sizeWithPrefix) {
|
||||
// Initialize subripSample to contain the required prefix and have space to hold a subtitle
|
||||
// twice as long as this one.
|
||||
subripSample.data = Arrays.copyOf(SUBRIP_PREFIX, sizeWithPrefix + size);
|
||||
}
|
||||
input.readFully(subripSample.data, SUBRIP_PREFIX.length, size);
|
||||
subripSample.setPosition(0);
|
||||
subripSample.setLimit(sizeWithPrefix);
|
||||
// Defer writing the data to the track output. We need to modify the sample data by setting
|
||||
// the correct end timecode, which we might not have yet.
|
||||
writeSubtitleSampleData(input, SUBRIP_PREFIX, size);
|
||||
return;
|
||||
} else if (CODEC_ID_ASS.equals(track.codecId)) {
|
||||
writeSubtitleSampleData(input, SSA_PREFIX, size);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1230,31 +1269,50 @@ public final class MatroskaExtractor implements Extractor {
|
|||
}
|
||||
}
|
||||
|
||||
private void writeSubripSample(Track track) {
|
||||
setSubripSampleEndTimecode(subripSample.data, blockDurationUs);
|
||||
// Note: If we ever want to support DRM protected subtitles then we'll need to output the
|
||||
// appropriate encryption data here.
|
||||
track.output.sampleData(subripSample, subripSample.limit());
|
||||
sampleBytesWritten += subripSample.limit();
|
||||
private void writeSubtitleSampleData(ExtractorInput input, byte[] samplePrefix, int size)
|
||||
throws IOException, InterruptedException {
|
||||
int sizeWithPrefix = samplePrefix.length + size;
|
||||
if (subtitleSample.capacity() < sizeWithPrefix) {
|
||||
// Initialize subripSample to contain the required prefix and have space to hold a subtitle
|
||||
// twice as long as this one.
|
||||
subtitleSample.data = Arrays.copyOf(samplePrefix, sizeWithPrefix + size);
|
||||
} else {
|
||||
System.arraycopy(samplePrefix, 0, subtitleSample.data, 0, samplePrefix.length);
|
||||
}
|
||||
input.readFully(subtitleSample.data, samplePrefix.length, size);
|
||||
subtitleSample.reset(sizeWithPrefix);
|
||||
// Defer writing the data to the track output. We need to modify the sample data by setting
|
||||
// the correct end timecode, which we might not have yet.
|
||||
}
|
||||
|
||||
private static void setSubripSampleEndTimecode(byte[] subripSampleData, long timeUs) {
|
||||
private void commitSubtitleSample(Track track, String timecodeFormat, int endTimecodeOffset,
|
||||
long lastTimecodeValueScalingFactor, byte[] emptyTimecode) {
|
||||
setSampleDuration(subtitleSample.data, blockDurationUs, timecodeFormat, endTimecodeOffset,
|
||||
lastTimecodeValueScalingFactor, emptyTimecode);
|
||||
// Note: If we ever want to support DRM protected subtitles then we'll need to output the
|
||||
// appropriate encryption data here.
|
||||
track.output.sampleData(subtitleSample, subtitleSample.limit());
|
||||
sampleBytesWritten += subtitleSample.limit();
|
||||
}
|
||||
|
||||
private static void setSampleDuration(byte[] subripSampleData, long durationUs,
|
||||
String timecodeFormat, int endTimecodeOffset, long lastTimecodeValueScalingFactor,
|
||||
byte[] emptyTimecode) {
|
||||
byte[] timeCodeData;
|
||||
if (timeUs == C.TIME_UNSET) {
|
||||
timeCodeData = SUBRIP_TIMECODE_EMPTY;
|
||||
if (durationUs == C.TIME_UNSET) {
|
||||
timeCodeData = emptyTimecode;
|
||||
} else {
|
||||
int hours = (int) (timeUs / 3600000000L);
|
||||
timeUs -= (hours * 3600000000L);
|
||||
int minutes = (int) (timeUs / 60000000);
|
||||
timeUs -= (minutes * 60000000);
|
||||
int seconds = (int) (timeUs / 1000000);
|
||||
timeUs -= (seconds * 1000000);
|
||||
int milliseconds = (int) (timeUs / 1000);
|
||||
timeCodeData = Util.getUtf8Bytes(String.format(Locale.US, "%02d:%02d:%02d,%03d", hours,
|
||||
minutes, seconds, milliseconds));
|
||||
int hours = (int) (durationUs / (3600 * C.MICROS_PER_SECOND));
|
||||
durationUs -= (hours * 3600 * C.MICROS_PER_SECOND);
|
||||
int minutes = (int) (durationUs / (60 * C.MICROS_PER_SECOND));
|
||||
durationUs -= (minutes * 60 * C.MICROS_PER_SECOND);
|
||||
int seconds = (int) (durationUs / C.MICROS_PER_SECOND);
|
||||
durationUs -= (seconds * C.MICROS_PER_SECOND);
|
||||
int lastValue = (int) (durationUs / lastTimecodeValueScalingFactor);
|
||||
timeCodeData = Util.getUtf8Bytes(String.format(Locale.US, timecodeFormat, hours, minutes,
|
||||
seconds, lastValue));
|
||||
}
|
||||
System.arraycopy(timeCodeData, 0, subripSampleData, SUBRIP_PREFIX_END_TIMECODE_OFFSET,
|
||||
SUBRIP_TIMECODE_LENGTH);
|
||||
System.arraycopy(timeCodeData, 0, subripSampleData, endTimecodeOffset, emptyTimecode.length);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1385,6 +1443,7 @@ public final class MatroskaExtractor implements Extractor {
|
|||
|| CODEC_ID_ACM.equals(codecId)
|
||||
|| CODEC_ID_PCM_INT_LIT.equals(codecId)
|
||||
|| CODEC_ID_SUBRIP.equals(codecId)
|
||||
|| CODEC_ID_ASS.equals(codecId)
|
||||
|| CODEC_ID_VOBSUB.equals(codecId)
|
||||
|| CODEC_ID_PGS.equals(codecId)
|
||||
|| CODEC_ID_DVBSUB.equals(codecId);
|
||||
|
|
@ -1650,6 +1709,9 @@ public final class MatroskaExtractor implements Extractor {
|
|||
case CODEC_ID_SUBRIP:
|
||||
mimeType = MimeTypes.APPLICATION_SUBRIP;
|
||||
break;
|
||||
case CODEC_ID_ASS:
|
||||
mimeType = MimeTypes.TEXT_SSA;
|
||||
break;
|
||||
case CODEC_ID_VOBSUB:
|
||||
mimeType = MimeTypes.APPLICATION_VOBSUB;
|
||||
initializationData = Collections.singletonList(codecPrivate);
|
||||
|
|
@ -1702,6 +1764,14 @@ public final class MatroskaExtractor implements Extractor {
|
|||
type = C.TRACK_TYPE_TEXT;
|
||||
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null,
|
||||
Format.NO_VALUE, selectionFlags, language, drmInitData);
|
||||
} else if (MimeTypes.TEXT_SSA.equals(mimeType)) {
|
||||
type = C.TRACK_TYPE_TEXT;
|
||||
initializationData = new ArrayList<>(2);
|
||||
initializationData.add(SSA_DIALOGUE_FORMAT);
|
||||
initializationData.add(codecPrivate);
|
||||
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null,
|
||||
Format.NO_VALUE, selectionFlags, language, Format.NO_VALUE, drmInitData,
|
||||
Format.OFFSET_SAMPLE_RELATIVE, initializationData);
|
||||
} else if (MimeTypes.APPLICATION_VOBSUB.equals(mimeType)
|
||||
|| MimeTypes.APPLICATION_PGS.equals(mimeType)
|
||||
|| MimeTypes.APPLICATION_DVBSUBS.equals(mimeType)) {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.google.android.exoplayer2.Format;
|
|||
import com.google.android.exoplayer2.text.cea.Cea608Decoder;
|
||||
import com.google.android.exoplayer2.text.cea.Cea708Decoder;
|
||||
import com.google.android.exoplayer2.text.dvb.DvbDecoder;
|
||||
import com.google.android.exoplayer2.text.ssa.SsaDecoder;
|
||||
import com.google.android.exoplayer2.text.subrip.SubripDecoder;
|
||||
import com.google.android.exoplayer2.text.ttml.TtmlDecoder;
|
||||
import com.google.android.exoplayer2.text.tx3g.Tx3gDecoder;
|
||||
|
|
@ -58,6 +59,7 @@ public interface SubtitleDecoderFactory {
|
|||
* <li>WebVTT (MP4) ({@link Mp4WebvttDecoder})</li>
|
||||
* <li>TTML ({@link TtmlDecoder})</li>
|
||||
* <li>SubRip ({@link SubripDecoder})</li>
|
||||
* <li>SSA/ASS ({@link SsaDecoder})</li>
|
||||
* <li>TX3G ({@link Tx3gDecoder})</li>
|
||||
* <li>Cea608 ({@link Cea608Decoder})</li>
|
||||
* <li>Cea708 ({@link Cea708Decoder})</li>
|
||||
|
|
@ -70,6 +72,7 @@ public interface SubtitleDecoderFactory {
|
|||
public boolean supportsFormat(Format format) {
|
||||
String mimeType = format.sampleMimeType;
|
||||
return MimeTypes.TEXT_VTT.equals(mimeType)
|
||||
|| MimeTypes.TEXT_SSA.equals(mimeType)
|
||||
|| MimeTypes.APPLICATION_TTML.equals(mimeType)
|
||||
|| MimeTypes.APPLICATION_MP4VTT.equals(mimeType)
|
||||
|| MimeTypes.APPLICATION_SUBRIP.equals(mimeType)
|
||||
|
|
@ -85,6 +88,8 @@ public interface SubtitleDecoderFactory {
|
|||
switch (format.sampleMimeType) {
|
||||
case MimeTypes.TEXT_VTT:
|
||||
return new WebvttDecoder();
|
||||
case MimeTypes.TEXT_SSA:
|
||||
return new SsaDecoder(format.initializationData);
|
||||
case MimeTypes.APPLICATION_MP4VTT:
|
||||
return new Mp4WebvttDecoder();
|
||||
case MimeTypes.APPLICATION_TTML:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,214 @@
|
|||
/*
|
||||
* Copyright (C) 2017 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.exoplayer2.text.ssa;
|
||||
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.text.Cue;
|
||||
import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.LongArray;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* A {@link SimpleSubtitleDecoder} for SSA/ASS.
|
||||
*/
|
||||
public final class SsaDecoder extends SimpleSubtitleDecoder {
|
||||
|
||||
private static final String TAG = "SsaDecoder";
|
||||
|
||||
private static final Pattern SSA_TIMECODE_PATTERN = Pattern.compile(
|
||||
"(?:(\\d+):)?(\\d+):(\\d+)(?::|\\.)(\\d+)");
|
||||
private static final String FORMAT_LINE_PREFIX = "Format: ";
|
||||
private static final String DIALOGUE_LINE_PREFIX = "Dialogue: ";
|
||||
|
||||
private final boolean haveInitializationData;
|
||||
|
||||
private int formatKeyCount;
|
||||
private int formatStartIndex;
|
||||
private int formatEndIndex;
|
||||
private int formatTextIndex;
|
||||
|
||||
public SsaDecoder() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param initializationData Optional initialization data for the decoder. If not null, the
|
||||
* initialization data must consist of two byte arrays. The first must contain an SSA format
|
||||
* line. The second must contain an SSA header that will be assumed common to all samples.
|
||||
*/
|
||||
public SsaDecoder(List<byte[]> initializationData) {
|
||||
super("SsaDecoder");
|
||||
if (initializationData != null) {
|
||||
haveInitializationData = true;
|
||||
String formatLine = new String(initializationData.get(0));
|
||||
Assertions.checkArgument(formatLine.startsWith(FORMAT_LINE_PREFIX));
|
||||
parseFormatLine(formatLine);
|
||||
parseHeader(new ParsableByteArray(initializationData.get(1)));
|
||||
} else {
|
||||
haveInitializationData = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SsaSubtitle decode(byte[] bytes, int length, boolean reset) {
|
||||
ArrayList<Cue> cues = new ArrayList<>();
|
||||
LongArray cueTimesUs = new LongArray();
|
||||
|
||||
ParsableByteArray data = new ParsableByteArray(bytes, length);
|
||||
if (!haveInitializationData) {
|
||||
parseHeader(data);
|
||||
}
|
||||
parseEventBody(data, cues, cueTimesUs);
|
||||
|
||||
Cue[] cuesArray = new Cue[cues.size()];
|
||||
cues.toArray(cuesArray);
|
||||
long[] cueTimesUsArray = cueTimesUs.toArray();
|
||||
return new SsaSubtitle(cuesArray, cueTimesUsArray);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the header of the subtitle.
|
||||
*
|
||||
* @param data A {@link ParsableByteArray} from which the header should be read.
|
||||
*/
|
||||
private void parseHeader(ParsableByteArray data) {
|
||||
String currentLine;
|
||||
while ((currentLine = data.readLine()) != null) {
|
||||
// TODO: Parse useful data from the header.
|
||||
if (currentLine.startsWith("[Events]")) {
|
||||
// We've reached the event body.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the event body of the subtitle.
|
||||
*
|
||||
* @param data A {@link ParsableByteArray} from which the body should be read.
|
||||
* @param cues A list to which parsed cues will be added.
|
||||
* @param cueTimesUs An array to which parsed cue timestamps will be added.
|
||||
*/
|
||||
private void parseEventBody(ParsableByteArray data, List<Cue> cues, LongArray cueTimesUs) {
|
||||
String currentLine;
|
||||
while ((currentLine = data.readLine()) != null) {
|
||||
if (!haveInitializationData && currentLine.startsWith(FORMAT_LINE_PREFIX)) {
|
||||
parseFormatLine(currentLine);
|
||||
} else if (currentLine.startsWith(DIALOGUE_LINE_PREFIX)) {
|
||||
parseDialogueLine(currentLine, cues, cueTimesUs);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a format line.
|
||||
*
|
||||
* @param formatLine The line to parse.
|
||||
*/
|
||||
private void parseFormatLine(String formatLine) {
|
||||
String[] values = TextUtils.split(formatLine.substring(FORMAT_LINE_PREFIX.length()), ",");
|
||||
formatKeyCount = values.length;
|
||||
formatStartIndex = C.INDEX_UNSET;
|
||||
formatEndIndex = C.INDEX_UNSET;
|
||||
formatTextIndex = C.INDEX_UNSET;
|
||||
for (int i = 0; i < formatKeyCount; i++) {
|
||||
String key = values[i].trim().toLowerCase();
|
||||
switch (key) {
|
||||
case "start":
|
||||
formatStartIndex = i;
|
||||
break;
|
||||
case "end":
|
||||
formatEndIndex = i;
|
||||
break;
|
||||
case "text":
|
||||
formatTextIndex = i;
|
||||
break;
|
||||
default:
|
||||
// Do nothing.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a dialogue line.
|
||||
*
|
||||
* @param dialogueLine The line to parse.
|
||||
* @param cues A list to which parsed cues will be added.
|
||||
* @param cueTimesUs An array to which parsed cue timestamps will be added.
|
||||
*/
|
||||
private void parseDialogueLine(String dialogueLine, List<Cue> cues, LongArray cueTimesUs) {
|
||||
if (formatKeyCount == 0) {
|
||||
Log.w(TAG, "Skipping dialogue line before format: " + dialogueLine);
|
||||
return;
|
||||
}
|
||||
|
||||
String[] lineValues = dialogueLine.substring(DIALOGUE_LINE_PREFIX.length())
|
||||
.split(",", formatKeyCount);
|
||||
long startTimeUs = SsaDecoder.parseTimecodeUs(lineValues[formatStartIndex]);
|
||||
if (startTimeUs == C.TIME_UNSET) {
|
||||
Log.w(TAG, "Skipping invalid timing: " + dialogueLine);
|
||||
return;
|
||||
}
|
||||
|
||||
long endTimeUs = C.TIME_UNSET;
|
||||
String endTimeString = lineValues[formatEndIndex];
|
||||
if (!endTimeString.trim().isEmpty()) {
|
||||
endTimeUs = SsaDecoder.parseTimecodeUs(endTimeString);
|
||||
if (endTimeUs == C.TIME_UNSET) {
|
||||
Log.w(TAG, "Skipping invalid timing: " + dialogueLine);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
String text = lineValues[formatTextIndex]
|
||||
.replaceAll("\\{.*?\\}", "")
|
||||
.replaceAll("\\\\N", "\n")
|
||||
.replaceAll("\\\\n", "\n");
|
||||
cues.add(new Cue(text));
|
||||
cueTimesUs.add(startTimeUs);
|
||||
if (endTimeUs != C.TIME_UNSET) {
|
||||
cues.add(null);
|
||||
cueTimesUs.add(endTimeUs);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an SSA timecode string.
|
||||
*
|
||||
* @param timeString The string to parse.
|
||||
* @return The parsed timestamp in microseconds.
|
||||
*/
|
||||
public static long parseTimecodeUs(String timeString) {
|
||||
Matcher matcher = SSA_TIMECODE_PATTERN.matcher(timeString);
|
||||
if (!matcher.matches()) {
|
||||
return C.TIME_UNSET;
|
||||
}
|
||||
long timestampUs = Long.parseLong(matcher.group(1)) * 60 * 60 * C.MICROS_PER_SECOND;
|
||||
timestampUs += Long.parseLong(matcher.group(2)) * 60 * C.MICROS_PER_SECOND;
|
||||
timestampUs += Long.parseLong(matcher.group(3)) * C.MICROS_PER_SECOND;
|
||||
timestampUs += Long.parseLong(matcher.group(4)) * 10000; // 100ths of a second.
|
||||
return timestampUs;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
/*
|
||||
* Copyright (C) 2017 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.exoplayer2.text.ssa;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.text.Cue;
|
||||
import com.google.android.exoplayer2.text.Subtitle;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* A representation of an SSA/ASS subtitle.
|
||||
*/
|
||||
/* package */ final class SsaSubtitle implements Subtitle {
|
||||
|
||||
private final Cue[] cues;
|
||||
private final long[] cueTimesUs;
|
||||
|
||||
/**
|
||||
* @param cues The cues in the subtitle. Null entries may be used to represent empty cues.
|
||||
* @param cueTimesUs The cue times, in microseconds.
|
||||
*/
|
||||
public SsaSubtitle(Cue[] cues, long[] cueTimesUs) {
|
||||
this.cues = cues;
|
||||
this.cueTimesUs = cueTimesUs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getNextEventTimeIndex(long timeUs) {
|
||||
int index = Util.binarySearchCeil(cueTimesUs, timeUs, false, false);
|
||||
return index < cueTimesUs.length ? index : C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEventTimeCount() {
|
||||
return cueTimesUs.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getEventTime(int index) {
|
||||
Assertions.checkArgument(index >= 0);
|
||||
Assertions.checkArgument(index < cueTimesUs.length);
|
||||
return cueTimesUs[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Cue> getCues(long timeUs) {
|
||||
int index = Util.binarySearchFloor(cueTimesUs, timeUs, true, false);
|
||||
if (index == -1 || cues[index] == null) {
|
||||
// timeUs is earlier than the start of the first cue, or we have an empty cue.
|
||||
return Collections.emptyList();
|
||||
} else {
|
||||
return Collections.singletonList(cues[index]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -65,6 +65,7 @@ public final class MimeTypes {
|
|||
public static final String AUDIO_UNKNOWN = BASE_TYPE_AUDIO + "/x-unknown";
|
||||
|
||||
public static final String TEXT_VTT = BASE_TYPE_TEXT + "/vtt";
|
||||
public static final String TEXT_SSA = BASE_TYPE_TEXT + "/x-ssa";
|
||||
|
||||
public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4";
|
||||
public static final String APPLICATION_WEBM = BASE_TYPE_APPLICATION + "/webm";
|
||||
|
|
|
|||
Loading…
Reference in a new issue