Commit e52a044e by christosts Committed by Oliver Woodman

Parse #EXT-X-SERVER-CONTROL and #EXT-X-PART-INF in HLS media playlists.

PiperOrigin-RevId: 338232910
parent 1051580a
...@@ -29,6 +29,54 @@ import java.util.List; ...@@ -29,6 +29,54 @@ import java.util.List;
/** Represents an HLS media playlist. */ /** Represents an HLS media playlist. */
public final class HlsMediaPlaylist extends HlsPlaylist { 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. */ /** Media segment reference. */
@SuppressWarnings("ComparableType") @SuppressWarnings("ComparableType")
public static final class Segment implements Comparable<Long> { public static final class Segment implements Comparable<Long> {
...@@ -208,8 +256,11 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -208,8 +256,11 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
*/ */
public final long targetDurationUs; 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; public final boolean hasEndTag;
/** /**
* Whether the playlist contains a #EXT-X-PROGRAM-DATE-TIME tag. * Whether the playlist contains a #EXT-X-PROGRAM-DATE-TIME tag.
...@@ -228,6 +279,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -228,6 +279,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* The total duration of the playlist in microseconds. * The total duration of the playlist in microseconds.
*/ */
public final long durationUs; public final long durationUs;
/** The attributes of the #EXT-X-SERVER-CONTROL header. */
public final ServerControl serverControl;
/** /**
* @param playlistType See {@link #playlistType}. * @param playlistType See {@link #playlistType}.
...@@ -245,6 +298,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -245,6 +298,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* @param protectionSchemes See {@link #protectionSchemes}. * @param protectionSchemes See {@link #protectionSchemes}.
* @param hasProgramDateTime See {@link #hasProgramDateTime}. * @param hasProgramDateTime See {@link #hasProgramDateTime}.
* @param segments See {@link #segments}. * @param segments See {@link #segments}.
* @param serverControl See {@link #serverControl}
*/ */
public HlsMediaPlaylist( public HlsMediaPlaylist(
@PlaylistType int playlistType, @PlaylistType int playlistType,
...@@ -257,11 +311,13 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -257,11 +311,13 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
long mediaSequence, long mediaSequence,
int version, int version,
long targetDurationUs, long targetDurationUs,
long partTargetDurationUs,
boolean hasIndependentSegments, boolean hasIndependentSegments,
boolean hasEndTag, boolean hasEndTag,
boolean hasProgramDateTime, boolean hasProgramDateTime,
@Nullable DrmInitData protectionSchemes, @Nullable DrmInitData protectionSchemes,
List<Segment> segments) { List<Segment> segments,
ServerControl serverControl) {
super(baseUri, tags, hasIndependentSegments); super(baseUri, tags, hasIndependentSegments);
this.playlistType = playlistType; this.playlistType = playlistType;
this.startTimeUs = startTimeUs; this.startTimeUs = startTimeUs;
...@@ -270,6 +326,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -270,6 +326,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
this.mediaSequence = mediaSequence; this.mediaSequence = mediaSequence;
this.version = version; this.version = version;
this.targetDurationUs = targetDurationUs; this.targetDurationUs = targetDurationUs;
this.partTargetDurationUs = partTargetDurationUs;
this.hasEndTag = hasEndTag; this.hasEndTag = hasEndTag;
this.hasProgramDateTime = hasProgramDateTime; this.hasProgramDateTime = hasProgramDateTime;
this.protectionSchemes = protectionSchemes; this.protectionSchemes = protectionSchemes;
...@@ -282,6 +339,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -282,6 +339,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
} }
this.startOffsetUs = startOffsetUs == C.TIME_UNSET ? C.TIME_UNSET this.startOffsetUs = startOffsetUs == C.TIME_UNSET ? C.TIME_UNSET
: startOffsetUs >= 0 ? startOffsetUs : durationUs + startOffsetUs; : startOffsetUs >= 0 ? startOffsetUs : durationUs + startOffsetUs;
this.serverControl = serverControl;
} }
@Override @Override
...@@ -337,11 +395,13 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -337,11 +395,13 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
mediaSequence, mediaSequence,
version, version,
targetDurationUs, targetDurationUs,
partTargetDurationUs,
hasIndependentSegments, hasIndependentSegments,
hasEndTag, hasEndTag,
hasProgramDateTime, hasProgramDateTime,
protectionSchemes, protectionSchemes,
segments); segments,
serverControl);
} }
/** /**
...@@ -363,11 +423,13 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -363,11 +423,13 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
mediaSequence, mediaSequence,
version, version,
targetDurationUs, targetDurationUs,
partTargetDurationUs,
hasIndependentSegments, hasIndependentSegments,
/* hasEndTag= */ true, /* hasEndTag= */ true,
hasProgramDateTime, hasProgramDateTime,
protectionSchemes, protectionSchemes,
segments); segments,
serverControl);
} }
} }
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
*/ */
package com.google.android.exoplayer2.source.hls.playlist; package com.google.android.exoplayer2.source.hls.playlist;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Base64; import android.util.Base64;
...@@ -68,7 +70,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -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_VERSION = "#EXT-X-VERSION";
private static final String TAG_PLAYLIST_TYPE = "#EXT-X-PLAYLIST-TYPE"; 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_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_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_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_IFRAME = "#EXT-X-I-FRAMES-ONLY";
private static final String TAG_MEDIA = "#EXT-X-MEDIA"; private static final String TAG_MEDIA = "#EXT-X-MEDIA";
...@@ -122,9 +126,20 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -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_FRAME_RATE = Pattern.compile("FRAME-RATE=([\\d\\.]+)\\b");
private static final Pattern REGEX_TARGET_DURATION = Pattern.compile(TAG_TARGET_DURATION private static final Pattern REGEX_TARGET_DURATION = Pattern.compile(TAG_TARGET_DURATION
+ ":(\\d+)\\b"); + ":(\\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_VERSION = Pattern.compile(TAG_VERSION + ":(\\d+)\\b");
private static final Pattern REGEX_PLAYLIST_TYPE = Pattern.compile(TAG_PLAYLIST_TYPE private static final Pattern REGEX_PLAYLIST_TYPE = Pattern.compile(TAG_PLAYLIST_TYPE
+ ":(.+)\\b"); + ":(.+)\\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 private static final Pattern REGEX_MEDIA_SEQUENCE = Pattern.compile(TAG_MEDIA_SEQUENCE
+ ":(\\d+)\\b"); + ":(\\d+)\\b");
private static final Pattern REGEX_MEDIA_DURATION = Pattern.compile(TAG_MEDIA_DURATION private static final Pattern REGEX_MEDIA_DURATION = Pattern.compile(TAG_MEDIA_DURATION
...@@ -394,7 +409,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -394,7 +409,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
new HlsTrackMetadataEntry( new HlsTrackMetadataEntry(
/* groupId= */ null, /* groupId= */ null,
/* name= */ null, /* name= */ null,
Assertions.checkNotNull(urlToVariantInfos.get(variant.url))); checkNotNull(urlToVariantInfos.get(variant.url)));
Metadata metadata = new Metadata(hlsMetadataEntry); Metadata metadata = new Metadata(hlsMetadataEntry);
Format format = variant.format.buildUpon().setMetadata(metadata).build(); Format format = variant.format.buildUpon().setMetadata(metadata).build();
deduplicatedVariants.add(variant.copyWithFormat(format)); deduplicatedVariants.add(variant.copyWithFormat(format));
...@@ -566,6 +581,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -566,6 +581,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
long mediaSequence = 0; long mediaSequence = 0;
int version = 1; // Default version == 1. int version = 1; // Default version == 1.
long targetDurationUs = C.TIME_UNSET; long targetDurationUs = C.TIME_UNSET;
long partTargetDurationUs = C.TIME_UNSET;
boolean hasIndependentSegmentsTag = masterPlaylist.hasIndependentSegments; boolean hasIndependentSegmentsTag = masterPlaylist.hasIndependentSegments;
boolean hasEndTag = false; boolean hasEndTag = false;
@Nullable Segment initializationSegment = null; @Nullable Segment initializationSegment = null;
...@@ -586,6 +602,13 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -586,6 +602,13 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
boolean isIFrameOnly = false; boolean isIFrameOnly = false;
long segmentMediaSequence = 0; long segmentMediaSequence = 0;
boolean hasGapTag = false; 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; DrmInitData playlistProtectionSchemes = null;
String fullSegmentEncryptionKeyUri = null; String fullSegmentEncryptionKeyUri = null;
...@@ -614,6 +637,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -614,6 +637,11 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
isIFrameOnly = true; isIFrameOnly = true;
} else if (line.startsWith(TAG_START)) { } else if (line.startsWith(TAG_START)) {
startOffsetUs = (long) (parseDoubleAttr(line, REGEX_TIME_OFFSET) * C.MICROS_PER_SECOND); 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)) { } else if (line.startsWith(TAG_INIT_SEGMENT)) {
String uri = parseStringAttr(line, REGEX_URI, variableDefinitions); String uri = parseStringAttr(line, REGEX_URI, variableDefinitions);
String byteRange = parseOptionalStringAttr(line, REGEX_ATTR_BYTERANGE, variableDefinitions); String byteRange = parseOptionalStringAttr(line, REGEX_ATTR_BYTERANGE, variableDefinitions);
...@@ -786,6 +814,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -786,6 +814,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
hasGapTag = false; hasGapTag = false;
} }
} }
return new HlsMediaPlaylist( return new HlsMediaPlaylist(
playlistType, playlistType,
baseUri, baseUri,
...@@ -797,11 +826,13 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -797,11 +826,13 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
mediaSequence, mediaSequence,
version, version,
targetDurationUs, targetDurationUs,
partTargetDurationUs,
hasIndependentSegmentsTag, hasIndependentSegmentsTag,
hasEndTag, hasEndTag,
/* hasProgramDateTime= */ playlistStartTimeUs != 0, /* hasProgramDateTime= */ playlistStartTimeUs != 0,
playlistProtectionSchemes, playlistProtectionSchemes,
segments); segments,
serverControl);
} }
@C.SelectionFlags @C.SelectionFlags
...@@ -866,6 +897,33 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -866,6 +897,33 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
return null; 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) { private static String parseEncryptionScheme(String method) {
return METHOD_SAMPLE_AES_CENC.equals(method) || METHOD_SAMPLE_AES_CTR.equals(method) return METHOD_SAMPLE_AES_CENC.equals(method) || METHOD_SAMPLE_AES_CTR.equals(method)
? C.CENC_TYPE_cenc ? C.CENC_TYPE_cenc
...@@ -879,7 +937,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -879,7 +937,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static int parseOptionalIntAttr(String line, Pattern pattern, int defaultValue) { private static int parseOptionalIntAttr(String line, Pattern pattern, int defaultValue) {
Matcher matcher = pattern.matcher(line); Matcher matcher = pattern.matcher(line);
if (matcher.find()) { if (matcher.find()) {
return Integer.parseInt(Assertions.checkNotNull(matcher.group(1))); return Integer.parseInt(checkNotNull(matcher.group(1)));
} }
return defaultValue; return defaultValue;
} }
...@@ -914,13 +972,20 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -914,13 +972,20 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
@PolyNull String defaultValue, @PolyNull String defaultValue,
Map<String, String> variableDefinitions) { Map<String, String> variableDefinitions) {
Matcher matcher = pattern.matcher(line); Matcher matcher = pattern.matcher(line);
@PolyNull @PolyNull String value = matcher.find() ? checkNotNull(matcher.group(1)) : defaultValue;
String value = matcher.find() ? Assertions.checkNotNull(matcher.group(1)) : defaultValue;
return variableDefinitions.isEmpty() || value == null return variableDefinitions.isEmpty() || value == null
? value ? value
: replaceVariableReferences(value, variableDefinitions); : 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( private static String replaceVariableReferences(
String string, Map<String, String> variableDefinitions) { String string, Map<String, String> variableDefinitions) {
Matcher matcher = REGEX_VARIABLE_REFERENCE.matcher(string); Matcher matcher = REGEX_VARIABLE_REFERENCE.matcher(string);
...@@ -970,7 +1035,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -970,7 +1035,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
return true; return true;
} }
if (!extraLines.isEmpty()) { if (!extraLines.isEmpty()) {
next = Assertions.checkNotNull(extraLines.poll()); next = checkNotNull(extraLines.poll());
return true; return true;
} }
while ((next = reader.readLine()) != null) { while ((next = reader.readLine()) != null) {
...@@ -992,7 +1057,5 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -992,7 +1057,5 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
throw new NoSuchElementException(); throw new NoSuchElementException();
} }
} }
} }
} }
...@@ -45,7 +45,7 @@ public class HlsMediaPlaylistParserTest { ...@@ -45,7 +45,7 @@ public class HlsMediaPlaylistParserTest {
"#EXTM3U\n" "#EXTM3U\n"
+ "#EXT-X-VERSION:3\n" + "#EXT-X-VERSION:3\n"
+ "#EXT-X-PLAYLIST-TYPE:VOD\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-TARGETDURATION:8\n"
+ "#EXT-X-MEDIA-SEQUENCE:2679\n" + "#EXT-X-MEDIA-SEQUENCE:2679\n"
+ "#EXT-X-DISCONTINUITY-SEQUENCE:4\n" + "#EXT-X-DISCONTINUITY-SEQUENCE:4\n"
...@@ -86,6 +86,8 @@ public class HlsMediaPlaylistParserTest { ...@@ -86,6 +86,8 @@ public class HlsMediaPlaylistParserTest {
assertThat(mediaPlaylist.version).isEqualTo(3); assertThat(mediaPlaylist.version).isEqualTo(3);
assertThat(mediaPlaylist.hasEndTag).isTrue(); assertThat(mediaPlaylist.hasEndTag).isTrue();
assertThat(mediaPlaylist.protectionSchemes).isNull(); assertThat(mediaPlaylist.protectionSchemes).isNull();
assertThat(mediaPlaylist.targetDurationUs).isEqualTo(8000000);
assertThat(mediaPlaylist.partTargetDurationUs).isEqualTo(C.TIME_UNSET);
List<Segment> segments = mediaPlaylist.segments; List<Segment> segments = mediaPlaylist.segments;
assertThat(segments).isNotNull(); assertThat(segments).isNotNull();
assertThat(segments).hasSize(5); assertThat(segments).hasSize(5);
...@@ -219,6 +221,7 @@ public class HlsMediaPlaylistParserTest { ...@@ -219,6 +221,7 @@ public class HlsMediaPlaylistParserTest {
+ "https://priv.example.com/2.ts\n" + "https://priv.example.com/2.ts\n"
+ "#EXT-X-ENDLIST\n"; + "#EXT-X-ENDLIST\n";
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString)); InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
HlsMediaPlaylist playlist = HlsMediaPlaylist playlist =
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream); (HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
assertThat(playlist.protectionSchemes.schemeType).isEqualTo(C.CENC_TYPE_cenc); assertThat(playlist.protectionSchemes.schemeType).isEqualTo(C.CENC_TYPE_cenc);
...@@ -227,6 +230,76 @@ public class HlsMediaPlaylistParserTest { ...@@ -227,6 +230,76 @@ public class HlsMediaPlaylistParserTest {
} }
@Test @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 { public void multipleExtXKeysForSingleSegment() throws Exception {
Uri playlistUri = Uri.parse("https://example.com/test.m3u8"); Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
String playlistString = String playlistString =
......
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