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
fe2acb59
authored
Oct 28, 2020
by
bachinger
Committed by
Oliver Woodman
Nov 02, 2020
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Parse HLS #EXT-X-PART tag
Issue: #5011 PiperOrigin-RevId: 339467702
parent
aab6aef4
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
415 additions
and
75 deletions
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java
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/HlsMediaPlaylist.java
View file @
fe2acb59
...
...
@@ -22,11 +22,11 @@ import androidx.annotation.Nullable;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.drm.DrmInitData
;
import
com.google.android.exoplayer2.offline.StreamKey
;
import
com.google.common.collect.ImmutableList
;
import
java.lang.annotation.Documented
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
/** Represents an HLS media playlist. */
...
...
@@ -82,57 +82,16 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
/** Media segment reference. */
@SuppressWarnings
(
"ComparableType"
)
public
static
final
class
Segment
implements
Comparable
<
Long
>
{
public
static
final
class
Segment
extends
SegmentBase
{
/**
* The url of the segment.
*/
public
final
String
url
;
/**
* The media initialization section for this segment, as defined by #EXT-X-MAP. May be null if
* the media playlist does not define a media section for this segment. The same instance is
* used for all segments that share an EXT-X-MAP tag.
*/
@Nullable
public
final
Segment
initializationSegment
;
/** The duration of the segment in microseconds, as defined by #EXTINF. */
public
final
long
durationUs
;
/** The human readable title of the segment. */
public
final
String
title
;
/**
* The number of #EXT-X-DISCONTINUITY tags in the playlist before the segment.
*/
public
final
int
relativeDiscontinuitySequence
;
/**
* The start time of the segment in microseconds, relative to the start of the playlist.
*/
public
final
long
relativeStartTimeUs
;
/**
* DRM initialization data for sample decryption, or null if the segment does not use CDM-DRM
* protection.
*/
@Nullable
public
final
DrmInitData
drmInitData
;
/**
* The encryption identity key uri as defined by #EXT-X-KEY, or null if the segment does not use
* full segment encryption with identity key.
*/
@Nullable
public
final
String
fullSegmentEncryptionKeyUri
;
/**
* The encryption initialization vector as defined by #EXT-X-KEY, or null if the segment is not
* encrypted.
*/
@Nullable
public
final
String
encryptionIV
;
/** The segment's byte range offset, as defined by #EXT-X-BYTERANGE. */
public
final
long
byteRangeOffset
;
/**
* The segment's byte range length, as defined by #EXT-X-BYTERANGE, or {@link C#LENGTH_UNSET} if
* no byte range is specified.
*/
public
final
long
byteRangeLength
;
/** Whether the segment is tagged with #EXT-X-GAP. */
public
final
boolean
hasGapTag
;
/** The parts belonging to this segment. */
public
final
List
<
Part
>
parts
;
/**
* Creates an instance to be used as init segment.
*
* @param uri See {@link #url}.
* @param byteRangeOffset See {@link #byteRangeOffset}.
* @param byteRangeLength See {@link #byteRangeLength}.
...
...
@@ -157,10 +116,13 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
encryptionIV
,
byteRangeOffset
,
byteRangeLength
,
/* hasGapTag= */
false
);
/* hasGapTag= */
false
,
/* parts= */
ImmutableList
.
of
());
}
/**
* Creates an instance.
*
* @param url See {@link #url}.
* @param initializationSegment See {@link #initializationSegment}.
* @param title See {@link #title}.
...
...
@@ -173,6 +135,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* @param byteRangeOffset See {@link #byteRangeOffset}.
* @param byteRangeLength See {@link #byteRangeLength}.
* @param hasGapTag See {@link #hasGapTag}.
* @param parts See {@link #parts}.
*/
public
Segment
(
String
url
,
...
...
@@ -186,10 +149,136 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
@Nullable
String
encryptionIV
,
long
byteRangeOffset
,
long
byteRangeLength
,
boolean
hasGapTag
,
List
<
Part
>
parts
)
{
super
(
url
,
initializationSegment
,
durationUs
,
relativeDiscontinuitySequence
,
relativeStartTimeUs
,
drmInitData
,
fullSegmentEncryptionKeyUri
,
encryptionIV
,
byteRangeOffset
,
byteRangeLength
,
hasGapTag
);
this
.
title
=
title
;
this
.
parts
=
ImmutableList
.
copyOf
(
parts
);
}
}
/** A media part. */
public
static
final
class
Part
extends
SegmentBase
{
/** Whether the part is independent. */
public
final
boolean
isIndependent
;
/**
* Creates an instance.
*
* @param url See {@link #url}.
* @param initializationSegment See {@link #initializationSegment}.
* @param durationUs See {@link #durationUs}.
* @param relativeDiscontinuitySequence See {@link #relativeDiscontinuitySequence}.
* @param relativeStartTimeUs See {@link #relativeStartTimeUs}.
* @param drmInitData See {@link #drmInitData}.
* @param fullSegmentEncryptionKeyUri See {@link #fullSegmentEncryptionKeyUri}.
* @param encryptionIV See {@link #encryptionIV}.
* @param byteRangeOffset See {@link #byteRangeOffset}.
* @param byteRangeLength See {@link #byteRangeLength}.
* @param hasGapTag See {@link #hasGapTag}.
* @param isIndependent See {@link #isIndependent}.
*/
public
Part
(
String
url
,
@Nullable
Segment
initializationSegment
,
long
durationUs
,
int
relativeDiscontinuitySequence
,
long
relativeStartTimeUs
,
@Nullable
DrmInitData
drmInitData
,
@Nullable
String
fullSegmentEncryptionKeyUri
,
@Nullable
String
encryptionIV
,
long
byteRangeOffset
,
long
byteRangeLength
,
boolean
hasGapTag
,
boolean
isIndependent
)
{
super
(
url
,
initializationSegment
,
durationUs
,
relativeDiscontinuitySequence
,
relativeStartTimeUs
,
drmInitData
,
fullSegmentEncryptionKeyUri
,
encryptionIV
,
byteRangeOffset
,
byteRangeLength
,
hasGapTag
);
this
.
isIndependent
=
isIndependent
;
}
}
/** The base for a {@link Segment} or a {@link Part} required for playback. */
@SuppressWarnings
(
"ComparableType"
)
public
static
class
SegmentBase
implements
Comparable
<
Long
>
{
/** The url of the segment. */
public
final
String
url
;
/**
* The media initialization section for this segment, as defined by #EXT-X-MAP. May be null if
* the media playlist does not define a media initialization section for this segment. The same
* instance is used for all segments that share an EXT-X-MAP tag.
*/
@Nullable
public
final
Segment
initializationSegment
;
/** The duration of the segment in microseconds, as defined by #EXTINF or #EXT-X-PART. */
public
final
long
durationUs
;
/** The number of #EXT-X-DISCONTINUITY tags in the playlist before the segment. */
public
final
int
relativeDiscontinuitySequence
;
/** The start time of the segment in microseconds, relative to the start of the playlist. */
public
final
long
relativeStartTimeUs
;
/**
* DRM initialization data for sample decryption, or null if the segment does not use CDM-DRM
* protection.
*/
@Nullable
public
final
DrmInitData
drmInitData
;
/**
* The encryption identity key uri as defined by #EXT-X-KEY, or null if the segment does not use
* full segment encryption with identity key.
*/
@Nullable
public
final
String
fullSegmentEncryptionKeyUri
;
/**
* The encryption initialization vector as defined by #EXT-X-KEY, or null if the segment is not
* encrypted.
*/
@Nullable
public
final
String
encryptionIV
;
/**
* The segment's byte range offset, as defined by #EXT-X-BYTERANGE, #EXT-X-PART or
* #EXT-X-PRELOAD-HINT.
*/
public
final
long
byteRangeOffset
;
/**
* The segment's byte range length, as defined by #EXT-X-BYTERANGE, #EXT-X-PART or
* #EXT-X-PRELOAD-HINT, or {@link C#LENGTH_UNSET} if no byte range is specified or the byte
* range is open-ended.
*/
public
final
long
byteRangeLength
;
/** Whether the segment is marked as a gap. */
public
final
boolean
hasGapTag
;
private
SegmentBase
(
String
url
,
@Nullable
Segment
initializationSegment
,
long
durationUs
,
int
relativeDiscontinuitySequence
,
long
relativeStartTimeUs
,
@Nullable
DrmInitData
drmInitData
,
@Nullable
String
fullSegmentEncryptionKeyUri
,
@Nullable
String
encryptionIV
,
long
byteRangeOffset
,
long
byteRangeLength
,
boolean
hasGapTag
)
{
this
.
url
=
url
;
this
.
initializationSegment
=
initializationSegment
;
this
.
title
=
title
;
this
.
durationUs
=
durationUs
;
this
.
relativeDiscontinuitySequence
=
relativeDiscontinuitySequence
;
this
.
relativeStartTimeUs
=
relativeStartTimeUs
;
...
...
@@ -206,7 +295,6 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
return
this
.
relativeStartTimeUs
>
relativeStartTimeUs
?
1
:
(
this
.
relativeStartTimeUs
<
relativeStartTimeUs
?
-
1
:
0
);
}
}
/**
...
...
@@ -280,6 +368,10 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
public
final
List
<
Segment
>
segments
;
/** The number of skipped segments. */
public
int
skippedSegmentCount
;
/**
* The list of parts at the end of the playlist for which the segment is not in the playlist yet.
*/
public
final
List
<
Part
>
trailingParts
;
/** The total duration of the playlist in microseconds. */
public
final
long
durationUs
;
/** The attributes of the #EXT-X-SERVER-CONTROL header. */
...
...
@@ -298,9 +390,11 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* @param targetDurationUs See {@link #targetDurationUs}.
* @param hasIndependentSegments See {@link #hasIndependentSegments}.
* @param hasEndTag See {@link #hasEndTag}.
* @param protectionSchemes See {@link #protectionSchemes}.
* @param hasProgramDateTime See {@link #hasProgramDateTime}.
* @param protectionSchemes See {@link #protectionSchemes}.
* @param segments See {@link #segments}.
* @param skippedSegmentCount See {@link #skippedSegmentCount}.
* @param trailingParts See {@link #trailingParts}.
* @param serverControl See {@link #serverControl}
*/
public
HlsMediaPlaylist
(
...
...
@@ -321,6 +415,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
@Nullable
DrmInitData
protectionSchemes
,
List
<
Segment
>
segments
,
int
skippedSegmentCount
,
List
<
Part
>
trailingParts
,
ServerControl
serverControl
)
{
super
(
baseUri
,
tags
,
hasIndependentSegments
);
this
.
playlistType
=
playlistType
;
...
...
@@ -334,8 +429,9 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
this
.
hasEndTag
=
hasEndTag
;
this
.
hasProgramDateTime
=
hasProgramDateTime
;
this
.
protectionSchemes
=
protectionSchemes
;
this
.
segments
=
Collections
.
unmodifiableList
(
segments
);
this
.
segments
=
ImmutableList
.
copyOf
(
segments
);
this
.
skippedSegmentCount
=
skippedSegmentCount
;
this
.
trailingParts
=
ImmutableList
.
copyOf
(
trailingParts
);
if
(!
segments
.
isEmpty
())
{
Segment
last
=
segments
.
get
(
segments
.
size
()
-
1
);
durationUs
=
last
.
relativeStartTimeUs
+
last
.
durationUs
;
...
...
@@ -420,6 +516,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
protectionSchemes
,
mergedSegments
,
/* skippedSegmentCount= */
0
,
trailingParts
,
serverControl
);
}
...
...
@@ -451,6 +548,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
protectionSchemes
,
segments
,
skippedSegmentCount
,
trailingParts
,
serverControl
);
}
...
...
@@ -480,6 +578,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
protectionSchemes
,
segments
,
skippedSegmentCount
,
trailingParts
,
serverControl
);
}
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
View file @
fe2acb59
...
...
@@ -33,6 +33,7 @@ import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry;
import
com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry.VariantInfo
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Rendition
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Part
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment
;
import
com.google.android.exoplayer2.upstream.ParsingLoadable
;
import
com.google.android.exoplayer2.util.Assertions
;
...
...
@@ -73,6 +74,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
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_PART
=
"#EXT-X-PART"
;
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"
;
...
...
@@ -127,6 +129,7 @@ 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_ATTR_DURATION
=
Pattern
.
compile
(
"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"
);
...
...
@@ -184,6 +187,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private
static
final
Pattern
REGEX_AUTOSELECT
=
compileBooleanAttrPattern
(
"AUTOSELECT"
);
private
static
final
Pattern
REGEX_DEFAULT
=
compileBooleanAttrPattern
(
"DEFAULT"
);
private
static
final
Pattern
REGEX_FORCED
=
compileBooleanAttrPattern
(
"FORCED"
);
private
static
final
Pattern
REGEX_INDEPENDENT
=
compileBooleanAttrPattern
(
"INDEPENDENT"
);
private
static
final
Pattern
REGEX_GAP
=
compileBooleanAttrPattern
(
"GAP"
);
private
static
final
Pattern
REGEX_VALUE
=
Pattern
.
compile
(
"VALUE=\"(.+?)\""
);
private
static
final
Pattern
REGEX_IMPORT
=
Pattern
.
compile
(
"IMPORT=\"(.+?)\""
);
private
static
final
Pattern
REGEX_VARIABLE_REFERENCE
=
...
...
@@ -333,7 +338,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
int
width
;
int
height
;
if
(
resolutionString
!=
null
)
{
String
[]
widthAndHeight
=
resolutionString
.
split
(
"x"
);
String
[]
widthAndHeight
=
Util
.
split
(
resolutionString
,
"x"
);
width
=
Integer
.
parseInt
(
widthAndHeight
[
0
]);
height
=
Integer
.
parseInt
(
widthAndHeight
[
1
]);
if
(
width
<=
0
||
height
<=
0
)
{
...
...
@@ -591,6 +596,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
HashMap
<
String
,
String
>
variableDefinitions
=
new
HashMap
<>();
HashMap
<
String
,
Segment
>
urlToInferredInitSegment
=
new
HashMap
<>();
List
<
Segment
>
segments
=
new
ArrayList
<>();
List
<
Part
>
parts
=
new
ArrayList
<>();
List
<
String
>
tags
=
new
ArrayList
<>();
long
segmentDurationUs
=
0
;
...
...
@@ -602,6 +608,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
long
segmentStartTimeUs
=
0
;
long
segmentByteRangeOffset
=
0
;
long
segmentByteRangeLength
=
C
.
LENGTH_UNSET
;
long
partStartTimeUs
=
0
;
long
partByteRangeOffset
=
0
;
boolean
isIFrameOnly
=
false
;
long
segmentMediaSequence
=
0
;
boolean
hasGapTag
=
false
;
...
...
@@ -614,12 +622,12 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
/* canBlockReload= */
false
);
int
skippedSegmentCount
=
0
;
DrmInitData
playlistProtectionSchemes
=
null
;
String
fullSegmentEncryptionKeyUri
=
null
;
String
fullSegmentEncryptionIV
=
null
;
@Nullable
DrmInitData
playlistProtectionSchemes
=
null
;
@Nullable
String
fullSegmentEncryptionKeyUri
=
null
;
@Nullable
String
fullSegmentEncryptionIV
=
null
;
TreeMap
<
String
,
SchemeData
>
currentSchemeDatas
=
new
TreeMap
<>();
String
encryptionScheme
=
null
;
DrmInitData
cachedDrmInitData
=
null
;
@Nullable
String
encryptionScheme
=
null
;
@Nullable
DrmInitData
cachedDrmInitData
=
null
;
String
line
;
while
(
iterator
.
hasNext
())
{
...
...
@@ -650,7 +658,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
String
uri
=
parseStringAttr
(
line
,
REGEX_URI
,
variableDefinitions
);
String
byteRange
=
parseOptionalStringAttr
(
line
,
REGEX_ATTR_BYTERANGE
,
variableDefinitions
);
if
(
byteRange
!=
null
)
{
String
[]
splitByteRange
=
byteRange
.
split
(
"@"
);
String
[]
splitByteRange
=
Util
.
split
(
byteRange
,
"@"
);
segmentByteRangeLength
=
Long
.
parseLong
(
splitByteRange
[
0
]);
if
(
splitByteRange
.
length
>
1
)
{
segmentByteRangeOffset
=
Long
.
parseLong
(
splitByteRange
[
1
]);
...
...
@@ -730,7 +738,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
}
}
else
if
(
line
.
startsWith
(
TAG_BYTERANGE
))
{
String
byteRange
=
parseStringAttr
(
line
,
REGEX_BYTERANGE
,
variableDefinitions
);
String
[]
splitByteRange
=
byteRange
.
split
(
"@"
);
String
[]
splitByteRange
=
Util
.
split
(
byteRange
,
"@"
);
segmentByteRangeLength
=
Long
.
parseLong
(
splitByteRange
[
0
]);
if
(
splitByteRange
.
length
>
1
)
{
segmentByteRangeOffset
=
Long
.
parseLong
(
splitByteRange
[
1
]);
...
...
@@ -752,16 +760,60 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
hasIndependentSegmentsTag
=
true
;
}
else
if
(
line
.
equals
(
TAG_ENDLIST
))
{
hasEndTag
=
true
;
}
else
if
(!
line
.
startsWith
(
"#"
))
{
String
segmentEncryptionIV
;
if
(
fullSegmentEncryptionKeyUri
==
null
)
{
segmentEncryptionIV
=
null
;
}
else
if
(
fullSegmentEncryptionIV
!=
null
)
{
segmentEncryptionIV
=
fullSegmentEncryptionIV
;
}
else
{
segmentEncryptionIV
=
Long
.
toHexString
(
segmentMediaSequence
);
}
else
if
(
line
.
startsWith
(
TAG_PART
))
{
@Nullable
String
segmentEncryptionIV
=
getSegmentEncryptionIV
(
segmentMediaSequence
,
fullSegmentEncryptionKeyUri
,
fullSegmentEncryptionIV
);
String
url
=
parseStringAttr
(
line
,
REGEX_URI
,
variableDefinitions
);
long
partDurationUs
=
(
long
)
(
parseDoubleAttr
(
line
,
REGEX_ATTR_DURATION
)
*
C
.
MICROS_PER_SECOND
);
boolean
isIndependent
=
parseOptionalBooleanAttribute
(
line
,
REGEX_INDEPENDENT
,
/* defaultValue= */
false
);
boolean
isGap
=
parseOptionalBooleanAttribute
(
line
,
REGEX_GAP
,
/* defaultValue= */
false
);
@Nullable
String
byteRange
=
parseOptionalStringAttr
(
line
,
REGEX_ATTR_BYTERANGE
,
variableDefinitions
);
long
partByteRangeLength
=
C
.
LENGTH_UNSET
;
if
(
byteRange
!=
null
)
{
String
[]
splitByteRange
=
Util
.
split
(
byteRange
,
"@"
);
partByteRangeLength
=
Long
.
parseLong
(
splitByteRange
[
0
]);
if
(
splitByteRange
.
length
>
1
)
{
partByteRangeOffset
=
Long
.
parseLong
(
splitByteRange
[
1
]);
}
}
if
(
partByteRangeLength
==
C
.
LENGTH_UNSET
)
{
partByteRangeOffset
=
0
;
}
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
,
partDurationUs
,
relativeDiscontinuitySequence
,
partStartTimeUs
,
cachedDrmInitData
,
fullSegmentEncryptionKeyUri
,
segmentEncryptionIV
,
partByteRangeOffset
,
partByteRangeLength
,
isGap
,
isIndependent
));
partStartTimeUs
+=
partDurationUs
;
if
(
partByteRangeLength
!=
C
.
LENGTH_UNSET
)
{
partByteRangeOffset
+=
partByteRangeLength
;
}
}
else
if
(!
line
.
startsWith
(
"#"
))
{
@Nullable
String
segmentEncryptionIV
=
getSegmentEncryptionIV
(
segmentMediaSequence
,
fullSegmentEncryptionKeyUri
,
fullSegmentEncryptionIV
);
segmentMediaSequence
++;
String
segmentUri
=
replaceVariableReferences
(
line
,
variableDefinitions
);
@Nullable
Segment
inferredInitSegment
=
urlToInferredInitSegment
.
get
(
segmentUri
);
...
...
@@ -788,11 +840,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
SchemeData
[]
schemeDatas
=
currentSchemeDatas
.
values
().
toArray
(
new
SchemeData
[
0
]);
cachedDrmInitData
=
new
DrmInitData
(
encryptionScheme
,
schemeDatas
);
if
(
playlistProtectionSchemes
==
null
)
{
SchemeData
[]
playlistSchemeDatas
=
new
SchemeData
[
schemeDatas
.
length
];
for
(
int
i
=
0
;
i
<
schemeDatas
.
length
;
i
++)
{
playlistSchemeDatas
[
i
]
=
schemeDatas
[
i
].
copyWithData
(
null
);
}
playlistProtectionSchemes
=
new
DrmInitData
(
encryptionScheme
,
playlistSchemeDatas
);
playlistProtectionSchemes
=
getPlaylistProtectionSchemes
(
encryptionScheme
,
schemeDatas
);
}
}
...
...
@@ -809,10 +857,13 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
segmentEncryptionIV
,
segmentByteRangeOffset
,
segmentByteRangeLength
,
hasGapTag
));
hasGapTag
,
parts
));
segmentStartTimeUs
+=
segmentDurationUs
;
partStartTimeUs
=
segmentStartTimeUs
;
segmentDurationUs
=
0
;
segmentTitle
=
""
;
parts
=
new
ArrayList
<>();
if
(
segmentByteRangeLength
!=
C
.
LENGTH_UNSET
)
{
segmentByteRangeOffset
+=
segmentByteRangeLength
;
}
...
...
@@ -839,9 +890,32 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
playlistProtectionSchemes
,
segments
,
skippedSegmentCount
,
parts
,
serverControl
);
}
private
static
DrmInitData
getPlaylistProtectionSchemes
(
@Nullable
String
encryptionScheme
,
SchemeData
[]
schemeDatas
)
{
SchemeData
[]
playlistSchemeDatas
=
new
SchemeData
[
schemeDatas
.
length
];
for
(
int
i
=
0
;
i
<
schemeDatas
.
length
;
i
++)
{
playlistSchemeDatas
[
i
]
=
schemeDatas
[
i
].
copyWithData
(
null
);
}
return
new
DrmInitData
(
encryptionScheme
,
playlistSchemeDatas
);
}
@Nullable
private
static
String
getSegmentEncryptionIV
(
long
segmentMediaSequence
,
@Nullable
String
fullSegmentEncryptionKeyUri
,
@Nullable
String
fullSegmentEncryptionIV
)
{
if
(
fullSegmentEncryptionKeyUri
==
null
)
{
return
null
;
}
else
if
(
fullSegmentEncryptionIV
!=
null
)
{
return
fullSegmentEncryptionIV
;
}
return
Long
.
toHexString
(
segmentMediaSequence
);
}
@C
.
SelectionFlags
private
static
int
parseSelectionFlags
(
String
line
)
{
int
flags
=
0
;
...
...
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java
View file @
fe2acb59
...
...
@@ -19,10 +19,12 @@ import static com.google.common.truth.Truth.assertThat;
import
static
org
.
junit
.
Assert
.
fail
;
import
android.net.Uri
;
import
android.util.Base64
;
import
androidx.annotation.Nullable
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ParserException
;
import
com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.ByteArrayInputStream
;
...
...
@@ -321,6 +323,171 @@ public class HlsMediaPlaylistParserTest {
}
@Test
public
void
parseMediaPlaylist_withParts_parsesPartWithAllAttributes
()
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-PART:DURATION=2.00000,GAP=YES,"
+
"INDEPENDENT=YES,URI=\"part267.1.ts\"\n"
+
"#EXT-X-PART:DURATION=2.00000,BYTERANGE=\"1000@1234\",URI=\"part267.2.ts\"\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence267.ts\n"
+
"#EXT-X-PART:DURATION=2.00000, BYTERANGE=\"1000@1234\",URI=\"part268.1.ts\"\n"
+
"#EXT-X-PART:DURATION=2.00000,URI=\"part268.2.ts\", BYTERANGE=\"1000\"\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
.
segments
.
get
(
1
).
parts
).
hasSize
(
2
);
assertThat
(
playlist
.
trailingParts
).
hasSize
(
2
);
HlsMediaPlaylist
.
Part
firstPart
=
playlist
.
segments
.
get
(
1
).
parts
.
get
(
0
);
assertThat
(
firstPart
.
byteRangeLength
).
isEqualTo
(
C
.
LENGTH_UNSET
);
assertThat
(
firstPart
.
byteRangeOffset
).
isEqualTo
(
0
);
assertThat
(
firstPart
.
durationUs
).
isEqualTo
(
2_000_000
);
assertThat
(
firstPart
.
relativeStartTimeUs
).
isEqualTo
(
playlist
.
segments
.
get
(
0
).
durationUs
);
assertThat
(
firstPart
.
isIndependent
).
isTrue
();
assertThat
(
firstPart
.
hasGapTag
).
isTrue
();
assertThat
(
firstPart
.
url
).
isEqualTo
(
"part267.1.ts"
);
HlsMediaPlaylist
.
Part
secondPart
=
playlist
.
segments
.
get
(
1
).
parts
.
get
(
1
);
assertThat
(
secondPart
.
byteRangeLength
).
isEqualTo
(
1000
);
assertThat
(
secondPart
.
byteRangeOffset
).
isEqualTo
(
1234
);
// Assert trailing parts.
HlsMediaPlaylist
.
Part
thirdPart
=
playlist
.
trailingParts
.
get
(
0
);
assertThat
(
thirdPart
.
byteRangeLength
).
isEqualTo
(
1000
);
assertThat
(
thirdPart
.
byteRangeOffset
).
isEqualTo
(
1234
);
assertThat
(
thirdPart
.
relativeStartTimeUs
).
isEqualTo
(
8_000_000
);
HlsMediaPlaylist
.
Part
lastPart
=
playlist
.
trailingParts
.
get
(
1
);
assertThat
(
lastPart
.
relativeStartTimeUs
).
isEqualTo
(
10_000_000
);
assertThat
(
lastPart
.
hasGapTag
).
isFalse
();
assertThat
(
lastPart
.
isIndependent
).
isFalse
();
assertThat
(
lastPart
.
byteRangeLength
).
isEqualTo
(
1000
);
assertThat
(
lastPart
.
byteRangeOffset
).
isEqualTo
(
2234
);
}
@Test
public
void
parseMediaPlaylist_withPartAndAesPlayReadyKey_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"
+
"#EXT-X-KEY:METHOD=SAMPLE-AES,"
+
"KEYFORMAT=\"com.microsoft.playready\","
+
"URI=\"data:text/plain;base64,RG9uJ3QgeW91IGdldCB0aXJlZCBvZiBkb2luZyB0aGlzPw==\"\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence266.ts\n"
+
"#EXT-X-PART:DURATION=2.00000,URI=\"part267.1.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
.
protectionSchemes
.
schemeType
).
isEqualTo
(
"cbcs"
);
HlsMediaPlaylist
.
Part
part
=
playlist
.
trailingParts
.
get
(
0
);
assertThat
(
part
.
drmInitData
.
schemeType
).
isEqualTo
(
"cbcs"
);
assertThat
(
part
.
drmInitData
.
schemeDataCount
).
isEqualTo
(
1
);
assertThat
(
part
.
drmInitData
.
get
(
0
).
uuid
).
isEqualTo
(
C
.
PLAYREADY_UUID
);
assertThat
(
part
.
drmInitData
.
get
(
0
).
data
)
.
isEqualTo
(
PsshAtomUtil
.
buildPsshAtom
(
C
.
PLAYREADY_UUID
,
Base64
.
decode
(
"RG9uJ3QgeW91IGdldCB0aXJlZCBvZiBkb2luZyB0aGlzPw=="
,
Base64
.
DEFAULT
)));
}
@Test
public
void
parseMediaPlaylist_withPartAndAesPlayReadyWithOutPrecedingSegment_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"
+
"#EXT-X-KEY:METHOD=SAMPLE-AES,"
+
"KEYFORMAT=\"com.microsoft.playready\","
+
"URI=\"data:text/plain;base64,RG9uJ3QgeW91IGdldCB0aXJlZCBvZiBkb2luZyB0aGlzPw==\"\n"
+
"#EXT-X-PART:DURATION=2.00000,URI=\"part267.1.ts\"\n"
;
InputStream
inputStream
=
new
ByteArrayInputStream
(
Util
.
getUtf8Bytes
(
playlistString
));
HlsMediaPlaylist
playlist
=
(
HlsMediaPlaylist
)
new
HlsPlaylistParser
().
parse
(
playlistUri
,
inputStream
);
assertThat
(
playlist
.
segments
).
isEmpty
();
assertThat
(
playlist
.
protectionSchemes
.
schemeType
).
isEqualTo
(
"cbcs"
);
HlsMediaPlaylist
.
Part
part
=
playlist
.
trailingParts
.
get
(
0
);
assertThat
(
part
.
drmInitData
.
schemeType
).
isEqualTo
(
"cbcs"
);
assertThat
(
part
.
drmInitData
.
schemeDataCount
).
isEqualTo
(
1
);
assertThat
(
part
.
drmInitData
.
get
(
0
).
uuid
).
isEqualTo
(
C
.
PLAYREADY_UUID
);
assertThat
(
part
.
drmInitData
.
get
(
0
).
data
)
.
isEqualTo
(
PsshAtomUtil
.
buildPsshAtom
(
C
.
PLAYREADY_UUID
,
Base64
.
decode
(
"RG9uJ3QgeW91IGdldCB0aXJlZCBvZiBkb2luZyB0aGlzPw=="
,
Base64
.
DEFAULT
)));
}
@Test
public
void
parseMediaPlaylist_withPartAndAes128_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"
+
"#EXT-X-KEY:METHOD=AES-128,KEYFORMAT=\"identity\""
+
", IV=0x410C8AC18AA42EFA18B5155484F5FC34,URI=\"fake://foo.bar/uri\"\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence266.ts\n"
+
"#EXT-X-PART:DURATION=2.00000,URI=\"part267.1.ts\"\n"
;
InputStream
inputStream
=
new
ByteArrayInputStream
(
Util
.
getUtf8Bytes
(
playlistString
));
HlsMediaPlaylist
playlist
=
(
HlsMediaPlaylist
)
new
HlsPlaylistParser
().
parse
(
playlistUri
,
inputStream
);
assertThat
(
playlist
.
segments
.
get
(
0
).
parts
).
isEmpty
();
HlsMediaPlaylist
.
Part
part
=
playlist
.
trailingParts
.
get
(
0
);
assertThat
(
playlist
.
protectionSchemes
).
isNull
();
assertThat
(
part
.
drmInitData
).
isNull
();
assertThat
(
part
.
fullSegmentEncryptionKeyUri
).
isEqualTo
(
"fake://foo.bar/uri"
);
assertThat
(
part
.
encryptionIV
).
isEqualTo
(
"0x410C8AC18AA42EFA18B5155484F5FC34"
);
}
@Test
public
void
parseMediaPlaylist_withPartAndAes128WithoutPrecedingSegment_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"
+
"#EXT-X-KEY:METHOD=AES-128,KEYFORMAT=\"identity\""
+
", IV=0x410C8AC18AA42EFA18B5155484F5FC34,URI=\"fake://foo.bar/uri\"\n"
+
"#EXT-X-PART:DURATION=2.00000,URI=\"part267.1.ts\"\n"
;
InputStream
inputStream
=
new
ByteArrayInputStream
(
Util
.
getUtf8Bytes
(
playlistString
));
HlsMediaPlaylist
playlist
=
(
HlsMediaPlaylist
)
new
HlsPlaylistParser
().
parse
(
playlistUri
,
inputStream
);
assertThat
(
playlist
.
segments
).
isEmpty
();
HlsMediaPlaylist
.
Part
part
=
playlist
.
trailingParts
.
get
(
0
);
assertThat
(
playlist
.
protectionSchemes
).
isNull
();
assertThat
(
part
.
drmInitData
).
isNull
();
assertThat
(
part
.
fullSegmentEncryptionKeyUri
).
isEqualTo
(
"fake://foo.bar/uri"
);
assertThat
(
part
.
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