diff --git a/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripCue.java b/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripCue.java
index c60aeeb679..a464316b00 100644
--- a/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripCue.java
+++ b/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripCue.java
@@ -25,24 +25,10 @@ import com.google.android.exoplayer.text.Cue;
public final long startTime;
public final long endTime;
- public SubripCue(CharSequence text) {
- this(Cue.UNSET_VALUE, Cue.UNSET_VALUE, Cue.UNSET_VALUE,text);
- }
-
- public SubripCue(long startTime, long endTime, int position, CharSequence text) {
- super(text, Cue.UNSET_VALUE, position, null, Cue.UNSET_VALUE);
+ public SubripCue(long startTime, long endTime, CharSequence text) {
+ super(text);
this.startTime = startTime;
this.endTime = endTime;
}
- /**
- * Returns whether or not this cue should be placed in the default position and rolled-up with
- * the other "normal" cues.
- *
- * @return True if this cue should be placed in the default position; false otherwise.
- */
- public boolean isNormalCue() {
- return (line == UNSET_VALUE && position == UNSET_VALUE);
- }
-
}
diff --git a/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripParser.java b/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripParser.java
index 66a4dbff86..e633cdc9b3 100644
--- a/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripParser.java
+++ b/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripParser.java
@@ -15,14 +15,15 @@
*/
package com.google.android.exoplayer.text.subrip;
-import android.text.Html;
-
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.ParserException;
-import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.SubtitleParser;
import com.google.android.exoplayer.util.MimeTypes;
+import android.text.Html;
+import android.text.Spanned;
+import android.text.TextUtils;
+
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@@ -32,93 +33,66 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
- * A simple SRT parser.
+ * A simple SubRip parser.
*
- *
- * @see Wikipedia on SRT
+ * @see Wikipedia on SubRip
*/
public final class SubripParser implements SubtitleParser {
- private static final String TAG = "SubRipParser";
-
- private static final String SUBRIP_POSITION_STRING = "^(\\d)$";
- private static final Pattern SUBRIP_POSITION = Pattern.compile(SUBRIP_POSITION_STRING);
-
- private static final String SUBRIP_CUE_IDENTIFIER_STRING = "^(.*)\\s-->\\s(.*)$";
- private static final Pattern SUBRIP_CUE_IDENTIFIER =
- Pattern.compile(SUBRIP_CUE_IDENTIFIER_STRING);
-
- private static final String SUBRIP_TIMESTAMP_STRING = "(\\d+:)?[0-5]\\d:[0-5]\\d:[0-5]\\d,\\d{3}";
- // private static final Pattern SUBRIP_TIMESTAMP = Pattern.compile(SUBRIP_TIMESTAMP_STRING);
+ private static final Pattern SUBRIP_TIMING_LINE = Pattern.compile("(.*)\\s+-->\\s+(.*)");
+ private static final Pattern SUBRIP_TIMESTAMP =
+ Pattern.compile("(?:(\\d+):)?(\\d+):(\\d+),(\\d+)");
private final StringBuilder textBuilder;
- private final boolean strictParsing;
-
public SubripParser() {
- this(true);
- }
-
- public SubripParser(boolean strictParsing) {
- this.strictParsing = strictParsing;
-
textBuilder = new StringBuilder();
}
@Override
public SubripSubtitle parse(InputStream inputStream, String inputEncoding, long startTimeUs)
throws IOException {
- ArrayList subtitles = new ArrayList<>();
+ ArrayList cues = new ArrayList<>();
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, C.UTF8_NAME));
+ String currentLine;
- // file should not be empty
- if (inputStream.available() == 0) {
- throw new ParserException("File is empty?");
- }
-
- BufferedReader subripData = new BufferedReader(new InputStreamReader(inputStream, C.UTF8_NAME));
- String line;
-
-
- // process the cues and text
- while ((line = subripData.readLine()) != null) {
- long startTime = Cue.UNSET_VALUE;
- long endTime = Cue.UNSET_VALUE;
- CharSequence text = null;
- int position = Cue.UNSET_VALUE;
-
- Matcher matcher = SUBRIP_POSITION.matcher(line);
- if (matcher.matches()) {
- position = Integer.parseInt(matcher.group());
+ while ((currentLine = reader.readLine()) != null) {
+ // Parse the numeric counter as a sanity check.
+ try {
+ Integer.parseInt(currentLine);
+ } catch (NumberFormatException e) {
+ throw new ParserException("Expected numeric counter: " + currentLine);
}
- line = subripData.readLine();
-
- // parse cue time
- matcher = SUBRIP_CUE_IDENTIFIER.matcher(line);
- if (!matcher.find()) {
- throw new ParserException("Expected cue start time: " + line);
+ // Read and parse the timing line.
+ long cueStartTimeUs;
+ long cueEndTimeUs;
+ currentLine = reader.readLine();
+ Matcher matcher = SUBRIP_TIMING_LINE.matcher(currentLine);
+ if (matcher.find()) {
+ cueStartTimeUs = parseTimestampUs(matcher.group(1)) + startTimeUs;
+ cueEndTimeUs = parseTimestampUs(matcher.group(2)) + startTimeUs;
} else {
- startTime = parseTimestampUs(matcher.group(1)) + startTimeUs;
- endTime = parseTimestampUs(matcher.group(2)) + startTimeUs;
+ throw new ParserException("Expected timing line: " + currentLine);
}
- // parse text
+ // Read and parse the text.
textBuilder.setLength(0);
- while (((line = subripData.readLine()) != null) && (!line.isEmpty())) {
+ while (!TextUtils.isEmpty(currentLine = reader.readLine())) {
if (textBuilder.length() > 0) {
textBuilder.append("
");
}
- textBuilder.append(line.trim());
+ textBuilder.append(currentLine.trim());
}
- text = Html.fromHtml(textBuilder.toString());
- SubripCue cue = new SubripCue(startTime, endTime, position, text);
- subtitles.add(cue);
+ Spanned text = Html.fromHtml(textBuilder.toString());
+ SubripCue cue = new SubripCue(cueStartTimeUs, cueEndTimeUs, text);
+ cues.add(cue);
}
- subripData.close();
+ reader.close();
inputStream.close();
- SubripSubtitle subtitle = new SubripSubtitle(subtitles, startTimeUs);
+ SubripSubtitle subtitle = new SubripSubtitle(cues, startTimeUs);
return subtitle;
}
@@ -127,23 +101,16 @@ public final class SubripParser implements SubtitleParser {
return MimeTypes.APPLICATION_SUBRIP.equals(mimeType);
}
- private void handleNoncompliantLine(String line) throws ParserException {
- if (strictParsing) {
- throw new ParserException("Unexpected line: " + line);
- }
- }
-
private static long parseTimestampUs(String s) throws NumberFormatException {
- if (!s.matches(SUBRIP_TIMESTAMP_STRING)) {
+ Matcher matcher = SUBRIP_TIMESTAMP.matcher(s);
+ if (!matcher.matches()) {
throw new NumberFormatException("has invalid format");
}
-
- String[] parts = s.split(",", 2);
- long value = 0;
- for (String group : parts[0].split(":")) {
- value = value * 60 + Long.parseLong(group);
- }
- return (value * 1000 + Long.parseLong(parts[1])) * 1000;
+ long timestampMs = Long.parseLong(matcher.group(1)) * 60 * 60 * 1000;
+ timestampMs += Long.parseLong(matcher.group(2)) * 60 * 1000;
+ timestampMs += Long.parseLong(matcher.group(3)) * 1000;
+ timestampMs += Long.parseLong(matcher.group(4));
+ return timestampMs * 1000;
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripSubtitle.java b/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripSubtitle.java
index 27cf7988ea..65282388be 100644
--- a/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripSubtitle.java
+++ b/library/src/main/java/com/google/android/exoplayer/text/subrip/SubripSubtitle.java
@@ -15,15 +15,11 @@
*/
package com.google.android.exoplayer.text.subrip;
-import android.text.SpannableStringBuilder;
-
import com.google.android.exoplayer.text.Cue;
import com.google.android.exoplayer.text.Subtitle;
import com.google.android.exoplayer.util.Assertions;
import com.google.android.exoplayer.util.Util;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -36,7 +32,6 @@ import java.util.List;
private final int numCues;
private final long startTimeUs;
private final long[] cueTimesUs;
- private final long[] sortedCueTimesUs;
/**
* @param cues A list of the cues in this subtitle.
@@ -44,19 +39,16 @@ import java.util.List;
*/
public SubripSubtitle(List cues, long startTimeUs) {
this.cues = cues;
- numCues = cues.size();
this.startTimeUs = startTimeUs;
- this.cueTimesUs = new long[2 * numCues];
+ numCues = cues.size();
+ cueTimesUs = new long[2 * numCues];
for (int cueIndex = 0; cueIndex < numCues; cueIndex++) {
SubripCue cue = cues.get(cueIndex);
int arrayIndex = cueIndex * 2;
cueTimesUs[arrayIndex] = cue.startTime;
cueTimesUs[arrayIndex + 1] = cue.endTime;
}
-
- this.sortedCueTimesUs = Arrays.copyOf(cueTimesUs, cueTimesUs.length);
- Arrays.sort(sortedCueTimesUs);
}
@Override
@@ -67,20 +59,20 @@ import java.util.List;
@Override
public int getNextEventTimeIndex(long timeUs) {
Assertions.checkArgument(timeUs >= 0);
- int index = Util.binarySearchCeil(sortedCueTimesUs, timeUs, false, false);
- return index < sortedCueTimesUs.length ? index : -1;
+ int index = Util.binarySearchCeil(cueTimesUs, timeUs, false, false);
+ return index < cueTimesUs.length ? index : -1;
}
@Override
public int getEventTimeCount() {
- return sortedCueTimesUs.length;
+ return cueTimesUs.length;
}
@Override
public long getEventTime(int index) {
Assertions.checkArgument(index >= 0);
- Assertions.checkArgument(index < sortedCueTimesUs.length);
- return sortedCueTimesUs[index];
+ Assertions.checkArgument(index < cueTimesUs.length);
+ return cueTimesUs[index];
}
@Override
@@ -88,50 +80,17 @@ import java.util.List;
if (getEventTimeCount() == 0) {
return -1;
}
- return sortedCueTimesUs[sortedCueTimesUs.length - 1];
+ return cueTimesUs[cueTimesUs.length - 1];
}
@Override
public List getCues(long timeUs) {
- ArrayList list = null;
- SubripCue firstNormalCue = null;
- SpannableStringBuilder normalCueTextBuilder = null;
-
- for (int i = 0; i < numCues; i++) {
- if ((cueTimesUs[i * 2] <= timeUs) && (timeUs < cueTimesUs[i * 2 + 1])) {
- if (list == null) {
- list = new ArrayList<>();
- }
- SubripCue cue = cues.get(i);
- if (cue.isNormalCue()) {
- // we want to merge all of the normal cues into a single cue to ensure they are drawn
- // correctly (i.e. don't overlap) and to emulate roll-up, but only if there are multiple
- // normal cues, otherwise we can just append the single normal cue
- if (firstNormalCue == null) {
- firstNormalCue = cue;
- } else if (normalCueTextBuilder == null) {
- normalCueTextBuilder = new SpannableStringBuilder();
- normalCueTextBuilder.append(firstNormalCue.text).append("\n").append(cue.text);
- } else {
- normalCueTextBuilder.append("\n").append(cue.text);
- }
- } else {
- list.add(cue);
- }
- }
- }
- if (normalCueTextBuilder != null) {
- // there were multiple normal cues, so create a new cue with all of the text
- list.add(new SubripCue(normalCueTextBuilder));
- } else if (firstNormalCue != null) {
- // there was only a single normal cue, so just add it to the list
- list.add(firstNormalCue);
- }
-
- if (list != null) {
- return list;
- } else {
+ int index = Util.binarySearchFloor(cueTimesUs, timeUs, true, false);
+ if (index == -1 || index % 2 == 1) {
+ // timeUs is earlier than the start of the first cue, or corresponds to a gap between cues.
return Collections.emptyList();
+ } else {
+ return Collections.singletonList((Cue) cues.get(index / 2));
}
}
diff --git a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttSubtitle.java b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttSubtitle.java
index 0ef8577a44..3ca64cdc21 100644
--- a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttSubtitle.java
+++ b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttSubtitle.java
@@ -44,18 +44,17 @@ public class WebvttSubtitle implements Subtitle {
*/
public WebvttSubtitle(List cues, long startTimeUs) {
this.cues = cues;
- numCues = cues.size();
this.startTimeUs = startTimeUs;
- this.cueTimesUs = new long[2 * numCues];
+ numCues = cues.size();
+ cueTimesUs = new long[2 * numCues];
for (int cueIndex = 0; cueIndex < numCues; cueIndex++) {
WebvttCue cue = cues.get(cueIndex);
int arrayIndex = cueIndex * 2;
cueTimesUs[arrayIndex] = cue.startTime;
cueTimesUs[arrayIndex + 1] = cue.endTime;
}
-
- this.sortedCueTimesUs = Arrays.copyOf(cueTimesUs, cueTimesUs.length);
+ sortedCueTimesUs = Arrays.copyOf(cueTimesUs, cueTimesUs.length);
Arrays.sort(sortedCueTimesUs);
}