diff --git a/library/src/main/java/com/google/android/exoplayer/C.java b/library/src/main/java/com/google/android/exoplayer/C.java index 2efc751449..68e1ff1c17 100644 --- a/library/src/main/java/com/google/android/exoplayer/C.java +++ b/library/src/main/java/com/google/android/exoplayer/C.java @@ -94,7 +94,7 @@ public final class C { /** * An element of a custom ExoPlayer WebVTT header. An {@code WEBVTT_OFFSET + value} element can * be added to a custom ExoPlayer WebVTT header to specify an offset time (in microseconds) that - * should be subtracted from the embedded MPEGTS value. + * should be added to the embedded MPEGTS value. * * @hide */ diff --git a/library/src/main/java/com/google/android/exoplayer/chunk/VideoFormatSelectorUtil.java b/library/src/main/java/com/google/android/exoplayer/chunk/VideoFormatSelectorUtil.java index f88d9076be..0866382fc7 100644 --- a/library/src/main/java/com/google/android/exoplayer/chunk/VideoFormatSelectorUtil.java +++ b/library/src/main/java/com/google/android/exoplayer/chunk/VideoFormatSelectorUtil.java @@ -132,7 +132,7 @@ public final class VideoFormatSelectorUtil { } } - return Util.toIntArray(selectedIndexList); + return Util.toArray(selectedIndexList); } /** diff --git a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java index 8a93b792d6..dff2b16ded 100644 --- a/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java +++ b/library/src/main/java/com/google/android/exoplayer/dash/DashChunkSource.java @@ -165,8 +165,8 @@ public class DashChunkSource implements ChunkSource { * note that the value sets an upper bound on the length of media that the player can buffer. * Hence a small value may increase the probability of rebuffering and playback failures. * @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between - * server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified - * as the server's unix time minus the local elapsed time. It unknown, set to 0. + * server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified + * as the server's unix time minus the local elapsed time. It unknown, set to 0. */ public DashChunkSource(ManifestFetcher manifestFetcher, int adaptationSetIndex, int[] representationIndices, DataSource dataSource, @@ -491,23 +491,23 @@ public class DashChunkSource implements ChunkSource { DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length, representation.getCacheKey()); - long presentationTimeOffsetUs = representation.presentationTimeOffsetUs; + long sampleOffsetUs = -1 * representation.presentationTimeOffsetUs; if (representation.format.mimeType.equals(MimeTypes.TEXT_VTT)) { - if (representationHolder.vttHeaderOffsetUs != presentationTimeOffsetUs) { + if (representationHolder.vttHeaderOffsetUs != sampleOffsetUs) { // Update the VTT header. headerBuilder.setLength(0); headerBuilder.append(C.WEBVTT_EXO_HEADER).append("=") - .append(C.WEBVTT_EXO_HEADER_OFFSET).append(presentationTimeOffsetUs) + .append(C.WEBVTT_EXO_HEADER_OFFSET).append(sampleOffsetUs) .append("\n"); representationHolder.vttHeader = headerBuilder.toString().getBytes(); - representationHolder.vttHeaderOffsetUs = presentationTimeOffsetUs; + representationHolder.vttHeaderOffsetUs = sampleOffsetUs; } return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL, representation.format, startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, MediaFormat.createTextFormat(MimeTypes.TEXT_VTT), null, representationHolder.vttHeader); } else { return new ContainerMediaChunk(dataSource, dataSpec, trigger, representation.format, - startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, 0, + startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, sampleOffsetUs, representationHolder.extractorWrapper, representationHolder.format, drmInitData, true); } } 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 c1261ba132..bfd04df251 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 @@ -19,8 +19,8 @@ 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.LongArray; import com.google.android.exoplayer.util.MimeTypes; -import com.google.android.exoplayer.util.Util; import android.text.Html; import android.text.Spanned; @@ -36,8 +36,6 @@ import java.util.regex.Pattern; /** * A simple SubRip parser. - *

- * @see Wikipedia on SubRip */ public final class SubripParser implements SubtitleParser { @@ -53,9 +51,9 @@ public final class SubripParser implements SubtitleParser { @Override public SubripSubtitle parse(InputStream inputStream, String inputEncoding, long startTimeUs) - throws IOException { + throws IOException { ArrayList cues = new ArrayList<>(); - ArrayList cueTimesUs = new ArrayList<>(); + LongArray cueTimesUs = new LongArray(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, C.UTF8_NAME)); String currentLine; @@ -90,12 +88,9 @@ public final class SubripParser implements SubtitleParser { cues.add(new Cue(text)); } - reader.close(); - inputStream.close(); - Cue[] cuesArray = new Cue[cues.size()]; cues.toArray(cuesArray); - long[] cueTimesUsArray = Util.toLongArray(cueTimesUs); + long[] cueTimesUsArray = cueTimesUs.toArray(); return new SubripSubtitle(startTimeUs, cuesArray, cueTimesUsArray); } diff --git a/library/src/main/java/com/google/android/exoplayer/text/tx3g/Tx3gParser.java b/library/src/main/java/com/google/android/exoplayer/text/tx3g/Tx3gParser.java index 27613c75a0..2d4d04079b 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/tx3g/Tx3gParser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/tx3g/Tx3gParser.java @@ -35,12 +35,8 @@ public final class Tx3gParser implements SubtitleParser { public Subtitle parse(InputStream inputStream, String inputEncoding, long startTimeUs) throws IOException { DataInputStream dataInputStream = new DataInputStream(inputStream); - try { - String cueText = dataInputStream.readUTF(); - return new Tx3gSubtitle(startTimeUs, new Cue(cueText)); - } finally { - dataInputStream.close(); - } + String cueText = dataInputStream.readUTF(); + return new Tx3gSubtitle(startTimeUs, new Cue(cueText)); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java index 289dd9efc6..3d683ea0b6 100644 --- a/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java +++ b/library/src/main/java/com/google/android/exoplayer/text/webvtt/WebvttParser.java @@ -59,7 +59,7 @@ public class WebvttParser implements SubtitleParser { private static final Pattern WEBVTT_CUE_SETTING = Pattern.compile(WEBVTT_CUE_SETTING_STRING); private static final Pattern MEDIA_TIMESTAMP_OFFSET = - Pattern.compile(C.WEBVTT_EXO_HEADER_OFFSET + "\\d+"); + Pattern.compile(C.WEBVTT_EXO_HEADER_OFFSET + "\\-?\\d+"); private static final Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:\\d+"); private static final String NON_NUMERIC_STRING = ".*[^0-9].*"; @@ -135,7 +135,7 @@ public class WebvttParser implements SubtitleParser { throw new ParserException("X-TIMESTAMP-MAP doesn't contain media timestamp: " + line); } else { mediaTimestampUs = (Long.parseLong(timestampMatcher.group().substring(7)) * 1000) - / SAMPLING_RATE - mediaTimestampOffsetUs; + / SAMPLING_RATE + mediaTimestampOffsetUs; } mediaTimestampUs = getAdjustedStartTime(mediaTimestampUs); } @@ -237,10 +237,7 @@ public class WebvttParser implements SubtitleParser { subtitles.add(cue); } - webvttData.close(); - inputStream.close(); - WebvttSubtitle subtitle = new WebvttSubtitle(subtitles, mediaTimestampUs); - return subtitle; + return new WebvttSubtitle(subtitles, mediaTimestampUs); } @Override diff --git a/library/src/main/java/com/google/android/exoplayer/util/LongArray.java b/library/src/main/java/com/google/android/exoplayer/util/LongArray.java index a88b2f3e6d..dee3fc61d0 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/LongArray.java +++ b/library/src/main/java/com/google/android/exoplayer/util/LongArray.java @@ -20,7 +20,7 @@ import java.util.Arrays; /** * An append-only, auto-growing {@code long[]}. */ -public class LongArray { +public final class LongArray { private static final int DEFAULT_INITIAL_CAPACITY = 32; @@ -74,4 +74,13 @@ public class LongArray { return size; } + /** + * Copies the current values into a newly allocated primitive array. + * + * @return The primitive array containing the copied values. + */ + public long[] toArray() { + return Arrays.copyOf(values, size); + } + } diff --git a/library/src/main/java/com/google/android/exoplayer/util/Util.java b/library/src/main/java/com/google/android/exoplayer/util/Util.java index c5bf9d808e..65d8197792 100644 --- a/library/src/main/java/com/google/android/exoplayer/util/Util.java +++ b/library/src/main/java/com/google/android/exoplayer/util/Util.java @@ -459,7 +459,7 @@ public final class Util { * @param list A list of integers. * @return The list in array form, or null if the input list was null. */ - public static int[] toIntArray(List list) { + public static int[] toArray(List list) { if (list == null) { return null; } @@ -471,24 +471,6 @@ public final class Util { return intArray; } - /** - * Converts a list of longs to a primitive array. - * - * @param list A list of longs. - * @return The list in array form, or null if the input list was null. - */ - public static long[] toLongArray(List list) { - if (list == null) { - return null; - } - int length = list.size(); - long[] longArray = new long[length]; - for (int i = 0; i < length; i++) { - longArray[i] = list.get(i); - } - return longArray; - } - /** * On platform API levels 19 and 20, okhttp's implementation of {@link InputStream#close} can * block for a long time if the stream has a lot of data remaining. Call this method before diff --git a/library/src/test/assets/webvtt/live_typical b/library/src/test/assets/webvtt/live_typical new file mode 100644 index 0000000000..9f4864b690 --- /dev/null +++ b/library/src/test/assets/webvtt/live_typical @@ -0,0 +1,9 @@ +EXO-HEADER=OFFSET:-5000000 +WEBVTT +X-TIMESTAMP-MAP=LOCAL:00:00.000,MPEGTS:450000 + +00:00.000 --> 00:01.234 +This is the first subtitle. + +00:02.345 --> 00:03.456 +This is the second subtitle. diff --git a/library/src/test/java/com/google/android/exoplayer/text/subrip/SubripParserTest.java b/library/src/test/java/com/google/android/exoplayer/text/subrip/SubripParserTest.java index c3fa7c2dd5..7459442fb2 100644 --- a/library/src/test/java/com/google/android/exoplayer/text/subrip/SubripParserTest.java +++ b/library/src/test/java/com/google/android/exoplayer/text/subrip/SubripParserTest.java @@ -25,7 +25,7 @@ import java.io.InputStream; /** * Unit test for {@link SubripParser}. */ -public class SubripParserTest extends InstrumentationTestCase { +public final class SubripParserTest extends InstrumentationTestCase { private static final String TYPICAL_SUBRIP_FILE = "subrip/typical"; private static final String EMPTY_SUBRIP_FILE = "subrip/empty"; diff --git a/library/src/test/java/com/google/android/exoplayer/text/webvtt/WebvttParserTest.java b/library/src/test/java/com/google/android/exoplayer/text/webvtt/WebvttParserTest.java index e5bbf7b331..e8bb58ed9f 100644 --- a/library/src/test/java/com/google/android/exoplayer/text/webvtt/WebvttParserTest.java +++ b/library/src/test/java/com/google/android/exoplayer/text/webvtt/WebvttParserTest.java @@ -30,6 +30,7 @@ public class WebvttParserTest extends InstrumentationTestCase { private static final String TYPICAL_WEBVTT_FILE = "webvtt/typical"; private static final String TYPICAL_WITH_IDS_WEBVTT_FILE = "webvtt/typical_with_identifiers"; private static final String TYPICAL_WITH_TAGS_WEBVTT_FILE = "webvtt/typical_with_tags"; + private static final String LIVE_TYPICAL_WEBVTT_FILE = "webvtt/live_typical"; private static final String EMPTY_WEBVTT_FILE = "webvtt/empty"; public void testParseNullWebvttFile() throws IOException { @@ -131,4 +132,28 @@ public class WebvttParserTest extends InstrumentationTestCase { assertEquals(startTimeUs + 7000000, subtitle.getEventTime(7)); } + public void testParseLiveTypicalWebvttFile() throws IOException { + WebvttParser parser = new WebvttParser(); + InputStream inputStream = + getInstrumentation().getContext().getResources().getAssets().open(LIVE_TYPICAL_WEBVTT_FILE); + WebvttSubtitle subtitle = parser.parse(inputStream, C.UTF8_NAME, 0); + + // test start time and event count + long startTimeUs = 0; + assertEquals(startTimeUs, subtitle.getStartTime()); + assertEquals(4, subtitle.getEventTimeCount()); + + // test first cue + assertEquals(startTimeUs, subtitle.getEventTime(0)); + assertEquals("This is the first subtitle.", + subtitle.getCues(subtitle.getEventTime(0)).get(0).text.toString()); + assertEquals(startTimeUs + 1234000, subtitle.getEventTime(1)); + + // test second cue + assertEquals(startTimeUs + 2345000, subtitle.getEventTime(2)); + assertEquals("This is the second subtitle.", + subtitle.getCues(subtitle.getEventTime(2)).get(0).text.toString()); + assertEquals(startTimeUs + 3456000, subtitle.getEventTime(3)); + } + }