Commit 4276a199 by aquilescanta Committed by Oliver Woodman

Add initial support for EXT-X-GAP

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=185676652
parent 0ae56cc1
...@@ -69,6 +69,7 @@ ...@@ -69,6 +69,7 @@
([#3622](https://github.com/google/ExoPlayer/issues/3622)). ([#3622](https://github.com/google/ExoPlayer/issues/3622)).
* Use long for media sequence numbers * Use long for media sequence numbers
([#3747](https://github.com/google/ExoPlayer/issues/3747)) ([#3747](https://github.com/google/ExoPlayer/issues/3747))
* Add initial support for the EXT-X-GAP tag.
* New Cast extension: Simplifies toggling between local and Cast playbacks. * New Cast extension: Simplifies toggling between local and Cast playbacks.
* Audio: * Audio:
* Support TrueHD passthrough for rechunked samples in Matroska files * Support TrueHD passthrough for rechunked samples in Matroska files
......
...@@ -33,7 +33,7 @@ import junit.framework.TestCase; ...@@ -33,7 +33,7 @@ import junit.framework.TestCase;
*/ */
public class HlsMediaPlaylistParserTest extends TestCase { public class HlsMediaPlaylistParserTest extends TestCase {
public void testParseMediaPlaylist() { public void testParseMediaPlaylist() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8"); Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
String playlistString = "#EXTM3U\n" String playlistString = "#EXTM3U\n"
+ "#EXT-X-VERSION:3\n" + "#EXT-X-VERSION:3\n"
...@@ -69,76 +69,106 @@ public class HlsMediaPlaylistParserTest extends TestCase { ...@@ -69,76 +69,106 @@ public class HlsMediaPlaylistParserTest extends TestCase {
+ "#EXT-X-ENDLIST"; + "#EXT-X-ENDLIST";
InputStream inputStream = new ByteArrayInputStream( InputStream inputStream = new ByteArrayInputStream(
playlistString.getBytes(Charset.forName(C.UTF8_NAME))); playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
try { HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream);
HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream);
assertThat(playlist).isNotNull();
HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist; HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
assertThat(mediaPlaylist.playlistType).isEqualTo(HlsMediaPlaylist.PLAYLIST_TYPE_VOD); assertThat(mediaPlaylist.playlistType).isEqualTo(HlsMediaPlaylist.PLAYLIST_TYPE_VOD);
assertThat(mediaPlaylist.startOffsetUs).isEqualTo(mediaPlaylist.durationUs - 25000000); assertThat(mediaPlaylist.startOffsetUs).isEqualTo(mediaPlaylist.durationUs - 25000000);
assertThat(mediaPlaylist.mediaSequence).isEqualTo(2679); assertThat(mediaPlaylist.mediaSequence).isEqualTo(2679);
assertThat(mediaPlaylist.version).isEqualTo(3); assertThat(mediaPlaylist.version).isEqualTo(3);
assertThat(mediaPlaylist.hasEndTag).isTrue(); assertThat(mediaPlaylist.hasEndTag).isTrue();
List<Segment> segments = mediaPlaylist.segments; List<Segment> segments = mediaPlaylist.segments;
assertThat(segments).isNotNull(); assertThat(segments).isNotNull();
assertThat(segments).hasSize(5); assertThat(segments).hasSize(5);
Segment segment = segments.get(0); Segment segment = segments.get(0);
assertThat(mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence) assertThat(mediaPlaylist.discontinuitySequence + segment.relativeDiscontinuitySequence)
.isEqualTo(4); .isEqualTo(4);
assertThat(segment.durationUs).isEqualTo(7975000); assertThat(segment.durationUs).isEqualTo(7975000);
assertThat(segment.fullSegmentEncryptionKeyUri).isNull(); assertThat(segment.fullSegmentEncryptionKeyUri).isNull();
assertThat(segment.encryptionIV).isNull(); assertThat(segment.encryptionIV).isNull();
assertThat(segment.byterangeLength).isEqualTo(51370); assertThat(segment.byterangeLength).isEqualTo(51370);
assertThat(segment.byterangeOffset).isEqualTo(0); assertThat(segment.byterangeOffset).isEqualTo(0);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2679.ts"); assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2679.ts");
segment = segments.get(1); segment = segments.get(1);
assertThat(segment.relativeDiscontinuitySequence).isEqualTo(0); assertThat(segment.relativeDiscontinuitySequence).isEqualTo(0);
assertThat(segment.durationUs).isEqualTo(7975000); assertThat(segment.durationUs).isEqualTo(7975000);
assertThat(segment.fullSegmentEncryptionKeyUri) assertThat(segment.fullSegmentEncryptionKeyUri)
.isEqualTo("https://priv.example.com/key.php?r=2680"); .isEqualTo("https://priv.example.com/key.php?r=2680");
assertThat(segment.encryptionIV).isEqualTo("0x1566B"); assertThat(segment.encryptionIV).isEqualTo("0x1566B");
assertThat(segment.byterangeLength).isEqualTo(51501); assertThat(segment.byterangeLength).isEqualTo(51501);
assertThat(segment.byterangeOffset).isEqualTo(2147483648L); assertThat(segment.byterangeOffset).isEqualTo(2147483648L);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2680.ts"); assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2680.ts");
segment = segments.get(2); segment = segments.get(2);
assertThat(segment.relativeDiscontinuitySequence).isEqualTo(0); assertThat(segment.relativeDiscontinuitySequence).isEqualTo(0);
assertThat(segment.durationUs).isEqualTo(7941000); assertThat(segment.durationUs).isEqualTo(7941000);
assertThat(segment.fullSegmentEncryptionKeyUri).isNull(); assertThat(segment.fullSegmentEncryptionKeyUri).isNull();
assertThat(segment.encryptionIV).isEqualTo(null); assertThat(segment.encryptionIV).isEqualTo(null);
assertThat(segment.byterangeLength).isEqualTo(51501); assertThat(segment.byterangeLength).isEqualTo(51501);
assertThat(segment.byterangeOffset).isEqualTo(2147535149L); assertThat(segment.byterangeOffset).isEqualTo(2147535149L);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2681.ts"); assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2681.ts");
segment = segments.get(3); segment = segments.get(3);
assertThat(segment.relativeDiscontinuitySequence).isEqualTo(1); assertThat(segment.relativeDiscontinuitySequence).isEqualTo(1);
assertThat(segment.durationUs).isEqualTo(7975000); assertThat(segment.durationUs).isEqualTo(7975000);
assertThat(segment.fullSegmentEncryptionKeyUri) assertThat(segment.fullSegmentEncryptionKeyUri)
.isEqualTo("https://priv.example.com/key.php?r=2682"); .isEqualTo("https://priv.example.com/key.php?r=2682");
// 0xA7A == 2682. // 0xA7A == 2682.
assertThat(segment.encryptionIV).isNotNull(); assertThat(segment.encryptionIV).isNotNull();
assertThat(segment.encryptionIV.toUpperCase(Locale.getDefault())).isEqualTo("A7A"); assertThat(segment.encryptionIV.toUpperCase(Locale.getDefault())).isEqualTo("A7A");
assertThat(segment.byterangeLength).isEqualTo(51740); assertThat(segment.byterangeLength).isEqualTo(51740);
assertThat(segment.byterangeOffset).isEqualTo(2147586650L); assertThat(segment.byterangeOffset).isEqualTo(2147586650L);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2682.ts"); assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2682.ts");
segment = segments.get(4); segment = segments.get(4);
assertThat(segment.relativeDiscontinuitySequence).isEqualTo(1); assertThat(segment.relativeDiscontinuitySequence).isEqualTo(1);
assertThat(segment.durationUs).isEqualTo(7975000); assertThat(segment.durationUs).isEqualTo(7975000);
assertThat(segment.fullSegmentEncryptionKeyUri) assertThat(segment.fullSegmentEncryptionKeyUri)
.isEqualTo("https://priv.example.com/key.php?r=2682"); .isEqualTo("https://priv.example.com/key.php?r=2682");
// 0xA7B == 2683. // 0xA7B == 2683.
assertThat(segment.encryptionIV).isNotNull(); assertThat(segment.encryptionIV).isNotNull();
assertThat(segment.encryptionIV.toUpperCase(Locale.getDefault())).isEqualTo("A7B"); assertThat(segment.encryptionIV.toUpperCase(Locale.getDefault())).isEqualTo("A7B");
assertThat(segment.byterangeLength).isEqualTo(C.LENGTH_UNSET); assertThat(segment.byterangeLength).isEqualTo(C.LENGTH_UNSET);
assertThat(segment.byterangeOffset).isEqualTo(0); assertThat(segment.byterangeOffset).isEqualTo(0);
assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2683.ts"); assertThat(segment.url).isEqualTo("https://priv.example.com/fileSequence2683.ts");
} catch (IOException exception) { }
fail(exception.getMessage());
} public void testGapTag() throws IOException {
Uri playlistUri = Uri.parse("https://example.com/test2.m3u8");
String playlistString =
"#EXTM3U\n"
+ "#EXT-X-VERSION:3\n"
+ "#EXT-X-TARGETDURATION:5\n"
+ "#EXT-X-PLAYLIST-TYPE:VOD\n"
+ "#EXT-X-MEDIA-SEQUENCE:0\n"
+ "#EXT-X-PROGRAM-DATE-TIME:2016-09-22T02:00:01+00:00\n"
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"https://example.com/key?value=something\"\n"
+ "#EXTINF:5.005,\n"
+ "02/00/27.ts\n"
+ "#EXTINF:5.005,\n"
+ "02/00/32.ts\n"
+ "#EXT-X-KEY:METHOD=NONE\n"
+ "#EXTINF:5.005,\n"
+ "#EXT-X-GAP \n"
+ "../dummy.ts\n"
+ "#EXT-X-KEY:METHOD=AES-128,URI=\"https://key-service.bamgrid.com/1.0/key?"
+ "hex-value=9FB8989D15EEAAF8B21B860D7ED3072A\",IV=0x410C8AC18AA42EFA18B5155484F5FC34\n"
+ "#EXTINF:5.005,\n"
+ "02/00/42.ts\n"
+ "#EXTINF:5.005,\n"
+ "02/00/47.ts\n";
InputStream inputStream =
new ByteArrayInputStream(playlistString.getBytes(Charset.forName(C.UTF8_NAME)));
HlsMediaPlaylist playlist =
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
assertThat(playlist.hasEndTag).isFalse();
assertThat(playlist.segments.get(1).hasGapTag).isFalse();
assertThat(playlist.segments.get(2).hasGapTag).isTrue();
assertThat(playlist.segments.get(3).hasGapTag).isFalse();
} }
} }
...@@ -330,11 +330,27 @@ import java.util.List; ...@@ -330,11 +330,27 @@ import java.util.List;
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url); Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength, DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
null); null);
out.chunk = new HlsMediaChunk(extractorFactory, mediaDataSource, dataSpec, initDataSpec, out.chunk =
selectedUrl, muxedCaptionFormats, trackSelection.getSelectionReason(), new HlsMediaChunk(
trackSelection.getSelectionData(), startTimeUs, startTimeUs + segment.durationUs, extractorFactory,
chunkMediaSequence, discontinuitySequence, isTimestampMaster, timestampAdjuster, previous, mediaDataSource,
mediaPlaylist.drmInitData, encryptionKey, encryptionIv); dataSpec,
initDataSpec,
selectedUrl,
muxedCaptionFormats,
trackSelection.getSelectionReason(),
trackSelection.getSelectionData(),
startTimeUs,
startTimeUs + segment.durationUs,
chunkMediaSequence,
discontinuitySequence,
segment.hasGapTag,
isTimestampMaster,
timestampAdjuster,
previous,
mediaPlaylist.drmInitData,
encryptionKey,
encryptionIv);
} }
/** /**
......
...@@ -66,6 +66,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -66,6 +66,7 @@ import java.util.concurrent.atomic.AtomicInteger;
private final DataSpec initDataSpec; private final DataSpec initDataSpec;
private final boolean isEncrypted; private final boolean isEncrypted;
private final boolean isMasterTimestampSource; private final boolean isMasterTimestampSource;
private final boolean hasGapTag;
private final TimestampAdjuster timestampAdjuster; private final TimestampAdjuster timestampAdjuster;
private final boolean shouldSpliceIn; private final boolean shouldSpliceIn;
private final Extractor extractor; private final Extractor extractor;
...@@ -97,6 +98,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -97,6 +98,7 @@ import java.util.concurrent.atomic.AtomicInteger;
* @param endTimeUs The end time of the chunk in microseconds. * @param endTimeUs The end time of the chunk in microseconds.
* @param chunkMediaSequence The media sequence number of the chunk. * @param chunkMediaSequence The media sequence number of the chunk.
* @param discontinuitySequenceNumber The discontinuity sequence number of the chunk. * @param discontinuitySequenceNumber The discontinuity sequence number of the chunk.
* @param hasGapTag Whether the chunk is tagged with EXT-X-GAP.
* @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster. * @param isMasterTimestampSource True if the chunk can initialize the timestamp adjuster.
* @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number. * @param timestampAdjuster Adjuster corresponding to the provided discontinuity sequence number.
* @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null. * @param previousChunk The {@link HlsMediaChunk} that preceded this one. May be null.
...@@ -119,6 +121,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -119,6 +121,7 @@ import java.util.concurrent.atomic.AtomicInteger;
long endTimeUs, long endTimeUs,
long chunkMediaSequence, long chunkMediaSequence,
int discontinuitySequenceNumber, int discontinuitySequenceNumber,
boolean hasGapTag,
boolean isMasterTimestampSource, boolean isMasterTimestampSource,
TimestampAdjuster timestampAdjuster, TimestampAdjuster timestampAdjuster,
HlsMediaChunk previousChunk, HlsMediaChunk previousChunk,
...@@ -141,6 +144,7 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -141,6 +144,7 @@ import java.util.concurrent.atomic.AtomicInteger;
this.timestampAdjuster = timestampAdjuster; this.timestampAdjuster = timestampAdjuster;
// Note: this.dataSource and dataSource may be different. // Note: this.dataSource and dataSource may be different.
this.isEncrypted = this.dataSource instanceof Aes128DataSource; this.isEncrypted = this.dataSource instanceof Aes128DataSource;
this.hasGapTag = hasGapTag;
Extractor previousExtractor = null; Extractor previousExtractor = null;
if (previousChunk != null) { if (previousChunk != null) {
shouldSpliceIn = previousChunk.hlsUrl != hlsUrl; shouldSpliceIn = previousChunk.hlsUrl != hlsUrl;
...@@ -211,7 +215,10 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -211,7 +215,10 @@ import java.util.concurrent.atomic.AtomicInteger;
public void load() throws IOException, InterruptedException { public void load() throws IOException, InterruptedException {
maybeLoadInitData(); maybeLoadInitData();
if (!loadCanceled) { if (!loadCanceled) {
loadMedia(); if (!hasGapTag) {
loadMedia();
}
loadCompleted = true;
} }
} }
...@@ -283,7 +290,6 @@ import java.util.concurrent.atomic.AtomicInteger; ...@@ -283,7 +290,6 @@ import java.util.concurrent.atomic.AtomicInteger;
} finally { } finally {
Util.closeQuietly(dataSource); Util.closeQuietly(dataSource);
} }
loadCompleted = true;
} }
/** /**
......
...@@ -69,8 +69,16 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -69,8 +69,16 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
*/ */
public final long byterangeLength; public final long byterangeLength;
/** Whether the segment is tagged with #EXT-X-GAP. */
public final boolean hasGapTag;
/**
* @param uri See {@link #url}.
* @param byterangeOffset See {@link #byterangeOffset}.
* @param byterangeLength See {@link #byterangeLength}.
*/
public Segment(String uri, long byterangeOffset, long byterangeLength) { public Segment(String uri, long byterangeOffset, long byterangeLength) {
this(uri, 0, -1, C.TIME_UNSET, null, null, byterangeOffset, byterangeLength); this(uri, 0, -1, C.TIME_UNSET, null, null, byterangeOffset, byterangeLength, false);
} }
/** /**
...@@ -82,10 +90,18 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -82,10 +90,18 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* @param encryptionIV See {@link #encryptionIV}. * @param encryptionIV See {@link #encryptionIV}.
* @param byterangeOffset See {@link #byterangeOffset}. * @param byterangeOffset See {@link #byterangeOffset}.
* @param byterangeLength See {@link #byterangeLength}. * @param byterangeLength See {@link #byterangeLength}.
* @param hasGapTag See {@link #hasGapTag}.
*/ */
public Segment(String url, long durationUs, int relativeDiscontinuitySequence, public Segment(
long relativeStartTimeUs, String fullSegmentEncryptionKeyUri, String url,
String encryptionIV, long byterangeOffset, long byterangeLength) { long durationUs,
int relativeDiscontinuitySequence,
long relativeStartTimeUs,
String fullSegmentEncryptionKeyUri,
String encryptionIV,
long byterangeOffset,
long byterangeLength,
boolean hasGapTag) {
this.url = url; this.url = url;
this.durationUs = durationUs; this.durationUs = durationUs;
this.relativeDiscontinuitySequence = relativeDiscontinuitySequence; this.relativeDiscontinuitySequence = relativeDiscontinuitySequence;
...@@ -94,6 +110,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -94,6 +110,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
this.encryptionIV = encryptionIV; this.encryptionIV = encryptionIV;
this.byterangeOffset = byterangeOffset; this.byterangeOffset = byterangeOffset;
this.byterangeLength = byterangeLength; this.byterangeLength = byterangeLength;
this.hasGapTag = hasGapTag;
} }
@Override @Override
......
...@@ -67,6 +67,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -67,6 +67,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final String TAG_ENDLIST = "#EXT-X-ENDLIST"; private static final String TAG_ENDLIST = "#EXT-X-ENDLIST";
private static final String TAG_KEY = "#EXT-X-KEY"; private static final String TAG_KEY = "#EXT-X-KEY";
private static final String TAG_BYTERANGE = "#EXT-X-BYTERANGE"; private static final String TAG_BYTERANGE = "#EXT-X-BYTERANGE";
private static final String TAG_GAP = "#EXT-X-GAP";
private static final String TYPE_AUDIO = "AUDIO"; private static final String TYPE_AUDIO = "AUDIO";
private static final String TYPE_VIDEO = "VIDEO"; private static final String TYPE_VIDEO = "VIDEO";
...@@ -357,6 +358,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -357,6 +358,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
long segmentByteRangeOffset = 0; long segmentByteRangeOffset = 0;
long segmentByteRangeLength = C.LENGTH_UNSET; long segmentByteRangeLength = C.LENGTH_UNSET;
long segmentMediaSequence = 0; long segmentMediaSequence = 0;
boolean hasGapTag = false;
String encryptionKeyUri = null; String encryptionKeyUri = null;
String encryptionIV = null; String encryptionIV = null;
...@@ -449,6 +451,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -449,6 +451,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
C.msToUs(Util.parseXsDateTime(line.substring(line.indexOf(':') + 1))); C.msToUs(Util.parseXsDateTime(line.substring(line.indexOf(':') + 1)));
playlistStartTimeUs = programDatetimeUs - segmentStartTimeUs; playlistStartTimeUs = programDatetimeUs - segmentStartTimeUs;
} }
} else if (line.equals(TAG_GAP)) {
hasGapTag = true;
} else if (line.equals(TAG_INDEPENDENT_SEGMENTS)) {
hasIndependentSegmentsTag = true;
} else if (line.equals(TAG_ENDLIST)) {
hasEndTag = true;
} else if (!line.startsWith("#")) { } else if (!line.startsWith("#")) {
String segmentEncryptionIV; String segmentEncryptionIV;
if (encryptionKeyUri == null) { if (encryptionKeyUri == null) {
...@@ -462,19 +470,24 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -462,19 +470,24 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
if (segmentByteRangeLength == C.LENGTH_UNSET) { if (segmentByteRangeLength == C.LENGTH_UNSET) {
segmentByteRangeOffset = 0; segmentByteRangeOffset = 0;
} }
segments.add(new Segment(line, segmentDurationUs, relativeDiscontinuitySequence, segments.add(
segmentStartTimeUs, encryptionKeyUri, segmentEncryptionIV, new Segment(
segmentByteRangeOffset, segmentByteRangeLength)); line,
segmentDurationUs,
relativeDiscontinuitySequence,
segmentStartTimeUs,
encryptionKeyUri,
segmentEncryptionIV,
segmentByteRangeOffset,
segmentByteRangeLength,
hasGapTag));
segmentStartTimeUs += segmentDurationUs; segmentStartTimeUs += segmentDurationUs;
segmentDurationUs = 0; segmentDurationUs = 0;
if (segmentByteRangeLength != C.LENGTH_UNSET) { if (segmentByteRangeLength != C.LENGTH_UNSET) {
segmentByteRangeOffset += segmentByteRangeLength; segmentByteRangeOffset += segmentByteRangeLength;
} }
segmentByteRangeLength = C.LENGTH_UNSET; segmentByteRangeLength = C.LENGTH_UNSET;
} else if (line.equals(TAG_INDEPENDENT_SEGMENTS)) { hasGapTag = false;
hasIndependentSegmentsTag = true;
} else if (line.equals(TAG_ENDLIST)) {
hasEndTag = true;
} }
} }
return new HlsMediaPlaylist(playlistType, baseUri, tags, startOffsetUs, playlistStartTimeUs, return new HlsMediaPlaylist(playlistType, baseUri, tags, startOffsetUs, playlistStartTimeUs,
......
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