diff --git a/library/hls/build.gradle b/library/hls/build.gradle index 4deef3b5f9..152fd35dff 100644 --- a/library/hls/build.gradle +++ b/library/hls/build.gradle @@ -33,6 +33,8 @@ android { } } + sourceSets.test.assets.srcDir '../../testdata/src/test/assets/' + testOptions.unitTests.includeAndroidResources = true } @@ -44,6 +46,7 @@ dependencies { compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion implementation project(modulePrefix + 'library-core') testImplementation project(modulePrefix + 'testutils') + testImplementation project(modulePrefix + 'testdata') testImplementation 'org.robolectric:robolectric:' + robolectricVersion } diff --git a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/WebvttExtractorTest.java b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/WebvttExtractorTest.java index 2f7f8e3fc0..5f1169e222 100644 --- a/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/WebvttExtractorTest.java +++ b/library/hls/src/test/java/com/google/android/exoplayer2/source/hls/WebvttExtractorTest.java @@ -17,9 +17,12 @@ package com.google.android.exoplayer2.source.hls; import static com.google.common.truth.Truth.assertThat; +import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.extractor.ExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorInput; +import com.google.android.exoplayer2.testutil.FakeExtractorOutput; +import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.util.TimestampAdjuster; import java.io.EOFException; import java.io.IOException; @@ -63,6 +66,27 @@ public class WebvttExtractorTest { assertThat(sniffData(data)).isFalse(); } + @Test + public void read_handlesLargeCueTimestamps() throws Exception { + TimestampAdjuster timestampAdjuster = new TimestampAdjuster(/* firstSampleTimestampUs= */ 0); + // Prime the TimestampAdjuster with a close-ish timestamp (5s before the first cue). + timestampAdjuster.adjustTsTimestamp(384615190); + WebvttExtractor extractor = new WebvttExtractor(/* language= */ null, timestampAdjuster); + // We can't use ExtractorAsserts because WebvttExtractor doesn't fulfill the whole Extractor + // interface (e.g. throws an exception from seek()). + FakeExtractorOutput output = + TestUtil.extractAllSamplesFromFile( + extractor, + ApplicationProvider.getApplicationContext(), + "webvtt/with_x-timestamp-map_header"); + + // The output has a ~5s sampleTime and a large, negative subsampleOffset because the cue + // timestamps are ~10 days ahead of the PTS (due to wrapping) so the offset is used to ensure + // they're rendered at the right time. + output.assertOutput( + ApplicationProvider.getApplicationContext(), "webvtt/with_x-timestamp-map_header.dump"); + } + private static boolean sniffData(byte[] data) throws IOException { ExtractorInput input = new FakeExtractorInput.Builder().setData(data).build(); try { diff --git a/testdata/src/test/assets/webvtt/with_x-timestamp-map_header b/testdata/src/test/assets/webvtt/with_x-timestamp-map_header new file mode 100644 index 0000000000..fb4edfdb7b --- /dev/null +++ b/testdata/src/test/assets/webvtt/with_x-timestamp-map_header @@ -0,0 +1,13 @@ +WEBVTT +X-TIMESTAMP-MAP=LOCAL:1:11:16.443,MPEGTS:384879885 + +NOTE +X-TIMESTAMP-MAP is defined in RFC 8216 section 3.5 (HLS spec). +The cue times below are deliberately greater than 2^33 in 90kHz PTS clock, +to test wraparound logic. + +266:18:35.679 --> 266:18:37.305 + Cue text one + +266:18:37.433 --> 266:18:38.276 + Cue text two diff --git a/testdata/src/test/assets/webvtt/with_x-timestamp-map_header.dump b/testdata/src/test/assets/webvtt/with_x-timestamp-map_header.dump new file mode 100644 index 0000000000..272155e8a8 --- /dev/null +++ b/testdata/src/test/assets/webvtt/with_x-timestamp-map_header.dump @@ -0,0 +1,16 @@ +seekMap: + isSeekable = false + duration = UNSET TIME + getPosition(0) = [[timeUs=0, position=0]] +numberOfTracks = 1 +track 0: + total output bytes = 324 + sample count = 1 + format 0: + sampleMimeType = text/vtt + subsampleOffsetUs = -958710678845 + sample 0: + time = 5000155 + flags = 1 + data = length 324, hash C0D159A2 +tracksEnded = true