mirror of
https://github.com/androidx/media.git
synced 2025-05-08 08:00:49 +08:00
Further cleanup subtitle implementations.
This commit is contained in:
parent
bdd1968abe
commit
02c978e16c
@ -94,7 +94,7 @@ public final class C {
|
|||||||
/**
|
/**
|
||||||
* An element of a custom ExoPlayer WebVTT header. An {@code WEBVTT_OFFSET + value} element can
|
* 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
|
* 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
|
* @hide
|
||||||
*/
|
*/
|
||||||
|
@ -132,7 +132,7 @@ public final class VideoFormatSelectorUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Util.toIntArray(selectedIndexList);
|
return Util.toArray(selectedIndexList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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.
|
* 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.
|
* Hence a small value may increase the probability of rebuffering and playback failures.
|
||||||
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
|
* @param elapsedRealtimeOffsetMs If known, an estimate of the instantaneous difference between
|
||||||
* server-side unix time and {@link SystemClock#elapsedRealtime()} in milliseconds, specified
|
* 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.
|
* as the server's unix time minus the local elapsed time. It unknown, set to 0.
|
||||||
*/
|
*/
|
||||||
public DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
|
public DashChunkSource(ManifestFetcher<MediaPresentationDescription> manifestFetcher,
|
||||||
int adaptationSetIndex, int[] representationIndices, DataSource dataSource,
|
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,
|
DataSpec dataSpec = new DataSpec(segmentUri.getUri(), segmentUri.start, segmentUri.length,
|
||||||
representation.getCacheKey());
|
representation.getCacheKey());
|
||||||
|
|
||||||
long presentationTimeOffsetUs = representation.presentationTimeOffsetUs;
|
long sampleOffsetUs = -1 * representation.presentationTimeOffsetUs;
|
||||||
if (representation.format.mimeType.equals(MimeTypes.TEXT_VTT)) {
|
if (representation.format.mimeType.equals(MimeTypes.TEXT_VTT)) {
|
||||||
if (representationHolder.vttHeaderOffsetUs != presentationTimeOffsetUs) {
|
if (representationHolder.vttHeaderOffsetUs != sampleOffsetUs) {
|
||||||
// Update the VTT header.
|
// Update the VTT header.
|
||||||
headerBuilder.setLength(0);
|
headerBuilder.setLength(0);
|
||||||
headerBuilder.append(C.WEBVTT_EXO_HEADER).append("=")
|
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");
|
.append("\n");
|
||||||
representationHolder.vttHeader = headerBuilder.toString().getBytes();
|
representationHolder.vttHeader = headerBuilder.toString().getBytes();
|
||||||
representationHolder.vttHeaderOffsetUs = presentationTimeOffsetUs;
|
representationHolder.vttHeaderOffsetUs = sampleOffsetUs;
|
||||||
}
|
}
|
||||||
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL,
|
return new SingleSampleMediaChunk(dataSource, dataSpec, Chunk.TRIGGER_INITIAL,
|
||||||
representation.format, startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment,
|
representation.format, startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment,
|
||||||
MediaFormat.createTextFormat(MimeTypes.TEXT_VTT), null, representationHolder.vttHeader);
|
MediaFormat.createTextFormat(MimeTypes.TEXT_VTT), null, representationHolder.vttHeader);
|
||||||
} else {
|
} else {
|
||||||
return new ContainerMediaChunk(dataSource, dataSpec, trigger, representation.format,
|
return new ContainerMediaChunk(dataSource, dataSpec, trigger, representation.format,
|
||||||
startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, 0,
|
startTimeUs, endTimeUs, absoluteSegmentNum, isLastSegment, sampleOffsetUs,
|
||||||
representationHolder.extractorWrapper, representationHolder.format, drmInitData, true);
|
representationHolder.extractorWrapper, representationHolder.format, drmInitData, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,8 @@ import com.google.android.exoplayer.C;
|
|||||||
import com.google.android.exoplayer.ParserException;
|
import com.google.android.exoplayer.ParserException;
|
||||||
import com.google.android.exoplayer.text.Cue;
|
import com.google.android.exoplayer.text.Cue;
|
||||||
import com.google.android.exoplayer.text.SubtitleParser;
|
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.MimeTypes;
|
||||||
import com.google.android.exoplayer.util.Util;
|
|
||||||
|
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
@ -36,8 +36,6 @@ import java.util.regex.Pattern;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* A simple SubRip parser.
|
* A simple SubRip parser.
|
||||||
* <p/>
|
|
||||||
* @see <a href="https://en.wikipedia.org/wiki/SubRip">Wikipedia on SubRip</a>
|
|
||||||
*/
|
*/
|
||||||
public final class SubripParser implements SubtitleParser {
|
public final class SubripParser implements SubtitleParser {
|
||||||
|
|
||||||
@ -53,9 +51,9 @@ public final class SubripParser implements SubtitleParser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SubripSubtitle parse(InputStream inputStream, String inputEncoding, long startTimeUs)
|
public SubripSubtitle parse(InputStream inputStream, String inputEncoding, long startTimeUs)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
ArrayList<Cue> cues = new ArrayList<>();
|
ArrayList<Cue> cues = new ArrayList<>();
|
||||||
ArrayList<Long> cueTimesUs = new ArrayList<>();
|
LongArray cueTimesUs = new LongArray();
|
||||||
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, C.UTF8_NAME));
|
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, C.UTF8_NAME));
|
||||||
String currentLine;
|
String currentLine;
|
||||||
|
|
||||||
@ -90,12 +88,9 @@ public final class SubripParser implements SubtitleParser {
|
|||||||
cues.add(new Cue(text));
|
cues.add(new Cue(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
reader.close();
|
|
||||||
inputStream.close();
|
|
||||||
|
|
||||||
Cue[] cuesArray = new Cue[cues.size()];
|
Cue[] cuesArray = new Cue[cues.size()];
|
||||||
cues.toArray(cuesArray);
|
cues.toArray(cuesArray);
|
||||||
long[] cueTimesUsArray = Util.toLongArray(cueTimesUs);
|
long[] cueTimesUsArray = cueTimesUs.toArray();
|
||||||
return new SubripSubtitle(startTimeUs, cuesArray, cueTimesUsArray);
|
return new SubripSubtitle(startTimeUs, cuesArray, cueTimesUsArray);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,12 +35,8 @@ public final class Tx3gParser implements SubtitleParser {
|
|||||||
public Subtitle parse(InputStream inputStream, String inputEncoding, long startTimeUs)
|
public Subtitle parse(InputStream inputStream, String inputEncoding, long startTimeUs)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
DataInputStream dataInputStream = new DataInputStream(inputStream);
|
DataInputStream dataInputStream = new DataInputStream(inputStream);
|
||||||
try {
|
String cueText = dataInputStream.readUTF();
|
||||||
String cueText = dataInputStream.readUTF();
|
return new Tx3gSubtitle(startTimeUs, new Cue(cueText));
|
||||||
return new Tx3gSubtitle(startTimeUs, new Cue(cueText));
|
|
||||||
} finally {
|
|
||||||
dataInputStream.close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -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 WEBVTT_CUE_SETTING = Pattern.compile(WEBVTT_CUE_SETTING_STRING);
|
||||||
|
|
||||||
private static final Pattern MEDIA_TIMESTAMP_OFFSET =
|
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 Pattern MEDIA_TIMESTAMP = Pattern.compile("MPEGTS:\\d+");
|
||||||
|
|
||||||
private static final String NON_NUMERIC_STRING = ".*[^0-9].*";
|
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);
|
throw new ParserException("X-TIMESTAMP-MAP doesn't contain media timestamp: " + line);
|
||||||
} else {
|
} else {
|
||||||
mediaTimestampUs = (Long.parseLong(timestampMatcher.group().substring(7)) * 1000)
|
mediaTimestampUs = (Long.parseLong(timestampMatcher.group().substring(7)) * 1000)
|
||||||
/ SAMPLING_RATE - mediaTimestampOffsetUs;
|
/ SAMPLING_RATE + mediaTimestampOffsetUs;
|
||||||
}
|
}
|
||||||
mediaTimestampUs = getAdjustedStartTime(mediaTimestampUs);
|
mediaTimestampUs = getAdjustedStartTime(mediaTimestampUs);
|
||||||
}
|
}
|
||||||
@ -237,10 +237,7 @@ public class WebvttParser implements SubtitleParser {
|
|||||||
subtitles.add(cue);
|
subtitles.add(cue);
|
||||||
}
|
}
|
||||||
|
|
||||||
webvttData.close();
|
return new WebvttSubtitle(subtitles, mediaTimestampUs);
|
||||||
inputStream.close();
|
|
||||||
WebvttSubtitle subtitle = new WebvttSubtitle(subtitles, mediaTimestampUs);
|
|
||||||
return subtitle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -20,7 +20,7 @@ import java.util.Arrays;
|
|||||||
/**
|
/**
|
||||||
* An append-only, auto-growing {@code long[]}.
|
* An append-only, auto-growing {@code long[]}.
|
||||||
*/
|
*/
|
||||||
public class LongArray {
|
public final class LongArray {
|
||||||
|
|
||||||
private static final int DEFAULT_INITIAL_CAPACITY = 32;
|
private static final int DEFAULT_INITIAL_CAPACITY = 32;
|
||||||
|
|
||||||
@ -74,4 +74,13 @@ public class LongArray {
|
|||||||
return size;
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -459,7 +459,7 @@ public final class Util {
|
|||||||
* @param list A list of integers.
|
* @param list A list of integers.
|
||||||
* @return The list in array form, or null if the input list was null.
|
* @return The list in array form, or null if the input list was null.
|
||||||
*/
|
*/
|
||||||
public static int[] toIntArray(List<Integer> list) {
|
public static int[] toArray(List<Integer> list) {
|
||||||
if (list == null) {
|
if (list == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -471,24 +471,6 @@ public final class Util {
|
|||||||
return intArray;
|
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<Long> 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
|
* 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
|
* block for a long time if the stream has a lot of data remaining. Call this method before
|
||||||
|
9
library/src/test/assets/webvtt/live_typical
Normal file
9
library/src/test/assets/webvtt/live_typical
Normal file
@ -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.
|
@ -25,7 +25,7 @@ import java.io.InputStream;
|
|||||||
/**
|
/**
|
||||||
* Unit test for {@link SubripParser}.
|
* 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 TYPICAL_SUBRIP_FILE = "subrip/typical";
|
||||||
private static final String EMPTY_SUBRIP_FILE = "subrip/empty";
|
private static final String EMPTY_SUBRIP_FILE = "subrip/empty";
|
||||||
|
@ -30,6 +30,7 @@ public class WebvttParserTest extends InstrumentationTestCase {
|
|||||||
private static final String TYPICAL_WEBVTT_FILE = "webvtt/typical";
|
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_IDS_WEBVTT_FILE = "webvtt/typical_with_identifiers";
|
||||||
private static final String TYPICAL_WITH_TAGS_WEBVTT_FILE = "webvtt/typical_with_tags";
|
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";
|
private static final String EMPTY_WEBVTT_FILE = "webvtt/empty";
|
||||||
|
|
||||||
public void testParseNullWebvttFile() throws IOException {
|
public void testParseNullWebvttFile() throws IOException {
|
||||||
@ -131,4 +132,28 @@ public class WebvttParserTest extends InstrumentationTestCase {
|
|||||||
assertEquals(startTimeUs + 7000000, subtitle.getEventTime(7));
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user