mirror of
https://github.com/samsonjs/media.git
synced 2026-03-28 09:55:48 +00:00
Parse #EXT-X-SERVER-CONTROL and #EXT-X-PART-INF in HLS media playlists.
PiperOrigin-RevId: 338232910
This commit is contained in:
parent
1051580a63
commit
e52a044ec5
3 changed files with 211 additions and 13 deletions
|
|
@ -29,6 +29,54 @@ import java.util.List;
|
|||
/** Represents an HLS media playlist. */
|
||||
public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
|
||||
/** Server control attributes. */
|
||||
public static final class ServerControl {
|
||||
|
||||
/**
|
||||
* The skip boundary for delta updates in microseconds, or {@link C#TIME_UNSET} if delta updates
|
||||
* are not supported.
|
||||
*/
|
||||
public final long skipUntilUs;
|
||||
/**
|
||||
* Whether the playlist can produce delta updates that skip older #EXT-X-DATERANGE tags in
|
||||
* addition to media segments.
|
||||
*/
|
||||
public final boolean canSkipDateRanges;
|
||||
/**
|
||||
* The server-recommended live offset in microseconds, or {@link C#TIME_UNSET} if none defined.
|
||||
*/
|
||||
public final long holdBackUs;
|
||||
/**
|
||||
* The server-recommended live offset in microseconds in low-latency mode, or {@link
|
||||
* C#TIME_UNSET} if none defined.
|
||||
*/
|
||||
public final long partHoldBackUs;
|
||||
/** Whether the server supports blocking playlist reload. */
|
||||
public final boolean canBlockReload;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*
|
||||
* @param skipUntilUs See {@link #skipUntilUs}.
|
||||
* @param canSkipDateRanges See {@link #canSkipDateRanges}.
|
||||
* @param holdBackUs See {@link #holdBackUs}.
|
||||
* @param partHoldBackUs See {@link #partHoldBackUs}.
|
||||
* @param canBlockReload See {@link #canBlockReload}.
|
||||
*/
|
||||
public ServerControl(
|
||||
long skipUntilUs,
|
||||
boolean canSkipDateRanges,
|
||||
long holdBackUs,
|
||||
long partHoldBackUs,
|
||||
boolean canBlockReload) {
|
||||
this.skipUntilUs = skipUntilUs;
|
||||
this.canSkipDateRanges = canSkipDateRanges;
|
||||
this.holdBackUs = holdBackUs;
|
||||
this.partHoldBackUs = partHoldBackUs;
|
||||
this.canBlockReload = canBlockReload;
|
||||
}
|
||||
}
|
||||
|
||||
/** Media segment reference. */
|
||||
@SuppressWarnings("ComparableType")
|
||||
public static final class Segment implements Comparable<Long> {
|
||||
|
|
@ -208,8 +256,11 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||
*/
|
||||
public final long targetDurationUs;
|
||||
/**
|
||||
* Whether the playlist contains the #EXT-X-ENDLIST tag.
|
||||
* The target duration for segment parts, as defined by #EXT-X-PART-INF, or {@link C#TIME_UNSET}
|
||||
* if undefined.
|
||||
*/
|
||||
public final long partTargetDurationUs;
|
||||
/** Whether the playlist contains the #EXT-X-ENDLIST tag. */
|
||||
public final boolean hasEndTag;
|
||||
/**
|
||||
* Whether the playlist contains a #EXT-X-PROGRAM-DATE-TIME tag.
|
||||
|
|
@ -228,6 +279,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||
* The total duration of the playlist in microseconds.
|
||||
*/
|
||||
public final long durationUs;
|
||||
/** The attributes of the #EXT-X-SERVER-CONTROL header. */
|
||||
public final ServerControl serverControl;
|
||||
|
||||
/**
|
||||
* @param playlistType See {@link #playlistType}.
|
||||
|
|
@ -245,6 +298,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||
* @param protectionSchemes See {@link #protectionSchemes}.
|
||||
* @param hasProgramDateTime See {@link #hasProgramDateTime}.
|
||||
* @param segments See {@link #segments}.
|
||||
* @param serverControl See {@link #serverControl}
|
||||
*/
|
||||
public HlsMediaPlaylist(
|
||||
@PlaylistType int playlistType,
|
||||
|
|
@ -257,11 +311,13 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||
long mediaSequence,
|
||||
int version,
|
||||
long targetDurationUs,
|
||||
long partTargetDurationUs,
|
||||
boolean hasIndependentSegments,
|
||||
boolean hasEndTag,
|
||||
boolean hasProgramDateTime,
|
||||
@Nullable DrmInitData protectionSchemes,
|
||||
List<Segment> segments) {
|
||||
List<Segment> segments,
|
||||
ServerControl serverControl) {
|
||||
super(baseUri, tags, hasIndependentSegments);
|
||||
this.playlistType = playlistType;
|
||||
this.startTimeUs = startTimeUs;
|
||||
|
|
@ -270,6 +326,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||
this.mediaSequence = mediaSequence;
|
||||
this.version = version;
|
||||
this.targetDurationUs = targetDurationUs;
|
||||
this.partTargetDurationUs = partTargetDurationUs;
|
||||
this.hasEndTag = hasEndTag;
|
||||
this.hasProgramDateTime = hasProgramDateTime;
|
||||
this.protectionSchemes = protectionSchemes;
|
||||
|
|
@ -282,6 +339,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||
}
|
||||
this.startOffsetUs = startOffsetUs == C.TIME_UNSET ? C.TIME_UNSET
|
||||
: startOffsetUs >= 0 ? startOffsetUs : durationUs + startOffsetUs;
|
||||
this.serverControl = serverControl;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -337,11 +395,13 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||
mediaSequence,
|
||||
version,
|
||||
targetDurationUs,
|
||||
partTargetDurationUs,
|
||||
hasIndependentSegments,
|
||||
hasEndTag,
|
||||
hasProgramDateTime,
|
||||
protectionSchemes,
|
||||
segments);
|
||||
segments,
|
||||
serverControl);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -363,11 +423,13 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||
mediaSequence,
|
||||
version,
|
||||
targetDurationUs,
|
||||
partTargetDurationUs,
|
||||
hasIndependentSegments,
|
||||
/* hasEndTag= */ true,
|
||||
hasProgramDateTime,
|
||||
protectionSchemes,
|
||||
segments);
|
||||
segments,
|
||||
serverControl);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@
|
|||
*/
|
||||
package com.google.android.exoplayer2.source.hls.playlist;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
|
|
@ -68,7 +70,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
private static final String TAG_VERSION = "#EXT-X-VERSION";
|
||||
private static final String TAG_PLAYLIST_TYPE = "#EXT-X-PLAYLIST-TYPE";
|
||||
private static final String TAG_DEFINE = "#EXT-X-DEFINE";
|
||||
private static final String TAG_SERVER_CONTROL = "#EXT-X-SERVER-CONTROL";
|
||||
private static final String TAG_STREAM_INF = "#EXT-X-STREAM-INF";
|
||||
private static final String TAG_PART_INF = "#EXT-X-PART-INF";
|
||||
private static final String TAG_I_FRAME_STREAM_INF = "#EXT-X-I-FRAME-STREAM-INF";
|
||||
private static final String TAG_IFRAME = "#EXT-X-I-FRAMES-ONLY";
|
||||
private static final String TAG_MEDIA = "#EXT-X-MEDIA";
|
||||
|
|
@ -122,9 +126,20 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
private static final Pattern REGEX_FRAME_RATE = Pattern.compile("FRAME-RATE=([\\d\\.]+)\\b");
|
||||
private static final Pattern REGEX_TARGET_DURATION = Pattern.compile(TAG_TARGET_DURATION
|
||||
+ ":(\\d+)\\b");
|
||||
private static final Pattern REGEX_PART_TARGET_DURATION =
|
||||
Pattern.compile("PART-TARGET=([\\d\\.]+)\\b");
|
||||
private static final Pattern REGEX_VERSION = Pattern.compile(TAG_VERSION + ":(\\d+)\\b");
|
||||
private static final Pattern REGEX_PLAYLIST_TYPE = Pattern.compile(TAG_PLAYLIST_TYPE
|
||||
+ ":(.+)\\b");
|
||||
private static final Pattern REGEX_CAN_SKIP_UNTIL =
|
||||
Pattern.compile("CAN-SKIP-UNTIL=([\\d\\.]+)\\b");
|
||||
private static final Pattern REGEX_CAN_SKIP_DATE_RANGES =
|
||||
compileBooleanAttrPattern("CAN-SKIP-DATERANGES");
|
||||
private static final Pattern REGEX_HOLD_BACK = Pattern.compile("[:|,]HOLD-BACK=([\\d\\.]+)\\b");
|
||||
private static final Pattern REGEX_PART_HOLD_BACK =
|
||||
Pattern.compile("PART-HOLD-BACK=([\\d\\.]+)\\b");
|
||||
private static final Pattern REGEX_CAN_BLOCK_RELOAD =
|
||||
compileBooleanAttrPattern("CAN-BLOCK-RELOAD");
|
||||
private static final Pattern REGEX_MEDIA_SEQUENCE = Pattern.compile(TAG_MEDIA_SEQUENCE
|
||||
+ ":(\\d+)\\b");
|
||||
private static final Pattern REGEX_MEDIA_DURATION = Pattern.compile(TAG_MEDIA_DURATION
|
||||
|
|
@ -394,7 +409,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
new HlsTrackMetadataEntry(
|
||||
/* groupId= */ null,
|
||||
/* name= */ null,
|
||||
Assertions.checkNotNull(urlToVariantInfos.get(variant.url)));
|
||||
checkNotNull(urlToVariantInfos.get(variant.url)));
|
||||
Metadata metadata = new Metadata(hlsMetadataEntry);
|
||||
Format format = variant.format.buildUpon().setMetadata(metadata).build();
|
||||
deduplicatedVariants.add(variant.copyWithFormat(format));
|
||||
|
|
@ -566,6 +581,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
long mediaSequence = 0;
|
||||
int version = 1; // Default version == 1.
|
||||
long targetDurationUs = C.TIME_UNSET;
|
||||
long partTargetDurationUs = C.TIME_UNSET;
|
||||
boolean hasIndependentSegmentsTag = masterPlaylist.hasIndependentSegments;
|
||||
boolean hasEndTag = false;
|
||||
@Nullable Segment initializationSegment = null;
|
||||
|
|
@ -586,6 +602,13 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
boolean isIFrameOnly = false;
|
||||
long segmentMediaSequence = 0;
|
||||
boolean hasGapTag = false;
|
||||
HlsMediaPlaylist.ServerControl serverControl =
|
||||
new HlsMediaPlaylist.ServerControl(
|
||||
/* skipUntilUs= */ C.TIME_UNSET,
|
||||
/* canSkipDateRanges= */ false,
|
||||
/* holdBackUs= */ C.TIME_UNSET,
|
||||
/* partHoldBackUs= */ C.TIME_UNSET,
|
||||
/* canBlockReload= */ false);
|
||||
|
||||
DrmInitData playlistProtectionSchemes = null;
|
||||
String fullSegmentEncryptionKeyUri = null;
|
||||
|
|
@ -614,6 +637,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
isIFrameOnly = true;
|
||||
} else if (line.startsWith(TAG_START)) {
|
||||
startOffsetUs = (long) (parseDoubleAttr(line, REGEX_TIME_OFFSET) * C.MICROS_PER_SECOND);
|
||||
} else if (line.startsWith(TAG_SERVER_CONTROL)) {
|
||||
serverControl = parseServerControl(line);
|
||||
} else if (line.startsWith(TAG_PART_INF)) {
|
||||
double partTargetDurationSeconds = parseDoubleAttr(line, REGEX_PART_TARGET_DURATION);
|
||||
partTargetDurationUs = (long) (partTargetDurationSeconds * C.MICROS_PER_SECOND);
|
||||
} else if (line.startsWith(TAG_INIT_SEGMENT)) {
|
||||
String uri = parseStringAttr(line, REGEX_URI, variableDefinitions);
|
||||
String byteRange = parseOptionalStringAttr(line, REGEX_ATTR_BYTERANGE, variableDefinitions);
|
||||
|
|
@ -786,6 +814,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
hasGapTag = false;
|
||||
}
|
||||
}
|
||||
|
||||
return new HlsMediaPlaylist(
|
||||
playlistType,
|
||||
baseUri,
|
||||
|
|
@ -797,11 +826,13 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
mediaSequence,
|
||||
version,
|
||||
targetDurationUs,
|
||||
partTargetDurationUs,
|
||||
hasIndependentSegmentsTag,
|
||||
hasEndTag,
|
||||
/* hasProgramDateTime= */ playlistStartTimeUs != 0,
|
||||
playlistProtectionSchemes,
|
||||
segments);
|
||||
segments,
|
||||
serverControl);
|
||||
}
|
||||
|
||||
@C.SelectionFlags
|
||||
|
|
@ -866,6 +897,33 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
return null;
|
||||
}
|
||||
|
||||
private static HlsMediaPlaylist.ServerControl parseServerControl(String line) {
|
||||
double skipUntilSeconds =
|
||||
parseOptionalDoubleAttr(line, REGEX_CAN_SKIP_UNTIL, /* defaultValue= */ C.TIME_UNSET);
|
||||
long skipUntilUs =
|
||||
skipUntilSeconds == C.TIME_UNSET
|
||||
? C.TIME_UNSET
|
||||
: (long) (skipUntilSeconds * C.MICROS_PER_SECOND);
|
||||
boolean canSkipDateRanges =
|
||||
parseOptionalBooleanAttribute(line, REGEX_CAN_SKIP_DATE_RANGES, /* defaultValue= */ false);
|
||||
double holdBackSeconds =
|
||||
parseOptionalDoubleAttr(line, REGEX_HOLD_BACK, /* defaultValue= */ C.TIME_UNSET);
|
||||
long holdBackUs =
|
||||
holdBackSeconds == C.TIME_UNSET
|
||||
? C.TIME_UNSET
|
||||
: (long) (holdBackSeconds * C.MICROS_PER_SECOND);
|
||||
double partHoldBackSeconds = parseOptionalDoubleAttr(line, REGEX_PART_HOLD_BACK, C.TIME_UNSET);
|
||||
long partHoldBackUs =
|
||||
partHoldBackSeconds == C.TIME_UNSET
|
||||
? C.TIME_UNSET
|
||||
: (long) (partHoldBackSeconds * C.MICROS_PER_SECOND);
|
||||
boolean canBlockReload =
|
||||
parseOptionalBooleanAttribute(line, REGEX_CAN_BLOCK_RELOAD, /* defaultValue= */ false);
|
||||
|
||||
return new HlsMediaPlaylist.ServerControl(
|
||||
skipUntilUs, canSkipDateRanges, holdBackUs, partHoldBackUs, canBlockReload);
|
||||
}
|
||||
|
||||
private static String parseEncryptionScheme(String method) {
|
||||
return METHOD_SAMPLE_AES_CENC.equals(method) || METHOD_SAMPLE_AES_CTR.equals(method)
|
||||
? C.CENC_TYPE_cenc
|
||||
|
|
@ -879,7 +937,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
private static int parseOptionalIntAttr(String line, Pattern pattern, int defaultValue) {
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
if (matcher.find()) {
|
||||
return Integer.parseInt(Assertions.checkNotNull(matcher.group(1)));
|
||||
return Integer.parseInt(checkNotNull(matcher.group(1)));
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
|
@ -914,13 +972,20 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
@PolyNull String defaultValue,
|
||||
Map<String, String> variableDefinitions) {
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
@PolyNull
|
||||
String value = matcher.find() ? Assertions.checkNotNull(matcher.group(1)) : defaultValue;
|
||||
@PolyNull String value = matcher.find() ? checkNotNull(matcher.group(1)) : defaultValue;
|
||||
return variableDefinitions.isEmpty() || value == null
|
||||
? value
|
||||
: replaceVariableReferences(value, variableDefinitions);
|
||||
}
|
||||
|
||||
private static double parseOptionalDoubleAttr(String line, Pattern pattern, double defaultValue) {
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
if (matcher.find()) {
|
||||
return Double.parseDouble(checkNotNull(matcher.group(1)));
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static String replaceVariableReferences(
|
||||
String string, Map<String, String> variableDefinitions) {
|
||||
Matcher matcher = REGEX_VARIABLE_REFERENCE.matcher(string);
|
||||
|
|
@ -970,7 +1035,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
return true;
|
||||
}
|
||||
if (!extraLines.isEmpty()) {
|
||||
next = Assertions.checkNotNull(extraLines.poll());
|
||||
next = checkNotNull(extraLines.poll());
|
||||
return true;
|
||||
}
|
||||
while ((next = reader.readLine()) != null) {
|
||||
|
|
@ -992,7 +1057,5 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||
throw new NoSuchElementException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ public class HlsMediaPlaylistParserTest {
|
|||
"#EXTM3U\n"
|
||||
+ "#EXT-X-VERSION:3\n"
|
||||
+ "#EXT-X-PLAYLIST-TYPE:VOD\n"
|
||||
+ "#EXT-X-START:TIME-OFFSET=-25"
|
||||
+ "#EXT-X-START:TIME-OFFSET=-25\n"
|
||||
+ "#EXT-X-TARGETDURATION:8\n"
|
||||
+ "#EXT-X-MEDIA-SEQUENCE:2679\n"
|
||||
+ "#EXT-X-DISCONTINUITY-SEQUENCE:4\n"
|
||||
|
|
@ -86,6 +86,8 @@ public class HlsMediaPlaylistParserTest {
|
|||
assertThat(mediaPlaylist.version).isEqualTo(3);
|
||||
assertThat(mediaPlaylist.hasEndTag).isTrue();
|
||||
assertThat(mediaPlaylist.protectionSchemes).isNull();
|
||||
assertThat(mediaPlaylist.targetDurationUs).isEqualTo(8000000);
|
||||
assertThat(mediaPlaylist.partTargetDurationUs).isEqualTo(C.TIME_UNSET);
|
||||
List<Segment> segments = mediaPlaylist.segments;
|
||||
assertThat(segments).isNotNull();
|
||||
assertThat(segments).hasSize(5);
|
||||
|
|
@ -219,6 +221,7 @@ public class HlsMediaPlaylistParserTest {
|
|||
+ "https://priv.example.com/2.ts\n"
|
||||
+ "#EXT-X-ENDLIST\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
assertThat(playlist.protectionSchemes.schemeType).isEqualTo(C.CENC_TYPE_cenc);
|
||||
|
|
@ -226,6 +229,76 @@ public class HlsMediaPlaylistParserTest {
|
|||
assertThat(playlist.protectionSchemes.get(0).hasData()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseMediaPlaylist_withPartMediaInformation_succeeds() throws IOException {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
||||
String playlistString =
|
||||
"#EXTM3U\n"
|
||||
+ "#EXT-X-TARGETDURATION:4\n"
|
||||
+ "#EXT-X-VERSION:6\n"
|
||||
+ "#EXT-X-SERVER-CONTROL:PART-HOLD-BACK=1.234\n"
|
||||
+ "#EXT-X-PART-INF:PART-TARGET=0.5\n"
|
||||
+ "#EXT-X-MEDIA-SEQUENCE:266\n"
|
||||
+ "#EXT-X-PROGRAM-DATE-TIME:2019-02-14T02:13:36.106Z\n"
|
||||
+ "#EXT-X-MAP:URI=\"init.mp4\"\n"
|
||||
+ "#EXTINF:4.00008,\n"
|
||||
+ "fileSequence266.mp4";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
assertThat(playlist.partTargetDurationUs).isEqualTo(500000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseMediaPlaylist_withoutServerControl_serverControlDefaultValues()
|
||||
throws IOException {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
||||
String playlistString =
|
||||
"#EXTM3U\n"
|
||||
+ "#EXT-X-MEDIA-SEQUENCE:0\n"
|
||||
+ "#EXTINF:8,\n"
|
||||
+ "https://priv.example.com/1.ts\n"
|
||||
+ "#EXT-X-ENDLIST\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
assertThat(playlist.serverControl.canBlockReload).isFalse();
|
||||
assertThat(playlist.serverControl.partHoldBackUs).isEqualTo(C.TIME_UNSET);
|
||||
assertThat(playlist.serverControl.holdBackUs).isEqualTo(C.TIME_UNSET);
|
||||
assertThat(playlist.serverControl.skipUntilUs).isEqualTo(C.TIME_UNSET);
|
||||
assertThat(playlist.serverControl.canSkipDateRanges).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseMediaPlaylist_withServerControl_succeeds() throws IOException {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
||||
String playlistString =
|
||||
"#EXTM3U\n"
|
||||
+ "#EXT-X-TARGETDURATION:4\n"
|
||||
+ "#EXT-X-VERSION:6\n"
|
||||
+ "#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,HOLD-BACK=18.5,PART-HOLD-BACK=1.234,"
|
||||
+ "CAN-SKIP-UNTIL=24.0,CAN-SKIP-DATERANGES=YES\n"
|
||||
+ "#EXT-X-PART-INF:PART-TARGET=0.5\n"
|
||||
+ "#EXT-X-MEDIA-SEQUENCE:266\n"
|
||||
+ "#EXT-X-PROGRAM-DATE-TIME:2019-02-14T02:13:36.106Z\n"
|
||||
+ "#EXT-X-MAP:URI=\"init.mp4\"\n"
|
||||
+ "#EXTINF:4.00008,\n"
|
||||
+ "fileSequence266.mp4";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
assertThat(playlist.serverControl.canBlockReload).isTrue();
|
||||
assertThat(playlist.serverControl.partHoldBackUs).isEqualTo(1234000);
|
||||
assertThat(playlist.serverControl.holdBackUs).isEqualTo(18500000);
|
||||
assertThat(playlist.serverControl.skipUntilUs).isEqualTo(24000000);
|
||||
assertThat(playlist.serverControl.canSkipDateRanges).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleExtXKeysForSingleSegment() throws Exception {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
||||
|
|
|
|||
Loading…
Reference in a new issue