Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
SDK
/
exoplayer
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
0d6ec21b
authored
Oct 29, 2020
by
bachinger
Committed by
Oliver Woodman
Nov 02, 2020
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Parse #EXT-X-PRELOAD-HINT tag
Issue: #5011 PiperOrigin-RevId: 339738292
parent
8f6b46f5
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
202 additions
and
0 deletions
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
View file @
0d6ec21b
...
...
@@ -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
()));
}
...
...
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java
View file @
0d6ec21b
...
...
@@ -488,6 +488,155 @@ public class HlsMediaPlaylistParserTest {
}
@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"
);
String
playlistString
=
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment