Parse #EXT-X-PRELOAD-HINT tag
Issue: #5011 PiperOrigin-RevId: 339738292
This commit is contained in:
parent
8f6b46f570
commit
0d6ec21b30
@ -93,11 +93,14 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
private static final String TAG_BYTERANGE = "#EXT-X-BYTERANGE";
|
||||
private static final String TAG_GAP = "#EXT-X-GAP";
|
||||
private static final String TAG_SKIP = "#EXT-X-SKIP";
|
||||
private static final String TAG_PRELOAD_HINT = "#EXT-X-PRELOAD-HINT";
|
||||
|
||||
private static final String TYPE_AUDIO = "AUDIO";
|
||||
private static final String TYPE_VIDEO = "VIDEO";
|
||||
private static final String TYPE_SUBTITLES = "SUBTITLES";
|
||||
private static final String TYPE_CLOSED_CAPTIONS = "CLOSED-CAPTIONS";
|
||||
private static final String TYPE_PART = "PART";
|
||||
private static final String TYPE_MAP = "MAP";
|
||||
|
||||
private static final String METHOD_NONE = "NONE";
|
||||
private static final String METHOD_AES_128 = "AES-128";
|
||||
@ -157,6 +160,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
+ ":(\\d+(?:@\\d+)?)\\b");
|
||||
private static final Pattern REGEX_ATTR_BYTERANGE =
|
||||
Pattern.compile("BYTERANGE=\"(\\d+(?:@\\d+)?)\\b\"");
|
||||
private static final Pattern REGEX_BYTERANGE_START = Pattern.compile("BYTERANGE-START=(\\d+)\\b");
|
||||
private static final Pattern REGEX_BYTERANGE_LENGTH =
|
||||
Pattern.compile("BYTERANGE-LENGTH=(\\d+)\\b");
|
||||
private static final Pattern REGEX_METHOD =
|
||||
Pattern.compile(
|
||||
"METHOD=("
|
||||
@ -178,6 +184,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
private static final Pattern REGEX_IV = Pattern.compile("IV=([^,.*]+)");
|
||||
private static final Pattern REGEX_TYPE = Pattern.compile("TYPE=(" + TYPE_AUDIO + "|" + TYPE_VIDEO
|
||||
+ "|" + TYPE_SUBTITLES + "|" + TYPE_CLOSED_CAPTIONS + ")");
|
||||
private static final Pattern REGEX_PRELOAD_HINT_TYPE =
|
||||
Pattern.compile("TYPE=(" + TYPE_PART + "|" + TYPE_MAP + ")");
|
||||
private static final Pattern REGEX_LANGUAGE = Pattern.compile("LANGUAGE=\"(.+?)\"");
|
||||
private static final Pattern REGEX_NAME = Pattern.compile("NAME=\"(.+?)\"");
|
||||
private static final Pattern REGEX_GROUP_ID = Pattern.compile("GROUP-ID=\"(.+?)\"");
|
||||
@ -592,6 +600,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
long partTargetDurationUs = C.TIME_UNSET;
|
||||
boolean hasIndependentSegmentsTag = masterPlaylist.hasIndependentSegments;
|
||||
boolean hasEndTag = false;
|
||||
boolean seenPreloadPart = false;
|
||||
@Nullable Segment initializationSegment = null;
|
||||
HashMap<String, String> variableDefinitions = new HashMap<>();
|
||||
HashMap<String, Segment> urlToInferredInitSegment = new HashMap<>();
|
||||
@ -760,6 +769,42 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
hasIndependentSegmentsTag = true;
|
||||
} else if (line.equals(TAG_ENDLIST)) {
|
||||
hasEndTag = true;
|
||||
} else if (line.startsWith(TAG_PRELOAD_HINT) && !seenPreloadPart) {
|
||||
String type = parseStringAttr(line, REGEX_PRELOAD_HINT_TYPE, variableDefinitions);
|
||||
if (!TYPE_PART.equals(type)) {
|
||||
continue;
|
||||
}
|
||||
String url = parseStringAttr(line, REGEX_URI, variableDefinitions);
|
||||
long byteRangeStart =
|
||||
parseOptionalLongAttr(line, REGEX_BYTERANGE_START, /* defaultValue= */ 0);
|
||||
long byteRangeLength =
|
||||
parseOptionalLongAttr(line, REGEX_BYTERANGE_LENGTH, /* defaultValue= */ C.TIME_UNSET);
|
||||
@Nullable
|
||||
String segmentEncryptionIV =
|
||||
getSegmentEncryptionIV(
|
||||
segmentMediaSequence, fullSegmentEncryptionKeyUri, fullSegmentEncryptionIV);
|
||||
if (cachedDrmInitData == null && !currentSchemeDatas.isEmpty()) {
|
||||
SchemeData[] schemeDatas = currentSchemeDatas.values().toArray(new SchemeData[0]);
|
||||
cachedDrmInitData = new DrmInitData(encryptionScheme, schemeDatas);
|
||||
if (playlistProtectionSchemes == null) {
|
||||
playlistProtectionSchemes = getPlaylistProtectionSchemes(encryptionScheme, schemeDatas);
|
||||
}
|
||||
}
|
||||
parts.add(
|
||||
new Part(
|
||||
url,
|
||||
initializationSegment,
|
||||
/* durationUs= */ 0,
|
||||
relativeDiscontinuitySequence,
|
||||
partStartTimeUs,
|
||||
cachedDrmInitData,
|
||||
fullSegmentEncryptionKeyUri,
|
||||
segmentEncryptionIV,
|
||||
byteRangeStart,
|
||||
byteRangeLength,
|
||||
/* hasGapTag= */ false,
|
||||
/* isIndependent= */ false));
|
||||
seenPreloadPart = true;
|
||||
} else if (line.startsWith(TAG_PART)) {
|
||||
@Nullable
|
||||
String segmentEncryptionIV =
|
||||
@ -1027,6 +1072,14 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
return Long.parseLong(parseStringAttr(line, pattern, Collections.emptyMap()));
|
||||
}
|
||||
|
||||
private static long parseOptionalLongAttr(String line, Pattern pattern, long defaultValue) {
|
||||
Matcher matcher = pattern.matcher(line);
|
||||
if (matcher.find()) {
|
||||
return Long.parseLong(checkNotNull(matcher.group(1)));
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static double parseDoubleAttr(String line, Pattern pattern) throws ParserException {
|
||||
return Double.parseDouble(parseStringAttr(line, pattern, Collections.emptyMap()));
|
||||
}
|
||||
|
@ -487,6 +487,155 @@ public class HlsMediaPlaylistParserTest {
|
||||
assertThat(part.encryptionIV).isEqualTo("0x410C8AC18AA42EFA18B5155484F5FC34");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseMediaPlaylist_withPreloadHintTypePart_hasPreloadPartWithAllAttributes()
|
||||
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-MEDIA-SEQUENCE:266\n"
|
||||
+ "#EXT-X-MAP:URI=\"map.mp4\"\n"
|
||||
+ "#EXT-X-KEY:METHOD=AES-128,KEYFORMAT=\"identity\""
|
||||
+ ", IV=0x410C8AC18AA42EFA18B5155484F5FC34,URI=\"fake://foo.bar/uri\"\n"
|
||||
+ "#EXTINF:4.00000,\n"
|
||||
+ "fileSequence266.mp4\n"
|
||||
+ "#EXT-X-PART:DURATION=2.00000,URI=\"part267.1.ts\"\n"
|
||||
+ "#EXT-X-PRELOAD-HINT:TYPE=PART,BYTERANGE-START=1234,BYTERANGE-LENGTH=1000,"
|
||||
+ "URI=\"filePart267.2.ts\"\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
assertThat(playlist.segments.get(0).parts).isEmpty();
|
||||
assertThat(playlist.trailingParts).hasSize(2);
|
||||
HlsMediaPlaylist.Part preloadPart = playlist.trailingParts.get(1);
|
||||
assertThat(preloadPart.durationUs).isEqualTo(0L);
|
||||
assertThat(preloadPart.url).isEqualTo("filePart267.2.ts");
|
||||
assertThat(preloadPart.byteRangeLength).isEqualTo(1000);
|
||||
assertThat(preloadPart.byteRangeOffset).isEqualTo(1234);
|
||||
assertThat(preloadPart.initializationSegment.url).isEqualTo("map.mp4");
|
||||
assertThat(preloadPart.encryptionIV).isEqualTo("0x410C8AC18AA42EFA18B5155484F5FC34");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseMediaPlaylist_withMultiplePreloadHintTypeParts_picksOnlyFirstPreloadPart()
|
||||
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-MEDIA-SEQUENCE:266\n"
|
||||
+ "#EXT-X-PART:DURATION=2.00000,URI=\"part267.1.ts\"\n"
|
||||
+ "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"filePart267.2.ts\"\n"
|
||||
+ "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"filePart267.3.ts\"\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
assertThat(playlist.trailingParts).hasSize(2);
|
||||
assertThat(playlist.trailingParts.get(1).url).isEqualTo("filePart267.2.ts");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseMediaPlaylist_withPreloadHintTypePartAndAesPlayReadyKey_inheritsDrmInitData()
|
||||
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-MEDIA-SEQUENCE:266\n"
|
||||
+ "#EXT-X-KEY:METHOD=SAMPLE-AES,"
|
||||
+ "KEYFORMAT=\"com.microsoft.playready\","
|
||||
+ "URI=\"data:text/plain;base64,RG9uJ3QgeW91IGdldCB0aXJlZCBvZiBkb2luZyB0aGlzPw==\"\n"
|
||||
+ "#EXTINF:4.00000,\n"
|
||||
+ "fileSequence266.mp4\n"
|
||||
+ "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"filePart267.2.ts\"\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
assertThat(playlist.segments.get(0).parts).isEmpty();
|
||||
assertThat(playlist.trailingParts).hasSize(1);
|
||||
assertThat(playlist.protectionSchemes.schemeDataCount).isEqualTo(1);
|
||||
HlsMediaPlaylist.Part preloadPart = playlist.trailingParts.get(0);
|
||||
assertThat(preloadPart.drmInitData.schemeType).isEqualTo("cbcs");
|
||||
assertThat(preloadPart.drmInitData.schemeDataCount).isEqualTo(1);
|
||||
assertThat(preloadPart.drmInitData.get(0).uuid).isEqualTo(C.PLAYREADY_UUID);
|
||||
assertThat(preloadPart.drmInitData.get(0).data)
|
||||
.isEqualTo(
|
||||
PsshAtomUtil.buildPsshAtom(
|
||||
C.PLAYREADY_UUID,
|
||||
Base64.decode("RG9uJ3QgeW91IGdldCB0aXJlZCBvZiBkb2luZyB0aGlzPw==", Base64.DEFAULT)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseMediaPlaylist_withPreloadHintTypePartAndNewAesPlayReadyKey_correctDrmInitData()
|
||||
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-MEDIA-SEQUENCE:266\n"
|
||||
+ "#EXTINF:4.00000,\n"
|
||||
+ "fileSequence266.mp4\n"
|
||||
+ "#EXT-X-KEY:METHOD=SAMPLE-AES,"
|
||||
+ "KEYFORMAT=\"com.microsoft.playready\","
|
||||
+ "URI=\"data:text/plain;base64,RG9uJ3QgeW91IGdldCB0aXJlZCBvZiBkb2luZyB0aGlzPw==\"\n"
|
||||
+ "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"filePart267.2.ts\"\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
assertThat(playlist.segments.get(0).parts).isEmpty();
|
||||
assertThat(playlist.trailingParts).hasSize(1);
|
||||
assertThat(playlist.protectionSchemes.schemeDataCount).isEqualTo(1);
|
||||
HlsMediaPlaylist.Part preloadPart = playlist.trailingParts.get(0);
|
||||
assertThat(preloadPart.drmInitData.schemeType).isEqualTo("cbcs");
|
||||
assertThat(preloadPart.drmInitData.schemeDataCount).isEqualTo(1);
|
||||
assertThat(preloadPart.drmInitData.get(0).uuid).isEqualTo(C.PLAYREADY_UUID);
|
||||
assertThat(preloadPart.drmInitData.get(0).data)
|
||||
.isEqualTo(
|
||||
PsshAtomUtil.buildPsshAtom(
|
||||
C.PLAYREADY_UUID,
|
||||
Base64.decode("RG9uJ3QgeW91IGdldCB0aXJlZCBvZiBkb2luZyB0aGlzPw==", Base64.DEFAULT)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void parseMediaPlaylist_withPreloadHintTypePartAndAes128_partHasDrmKeyUriAndIV()
|
||||
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-MEDIA-SEQUENCE:266\n"
|
||||
+ "#EXTINF:4.00000,\n"
|
||||
+ "fileSequence266.mp4\n"
|
||||
+ "#EXT-X-KEY:METHOD=AES-128,KEYFORMAT=\"identity\""
|
||||
+ ", IV=0x410C8AC18AA42EFA18B5155484F5FC34,URI=\"fake://foo.bar/uri\"\n"
|
||||
+ "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"filePart267.2.ts\"\n";
|
||||
InputStream inputStream = new ByteArrayInputStream(Util.getUtf8Bytes(playlistString));
|
||||
|
||||
HlsMediaPlaylist playlist =
|
||||
(HlsMediaPlaylist) new HlsPlaylistParser().parse(playlistUri, inputStream);
|
||||
|
||||
assertThat(playlist.segments.get(0).parts).isEmpty();
|
||||
assertThat(playlist.trailingParts).hasSize(1);
|
||||
HlsMediaPlaylist.Part preloadPart = playlist.trailingParts.get(0);
|
||||
assertThat(preloadPart.drmInitData).isNull();
|
||||
assertThat(preloadPart.fullSegmentEncryptionKeyUri).isEqualTo("fake://foo.bar/uri");
|
||||
assertThat(preloadPart.encryptionIV).isEqualTo("0x410C8AC18AA42EFA18B5155484F5FC34");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void multipleExtXKeysForSingleSegment() throws Exception {
|
||||
Uri playlistUri = Uri.parse("https://example.com/test.m3u8");
|
||||
|
Loading…
x
Reference in New Issue
Block a user