Commit b333169a by Oliver Woodman

Merge branch 'dev-v2' of git://github.com/jcable/ExoPlayer into jcable-dev-v2

parents 78e05457 402c985a
[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.
\ No newline at end of file
[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.
\ No newline at end of file
[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.
\ No newline at end of file
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.
\ No newline at end of file
Format: Layer, Start, End, Style, Name, Text
\ No newline at end of file
[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
\ No newline at end of file
/*
* 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());
......
......@@ -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:
......
/*
* 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;
}
}
/*
* 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";
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment