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
e3c725aa
authored
Nov 12, 2020
by
bachinger
Committed by
Christos Tsilopoulos
Nov 13, 2020
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Create chunks from parts in HlsChunkSource
Issue: #5011 PiperOrigin-RevId: 342022947
parent
2693a107
Hide whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
535 additions
and
92 deletions
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.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/HlsMediaPlaylistSegmentIteratorTest.java
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java
testdata/src/test/assets/media/m3u8/live_low_latency_segments_and_parts
testdata/src/test/assets/media/m3u8/live_low_latency_segments_only
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
View file @
e3c725aa
...
...
@@ -19,7 +19,9 @@ import static java.lang.Math.max;
import
android.net.Uri
;
import
android.os.SystemClock
;
import
android.util.Pair
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.VisibleForTesting
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.Format
;
import
com.google.android.exoplayer2.source.BehindLiveWindowException
;
...
...
@@ -41,10 +43,13 @@ import com.google.android.exoplayer2.util.Assertions;
import
com.google.android.exoplayer2.util.TimestampAdjuster
;
import
com.google.android.exoplayer2.util.UriUtil
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.common.collect.ImmutableList
;
import
com.google.common.collect.Iterables
;
import
com.google.common.primitives.Ints
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.List
;
import
org.checkerframework.checker.nullness.qual.MonotonicNonNull
;
...
...
@@ -242,7 +247,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
List
<
HlsMediaChunk
>
queue
,
boolean
allowEndOfStream
,
HlsChunkHolder
out
)
{
HlsMediaChunk
previous
=
queue
.
isEmpty
()
?
null
:
queue
.
get
(
queue
.
size
()
-
1
);
@Nullable
HlsMediaChunk
previous
=
queue
.
isEmpty
()
?
null
:
Iterables
.
getLast
(
queue
);
int
oldTrackIndex
=
previous
==
null
?
C
.
INDEX_UNSET
:
trackGroup
.
indexOf
(
previous
.
trackFormat
);
long
bufferedDurationUs
=
loadPositionUs
-
playbackPositionUs
;
long
timeToLiveEdgeUs
=
resolveTimeToLiveEdgeUs
(
playbackPositionUs
);
...
...
@@ -275,6 +280,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// Retry when playlist is refreshed.
return
;
}
@Nullable
HlsMediaPlaylist
mediaPlaylist
=
playlistTracker
.
getPlaylistSnapshot
(
selectedPlaylistUrl
,
/* isForPlayback= */
true
);
// playlistTracker snapshot is valid (checked by if() above), so mediaPlaylist must be non-null.
...
...
@@ -286,22 +292,33 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// Select the chunk.
long
startOfPlaylistInPeriodUs
=
mediaPlaylist
.
startTimeUs
-
playlistTracker
.
getInitialStartTimeUs
();
long
chunkMediaSequence
=
get
ChunkMediaSequence
(
Pair
<
Long
,
Integer
>
nextMediaSequenceAndPartIndex
=
get
NextMediaSequenceAndPartIndex
(
previous
,
switchingTrack
,
mediaPlaylist
,
startOfPlaylistInPeriodUs
,
loadPositionUs
);
long
chunkMediaSequence
=
nextMediaSequenceAndPartIndex
.
first
;
int
partIndex
=
nextMediaSequenceAndPartIndex
.
second
;
if
(
chunkMediaSequence
<
mediaPlaylist
.
mediaSequence
&&
previous
!=
null
&&
switchingTrack
)
{
// We try getting the next chunk without adapting in case that's the reason for falling
// behind the live window.
selectedTrackIndex
=
oldTrackIndex
;
selectedPlaylistUrl
=
playlistUrls
[
selectedTrackIndex
];
// We try getting the next chunk without adapting in case that's the reason for falling
// behind the live window.
selectedTrackIndex
=
oldTrackIndex
;
selectedPlaylistUrl
=
playlistUrls
[
selectedTrackIndex
];
mediaPlaylist
=
playlistTracker
.
getPlaylistSnapshot
(
selectedPlaylistUrl
,
/* isForPlayback= */
true
);
// playlistTracker snapshot is valid (checked by if() above), so mediaPlaylist must be
// non-null.
Assertions
.
checkNotNull
(
mediaPlaylist
);
startOfPlaylistInPeriodUs
=
mediaPlaylist
.
startTimeUs
-
playlistTracker
.
getInitialStartTimeUs
();
chunkMediaSequence
=
previous
.
getNextChunkIndex
();
startOfPlaylistInPeriodUs
=
mediaPlaylist
.
startTimeUs
-
playlistTracker
.
getInitialStartTimeUs
();
// Get the next segment/part without switching tracks.
Pair
<
Long
,
Integer
>
nextMediaSequenceAndPartIndexWithoutAdapting
=
getNextMediaSequenceAndPartIndex
(
previous
,
/* switchingTrack= */
false
,
mediaPlaylist
,
startOfPlaylistInPeriodUs
,
loadPositionUs
);
chunkMediaSequence
=
nextMediaSequenceAndPartIndexWithoutAdapting
.
first
;
partIndex
=
nextMediaSequenceAndPartIndexWithoutAdapting
.
second
;
}
if
(
chunkMediaSequence
<
mediaPlaylist
.
mediaSequence
)
{
...
...
@@ -309,36 +326,42 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return
;
}
int
segmentIndexInPlaylist
=
(
int
)
(
chunkMediaSequence
-
mediaPlaylist
.
mediaSequence
);
int
availableSegmentCount
=
mediaPlaylist
.
segments
.
size
();
if
(
segmentIndexInPlaylist
>=
availableSegmentCount
)
{
if
(
mediaPlaylist
.
hasEndTag
)
{
if
(
allowEndOfStream
||
availableSegmentCount
==
0
)
{
out
.
endOfStream
=
true
;
return
;
}
segmentIndexInPlaylist
=
availableSegmentCount
-
1
;
}
else
/* Live */
{
@Nullable
SegmentBaseHolder
segmentBaseHolder
=
getNextSegmentHolder
(
mediaPlaylist
,
chunkMediaSequence
,
partIndex
);
if
(
segmentBaseHolder
==
null
)
{
if
(!
mediaPlaylist
.
hasEndTag
)
{
// Reload the playlist in case of a live stream.
out
.
playlistUrl
=
selectedPlaylistUrl
;
seenExpectedPlaylistError
&=
selectedPlaylistUrl
.
equals
(
expectedPlaylistUrl
);
expectedPlaylistUrl
=
selectedPlaylistUrl
;
return
;
}
else
if
(
allowEndOfStream
||
mediaPlaylist
.
segments
.
isEmpty
())
{
out
.
endOfStream
=
true
;
return
;
}
// Use the last segment available in case of a VOD stream.
segmentBaseHolder
=
new
SegmentBaseHolder
(
Iterables
.
getLast
(
mediaPlaylist
.
segments
),
mediaPlaylist
.
mediaSequence
+
mediaPlaylist
.
segments
.
size
()
-
1
,
/* partIndex= */
C
.
INDEX_UNSET
);
}
// We have a valid playlist snapshot, we can discard any playlist errors at this point.
// We have a valid media segment, we can discard any playlist errors at this point.
seenExpectedPlaylistError
=
false
;
expectedPlaylistUrl
=
null
;
// Handle encryption.
HlsMediaPlaylist
.
Segment
segment
=
mediaPlaylist
.
segments
.
get
(
segmentIndexInPlaylist
);
// Check if the segment or its initialization segment are fully encrypted.
Uri
initSegmentKeyUri
=
getFullEncryptionKeyUri
(
mediaPlaylist
,
segment
.
initializationSegment
);
// Check if the media segment or its initialization segment are fully encrypted.
@Nullable
Uri
initSegmentKeyUri
=
getFullEncryptionKeyUri
(
mediaPlaylist
,
segmentBaseHolder
.
segmentBase
.
initializationSegment
);
out
.
chunk
=
maybeCreateEncryptionChunkFor
(
initSegmentKeyUri
,
selectedTrackIndex
);
if
(
out
.
chunk
!=
null
)
{
return
;
}
Uri
mediaSegmentKeyUri
=
getFullEncryptionKeyUri
(
mediaPlaylist
,
segment
);
@Nullable
Uri
mediaSegmentKeyUri
=
getFullEncryptionKeyUri
(
mediaPlaylist
,
segmentBaseHolder
.
segmentBase
);
out
.
chunk
=
maybeCreateEncryptionChunkFor
(
mediaSegmentKeyUri
,
selectedTrackIndex
);
if
(
out
.
chunk
!=
null
)
{
return
;
...
...
@@ -351,7 +374,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
playlistFormats
[
selectedTrackIndex
],
startOfPlaylistInPeriodUs
,
mediaPlaylist
,
segment
IndexInPlaylist
,
segment
BaseHolder
,
selectedPlaylistUrl
,
muxedCaptionFormats
,
trackSelection
.
getSelectionReason
(),
...
...
@@ -363,6 +386,40 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/* initSegmentKey= */
keyCache
.
get
(
initSegmentKeyUri
));
}
@Nullable
private
static
SegmentBaseHolder
getNextSegmentHolder
(
HlsMediaPlaylist
mediaPlaylist
,
long
nextMediaSequence
,
int
nextPartIndex
)
{
int
segmentIndexInPlaylist
=
(
int
)
(
nextMediaSequence
-
mediaPlaylist
.
mediaSequence
);
if
(
segmentIndexInPlaylist
==
mediaPlaylist
.
segments
.
size
())
{
int
index
=
nextPartIndex
!=
C
.
INDEX_UNSET
?
nextPartIndex
:
0
;
return
index
<
mediaPlaylist
.
trailingParts
.
size
()
?
new
SegmentBaseHolder
(
mediaPlaylist
.
trailingParts
.
get
(
index
),
nextMediaSequence
,
index
)
:
null
;
}
Segment
mediaSegment
=
mediaPlaylist
.
segments
.
get
(
segmentIndexInPlaylist
);
if
(
nextPartIndex
==
C
.
INDEX_UNSET
)
{
return
new
SegmentBaseHolder
(
mediaSegment
,
nextMediaSequence
,
nextPartIndex
);
}
if
(
nextPartIndex
<
mediaSegment
.
parts
.
size
())
{
// The requested part is available in the requested segment.
return
new
SegmentBaseHolder
(
mediaSegment
.
parts
.
get
(
nextPartIndex
),
nextMediaSequence
,
nextPartIndex
);
}
else
if
(
segmentIndexInPlaylist
+
1
<
mediaPlaylist
.
segments
.
size
())
{
// The first part of the next segment is requested, but we can use the next full segment.
return
new
SegmentBaseHolder
(
mediaPlaylist
.
segments
.
get
(
segmentIndexInPlaylist
+
1
),
nextMediaSequence
+
1
,
/* partIndex= */
C
.
INDEX_UNSET
);
}
else
if
(!
mediaPlaylist
.
trailingParts
.
isEmpty
())
{
// The part index is rolling over to the first trailing part.
return
new
SegmentBaseHolder
(
mediaPlaylist
.
trailingParts
.
get
(
0
),
nextMediaSequence
+
1
,
/* partIndex= */
0
);
}
return
null
;
}
/**
* Called when the {@link HlsSampleStreamWrapper} has finished loading a chunk obtained from this
* source.
...
...
@@ -438,6 +495,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
chunkIterators
[
i
]
=
MediaChunkIterator
.
EMPTY
;
continue
;
}
@Nullable
HlsMediaPlaylist
playlist
=
playlistTracker
.
getPlaylistSnapshot
(
playlistUrl
,
/* isForPlayback= */
false
);
// Playlist snapshot is valid (checked by if() above) so playlist must be non-null.
...
...
@@ -445,16 +503,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
long
startOfPlaylistInPeriodUs
=
playlist
.
startTimeUs
-
playlistTracker
.
getInitialStartTimeUs
();
boolean
switchingTrack
=
trackIndex
!=
oldTrackIndex
;
long
chunkMediaSequence
=
get
ChunkMediaSequence
(
Pair
<
Long
,
Integer
>
chunkMediaSequenceAndPartIndex
=
get
NextMediaSequenceAndPartIndex
(
previous
,
switchingTrack
,
playlist
,
startOfPlaylistInPeriodUs
,
loadPositionUs
);
if
(
chunkMediaSequence
<
playlist
.
mediaSequence
)
{
chunkIterators
[
i
]
=
MediaChunkIterator
.
EMPTY
;
continue
;
}
int
chunkIndex
=
(
int
)
(
chunkMediaSequence
-
playlist
.
mediaSequence
);
long
chunkMediaSequence
=
chunkMediaSequenceAndPartIndex
.
first
;
int
partIndex
=
chunkMediaSequenceAndPartIndex
.
second
;
chunkIterators
[
i
]
=
new
HlsMediaPlaylistSegmentIterator
(
playlist
,
startOfPlaylistInPeriodUs
,
chunkIndex
);
new
HlsMediaPlaylistSegmentIterator
(
playlist
.
baseUri
,
startOfPlaylistInPeriodUs
,
getSegmentBaseList
(
playlist
,
chunkMediaSequence
,
partIndex
));
}
return
chunkIterators
;
}
...
...
@@ -495,10 +553,56 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return
trackSelection
.
shouldCancelChunkLoad
(
playbackPositionUs
,
loadingChunk
,
queue
);
}
// Package methods.
/**
* Returns a list with all segment bases in the playlist starting from {@code mediaSequence} and
* {@code partIndex} in the given playlist. The list may be empty if the starting point is not in
* the playlist.
*/
@VisibleForTesting
/* package */
static
List
<
HlsMediaPlaylist
.
SegmentBase
>
getSegmentBaseList
(
HlsMediaPlaylist
playlist
,
long
mediaSequence
,
int
partIndex
)
{
int
firstSegmentIndexInPlaylist
=
(
int
)
(
mediaSequence
-
playlist
.
mediaSequence
);
if
(
firstSegmentIndexInPlaylist
<
0
||
playlist
.
segments
.
size
()
<
firstSegmentIndexInPlaylist
)
{
// The first media sequence is not in the playlist.
return
ImmutableList
.
of
();
}
List
<
HlsMediaPlaylist
.
SegmentBase
>
segmentBases
=
new
ArrayList
<>();
if
(
firstSegmentIndexInPlaylist
<
playlist
.
segments
.
size
())
{
if
(
partIndex
!=
C
.
INDEX_UNSET
)
{
// The iterator starts with a part that belongs to a segment.
Segment
firstSegment
=
playlist
.
segments
.
get
(
firstSegmentIndexInPlaylist
);
if
(
partIndex
==
0
)
{
// Use the full segment instead of the first part.
segmentBases
.
add
(
firstSegment
);
}
else
if
(
partIndex
<
firstSegment
.
parts
.
size
())
{
// Add the parts from the first requested segment.
segmentBases
.
addAll
(
firstSegment
.
parts
.
subList
(
partIndex
,
firstSegment
.
parts
.
size
()));
}
firstSegmentIndexInPlaylist
++;
}
partIndex
=
0
;
// Add all remaining segments.
segmentBases
.
addAll
(
playlist
.
segments
.
subList
(
firstSegmentIndexInPlaylist
,
playlist
.
segments
.
size
()));
}
if
(
playlist
.
partTargetDurationUs
!=
C
.
TIME_UNSET
)
{
// That's a low latency playlist.
partIndex
=
partIndex
==
C
.
INDEX_UNSET
?
0
:
partIndex
;
if
(
partIndex
<
playlist
.
trailingParts
.
size
())
{
segmentBases
.
addAll
(
playlist
.
trailingParts
.
subList
(
partIndex
,
playlist
.
trailingParts
.
size
()));
}
}
return
Collections
.
unmodifiableList
(
segmentBases
);
}
// Private methods.
/**
* Returns the media sequence number
of the segment to load next in
{@code mediaPlaylist}.
* Returns the media sequence number
and part index to load next in the
{@code mediaPlaylist}.
*
* @param previous The last (at least partially) loaded segment.
* @param switchingTrack Whether the segment to load is not preceded by a segment in the same
...
...
@@ -507,9 +611,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* @param startOfPlaylistInPeriodUs The start of {@code mediaPlaylist} relative to the period
* start in microseconds.
* @param loadPositionUs The current load position relative to the period start in microseconds.
* @return The media sequence
of the segment
to load.
* @return The media sequence
and part index
to load.
*/
private
long
getChunkMediaSequence
(
private
Pair
<
Long
,
Integer
>
getNextMediaSequenceAndPartIndex
(
@Nullable
HlsMediaChunk
previous
,
boolean
switchingTrack
,
HlsMediaPlaylist
mediaPlaylist
,
...
...
@@ -521,17 +625,28 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
(
previous
==
null
||
independentSegments
)
?
loadPositionUs
:
previous
.
startTimeUs
;
if
(!
mediaPlaylist
.
hasEndTag
&&
targetPositionInPeriodUs
>=
endOfPlaylistInPeriodUs
)
{
// If the playlist is too old to contain the chunk, we need to refresh it.
return
mediaPlaylist
.
mediaSequence
+
mediaPlaylist
.
segments
.
size
();
return
new
Pair
<>(
mediaPlaylist
.
mediaSequence
+
mediaPlaylist
.
segments
.
size
(),
/* partIndex */
C
.
INDEX_UNSET
);
}
long
targetPositionInPlaylistUs
=
targetPositionInPeriodUs
-
startOfPlaylistInPeriodUs
;
return
Util
.
binarySearchFloor
(
mediaPlaylist
.
segments
,
/* value= */
targetPositionInPlaylistUs
,
/* inclusive= */
true
,
/* stayInBounds= */
!
playlistTracker
.
isLive
()
||
previous
==
null
)
+
mediaPlaylist
.
mediaSequence
;
}
return
previous
.
isLoadCompleted
()
?
previous
.
getNextChunkIndex
()
:
previous
.
chunkIndex
;
long
mediaSequence
=
Util
.
binarySearchFloor
(
mediaPlaylist
.
segments
,
/* value= */
targetPositionInPlaylistUs
,
/* inclusive= */
true
,
/* stayInBounds= */
!
playlistTracker
.
isLive
()
||
previous
==
null
)
+
mediaPlaylist
.
mediaSequence
;
return
new
Pair
<>(
mediaSequence
,
/* partIndex */
C
.
INDEX_UNSET
);
}
// If loading has not completed, we return the previous chunk again.
return
(
previous
.
isLoadCompleted
()
?
new
Pair
<>(
previous
.
partIndex
==
C
.
INDEX_UNSET
?
previous
.
getNextChunkIndex
()
:
previous
.
chunkIndex
,
previous
.
partIndex
==
C
.
INDEX_UNSET
?
C
.
INDEX_UNSET
:
previous
.
partIndex
+
1
)
:
new
Pair
<>(
previous
.
chunkIndex
,
previous
.
partIndex
));
}
private
long
resolveTimeToLiveEdgeUs
(
long
playbackPositionUs
)
{
...
...
@@ -574,11 +689,29 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
@Nullable
private
static
Uri
getFullEncryptionKeyUri
(
HlsMediaPlaylist
playlist
,
@Nullable
Segment
segment
)
{
if
(
segment
==
null
||
segment
.
fullSegmentEncryptionKeyUri
==
null
)
{
private
static
Uri
getFullEncryptionKeyUri
(
HlsMediaPlaylist
playlist
,
@Nullable
HlsMediaPlaylist
.
SegmentBase
segmentBase
)
{
if
(
segmentBase
==
null
||
segmentBase
.
fullSegmentEncryptionKeyUri
==
null
)
{
return
null
;
}
return
UriUtil
.
resolveToUri
(
playlist
.
baseUri
,
segment
.
fullSegmentEncryptionKeyUri
);
return
UriUtil
.
resolveToUri
(
playlist
.
baseUri
,
segmentBase
.
fullSegmentEncryptionKeyUri
);
}
// Package classes.
/* package */
static
final
class
SegmentBaseHolder
{
public
final
HlsMediaPlaylist
.
SegmentBase
segmentBase
;
public
final
long
mediaSequence
;
public
final
int
partIndex
;
/** Creates a new instance. */
public
SegmentBaseHolder
(
HlsMediaPlaylist
.
SegmentBase
segmentBase
,
long
mediaSequence
,
int
partIndex
)
{
this
.
segmentBase
=
segmentBase
;
this
.
mediaSequence
=
mediaSequence
;
this
.
partIndex
=
partIndex
;
}
}
// Private classes.
...
...
@@ -665,48 +798,52 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
/** {@link MediaChunkIterator} wrapping a {@link HlsMediaPlaylist}. */
private
static
final
class
HlsMediaPlaylistSegmentIterator
extends
BaseMediaChunkIterator
{
@VisibleForTesting
/* package */
static
final
class
HlsMediaPlaylistSegmentIterator
extends
BaseMediaChunkIterator
{
private
final
HlsMediaPlaylist
playlist
;
private
final
List
<
HlsMediaPlaylist
.
SegmentBase
>
segmentBases
;
private
final
long
startOfPlaylistInPeriodUs
;
private
final
String
playlistBaseUri
;
/**
* Creates
iterator
.
* Creates
an iterator instance wrapping a list of {@link HlsMediaPlaylist.SegmentBase}
.
*
* @param playlist
The {@link HlsMediaPlaylist} to wrap
.
* @param playlist
BaseUri The base URI of the {@link HlsMediaPlaylist}
.
* @param startOfPlaylistInPeriodUs The start time of the playlist in the period, in
* microseconds.
* @param
chunkIndex The index of the first available chunk in the playlist
.
* @param
segmentBases The list of {@link HlsMediaPlaylist.SegmentBase segment bases} to wrap
.
*/
public
HlsMediaPlaylistSegmentIterator
(
HlsMediaPlaylist
playlist
,
long
startOfPlaylistInPeriodUs
,
int
chunkIndex
)
{
super
(
/* fromIndex= */
chunkIndex
,
/* toIndex= */
playlist
.
segments
.
size
()
-
1
);
this
.
playlist
=
playlist
;
String
playlistBaseUri
,
long
startOfPlaylistInPeriodUs
,
List
<
HlsMediaPlaylist
.
SegmentBase
>
segmentBases
)
{
super
(
/* fromIndex= */
0
,
segmentBases
.
size
()
-
1
);
this
.
playlistBaseUri
=
playlistBaseUri
;
this
.
startOfPlaylistInPeriodUs
=
startOfPlaylistInPeriodUs
;
this
.
segmentBases
=
segmentBases
;
}
@Override
public
DataSpec
getDataSpec
()
{
checkInBounds
();
Segment
segment
=
playlist
.
segment
s
.
get
((
int
)
getCurrentIndex
());
Uri
chunkUri
=
UriUtil
.
resolveToUri
(
playlist
.
baseUri
,
segment
.
url
);
return
new
DataSpec
(
chunkUri
,
segment
.
byteRangeOffset
,
segment
.
byteRangeLength
);
HlsMediaPlaylist
.
SegmentBase
segmentBase
=
segmentBase
s
.
get
((
int
)
getCurrentIndex
());
Uri
chunkUri
=
UriUtil
.
resolveToUri
(
playlist
BaseUri
,
segmentBase
.
url
);
return
new
DataSpec
(
chunkUri
,
segment
Base
.
byteRangeOffset
,
segmentBase
.
byteRangeLength
);
}
@Override
public
long
getChunkStartTimeUs
()
{
checkInBounds
();
Segment
segment
=
playlist
.
segments
.
get
((
int
)
getCurrentIndex
());
return
startOfPlaylistInPeriodUs
+
segment
.
relativeStartTimeUs
;
return
startOfPlaylistInPeriodUs
+
segmentBases
.
get
((
int
)
getCurrentIndex
())
.
relativeStartTimeUs
;
}
@Override
public
long
getChunkEndTimeUs
()
{
checkInBounds
();
Segment
segment
=
playlist
.
segment
s
.
get
((
int
)
getCurrentIndex
());
long
segmentStartTimeInPeriodUs
=
startOfPlaylistInPeriodUs
+
segment
.
relativeStartTimeUs
;
return
segmentStartTimeInPeriodUs
+
segment
.
durationUs
;
HlsMediaPlaylist
.
SegmentBase
segmentBase
=
segmentBase
s
.
get
((
int
)
getCurrentIndex
());
long
segmentStartTimeInPeriodUs
=
startOfPlaylistInPeriodUs
+
segment
Base
.
relativeStartTimeUs
;
return
segmentStartTimeInPeriodUs
+
segment
Base
.
durationUs
;
}
}
}
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java
View file @
e3c725aa
...
...
@@ -59,7 +59,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
* @param format The chunk format.
* @param startOfPlaylistInPeriodUs The position of the playlist in the period in microseconds.
* @param mediaPlaylist The media playlist from which this chunk was obtained.
* @param segment
IndexInPlaylist The index of the segment in the media playlist
.
* @param segment
BaseHolder The segment holder
.
* @param playlistUrl The url of the playlist from which this chunk was obtained.
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
* information is available in the master playlist.
...
...
@@ -79,7 +79,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
Format
format
,
long
startOfPlaylistInPeriodUs
,
HlsMediaPlaylist
mediaPlaylist
,
int
segmentIndexInPlaylist
,
HlsChunkSource
.
SegmentBaseHolder
segmentBaseHolder
,
Uri
playlistUrl
,
@Nullable
List
<
Format
>
muxedCaptionFormats
,
int
trackSelectionReason
,
...
...
@@ -90,7 +90,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Nullable
byte
[]
mediaSegmentKey
,
@Nullable
byte
[]
initSegmentKey
)
{
// Media segment.
HlsMediaPlaylist
.
Segment
mediaSegment
=
mediaPlaylist
.
segments
.
get
(
segmentIndexInPlaylist
)
;
HlsMediaPlaylist
.
Segment
Base
mediaSegment
=
segmentBaseHolder
.
segmentBase
;
DataSpec
dataSpec
=
new
DataSpec
(
UriUtil
.
resolveToUri
(
mediaPlaylist
.
baseUri
,
mediaSegment
.
url
),
...
...
@@ -136,10 +136,10 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
playlistUrl
.
equals
(
previousChunk
.
playlistUrl
)
&&
previousChunk
.
loadCompleted
;
id3Decoder
=
previousChunk
.
id3Decoder
;
scratchId3Data
=
previousChunk
.
scratchId3Data
;
boolean
isIndependent
=
isIndependent
(
segmentBaseHolder
,
mediaPlaylist
);
boolean
canContinueWithoutSplice
=
isFollowingChunk
||
(
mediaPlaylist
.
hasIndependentSegments
&&
segmentStartTimeInPeriodUs
>=
previousChunk
.
endTimeUs
);
||
(
isIndependent
&&
segmentStartTimeInPeriodUs
>=
previousChunk
.
endTimeUs
);
shouldSpliceIn
=
!
canContinueWithoutSplice
;
previousExtractor
=
isFollowingChunk
...
...
@@ -152,7 +152,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
scratchId3Data
=
new
ParsableByteArray
(
Id3Decoder
.
ID3_HEADER_LENGTH
);
shouldSpliceIn
=
false
;
}
return
new
HlsMediaChunk
(
extractorFactory
,
mediaDataSource
,
...
...
@@ -168,7 +167,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
trackSelectionData
,
segmentStartTimeInPeriodUs
,
segmentEndTimeInPeriodUs
,
/* chunkMediaSequence= */
mediaPlaylist
.
mediaSequence
+
segmentIndexInPlaylist
,
segmentBaseHolder
.
mediaSequence
,
segmentBaseHolder
.
partIndex
,
discontinuitySequenceNumber
,
mediaSegment
.
hasGapTag
,
isMasterTimestampSource
,
...
...
@@ -201,6 +201,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** Whether samples for this chunk should be spliced into existing samples. */
public
final
boolean
shouldSpliceIn
;
/** The part index or {@link C#INDEX_UNSET} if the chunk is a full segment */
public
final
int
partIndex
;
@Nullable
private
final
DataSource
initDataSource
;
@Nullable
private
final
DataSpec
initDataSpec
;
@Nullable
private
final
HlsMediaChunkExtractor
previousExtractor
;
...
...
@@ -243,6 +246,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
long
startTimeUs
,
long
endTimeUs
,
long
chunkMediaSequence
,
int
partIndex
,
int
discontinuitySequenceNumber
,
boolean
hasGapTag
,
boolean
isMasterTimestampSource
,
...
...
@@ -262,6 +266,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
endTimeUs
,
chunkMediaSequence
);
this
.
mediaSegmentEncrypted
=
mediaSegmentEncrypted
;
this
.
partIndex
=
partIndex
;
this
.
discontinuitySequenceNumber
=
discontinuitySequenceNumber
;
this
.
initDataSpec
=
initDataSpec
;
this
.
initDataSource
=
initDataSource
;
...
...
@@ -541,4 +546,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
}
return
dataSource
;
}
private
static
boolean
isIndependent
(
HlsChunkSource
.
SegmentBaseHolder
segmentBaseHolder
,
HlsMediaPlaylist
mediaPlaylist
)
{
if
(
segmentBaseHolder
.
segmentBase
instanceof
HlsMediaPlaylist
.
Part
)
{
return
((
HlsMediaPlaylist
.
Part
)
segmentBaseHolder
.
segmentBase
).
isIndependent
||
(
segmentBaseHolder
.
partIndex
==
0
&&
mediaPlaylist
.
hasIndependentSegments
);
}
return
mediaPlaylist
.
hasIndependentSegments
;
}
}
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
View file @
e3c725aa
...
...
@@ -797,9 +797,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
}
String
url
=
parseStringAttr
(
line
,
REGEX_URI
,
variableDefinitions
);
long
byteRangeStart
=
parseOptionalLongAttr
(
line
,
REGEX_BYTERANGE_START
,
/* defaultValue= */
0
);
parseOptionalLongAttr
(
line
,
REGEX_BYTERANGE_START
,
/* defaultValue= */
C
.
LENGTH_UNSET
);
long
byteRangeLength
=
parseOptionalLongAttr
(
line
,
REGEX_BYTERANGE_LENGTH
,
/* defaultValue= */
C
.
TIME
_UNSET
);
parseOptionalLongAttr
(
line
,
REGEX_BYTERANGE_LENGTH
,
/* defaultValue= */
C
.
LENGTH
_UNSET
);
@Nullable
String
segmentEncryptionIV
=
getSegmentEncryptionIV
(
...
...
@@ -811,21 +811,24 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
playlistProtectionSchemes
=
getPlaylistProtectionSchemes
(
encryptionScheme
,
schemeDatas
);
}
}
preloadPart
=
new
Part
(
url
,
initializationSegment
,
/* durationUs= */
0
,
relativeDiscontinuitySequence
,
partStartTimeUs
,
cachedDrmInitData
,
fullSegmentEncryptionKeyUri
,
segmentEncryptionIV
,
byteRangeStart
,
byteRangeLength
,
/* hasGapTag= */
false
,
/* isIndependent= */
false
,
/* isPreload= */
true
);
if
(
byteRangeStart
==
C
.
LENGTH_UNSET
||
byteRangeLength
!=
C
.
LENGTH_UNSET
)
{
// Skip preload part if it is an unbounded range request.
preloadPart
=
new
Part
(
url
,
initializationSegment
,
/* durationUs= */
0
,
relativeDiscontinuitySequence
,
partStartTimeUs
,
cachedDrmInitData
,
fullSegmentEncryptionKeyUri
,
segmentEncryptionIV
,
byteRangeStart
!=
C
.
LENGTH_UNSET
?
byteRangeStart
:
0
,
byteRangeLength
,
/* hasGapTag= */
false
,
/* isIndependent= */
false
,
/* isPreload= */
true
);
}
}
else
if
(
line
.
startsWith
(
TAG_PART
))
{
@Nullable
String
segmentEncryptionIV
=
...
...
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaPlaylistSegmentIteratorTest.java
0 → 100644
View file @
e3c725aa
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
source
.
hls
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
static
org
.
junit
.
Assert
.
fail
;
import
android.net.Uri
;
import
androidx.test.core.app.ApplicationProvider
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser
;
import
com.google.android.exoplayer2.testutil.TestUtil
;
import
com.google.android.exoplayer2.upstream.DataSpec
;
import
com.google.common.collect.Iterables
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.List
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
/** Unit test for {@link HlsChunkSource.HlsMediaPlaylistSegmentIterator}. */
@RunWith
(
AndroidJUnit4
.
class
)
public
class
HlsMediaPlaylistSegmentIteratorTest
{
public
static
final
String
LOW_LATENCY_SEGMENTS_AND_PARTS
=
"media/m3u8/live_low_latency_segments_and_parts"
;
public
static
final
String
SEGMENTS_ONLY
=
"media/m3u8/live_low_latency_segments_only"
;
@Test
public
void
create_withMediaSequenceBehindLiveWindow_isEmpty
()
{
HlsMediaPlaylist
mediaPlaylist
=
getHlsMediaPlaylist
(
LOW_LATENCY_SEGMENTS_AND_PARTS
);
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
hlsMediaPlaylistSegmentIterator
=
new
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
(
mediaPlaylist
.
baseUri
,
/* startOfPlaylistInPeriodUs= */
0
,
HlsChunkSource
.
getSegmentBaseList
(
mediaPlaylist
,
mediaPlaylist
.
mediaSequence
-
1
,
/* partIndex= */
C
.
INDEX_UNSET
));
assertThat
(
hlsMediaPlaylistSegmentIterator
.
next
()).
isFalse
();
}
@Test
public
void
create_withMediaSequenceBeforeTrailingPartSegment_isEmpty
()
{
HlsMediaPlaylist
mediaPlaylist
=
getHlsMediaPlaylist
(
LOW_LATENCY_SEGMENTS_AND_PARTS
);
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
hlsMediaPlaylistSegmentIterator
=
new
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
(
mediaPlaylist
.
baseUri
,
/* startOfPlaylistInPeriodUs= */
0
,
HlsChunkSource
.
getSegmentBaseList
(
mediaPlaylist
,
mediaPlaylist
.
mediaSequence
+
mediaPlaylist
.
segments
.
size
()
+
1
,
/* partIndex= */
C
.
INDEX_UNSET
));
assertThat
(
hlsMediaPlaylistSegmentIterator
.
next
()).
isFalse
();
}
@Test
public
void
create_withPartIndexBeforeLastTrailingPartSegment_isEmpty
()
{
HlsMediaPlaylist
mediaPlaylist
=
getHlsMediaPlaylist
(
LOW_LATENCY_SEGMENTS_AND_PARTS
);
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
hlsMediaPlaylistSegmentIterator
=
new
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
(
mediaPlaylist
.
baseUri
,
/* startOfPlaylistInPeriodUs= */
0
,
HlsChunkSource
.
getSegmentBaseList
(
mediaPlaylist
,
mediaPlaylist
.
mediaSequence
+
mediaPlaylist
.
segments
.
size
(),
/* partIndex= */
3
));
assertThat
(
hlsMediaPlaylistSegmentIterator
.
next
()).
isFalse
();
}
@Test
public
void
next_conventionalLiveStartIteratorAtSecondSegment_correctElements
()
{
HlsMediaPlaylist
mediaPlaylist
=
getHlsMediaPlaylist
(
SEGMENTS_ONLY
);
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
hlsMediaPlaylistSegmentIterator
=
new
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
(
mediaPlaylist
.
baseUri
,
/* startOfPlaylistInPeriodUs= */
0
,
HlsChunkSource
.
getSegmentBaseList
(
mediaPlaylist
,
/* mediaSequence= */
11
,
/* partIndex= */
C
.
INDEX_UNSET
));
List
<
DataSpec
>
datasSpecs
=
new
ArrayList
<>();
while
(
hlsMediaPlaylistSegmentIterator
.
next
())
{
datasSpecs
.
add
(
hlsMediaPlaylistSegmentIterator
.
getDataSpec
());
}
assertThat
(
datasSpecs
).
hasSize
(
5
);
assertThat
(
datasSpecs
.
get
(
0
).
uri
.
toString
()).
isEqualTo
(
"fileSequence11.ts"
);
assertThat
(
Iterables
.
getLast
(
datasSpecs
).
uri
.
toString
()).
isEqualTo
(
"fileSequence15.ts"
);
}
@Test
public
void
next_startIteratorAtFirstSegment_correctElements
()
{
HlsMediaPlaylist
mediaPlaylist
=
getHlsMediaPlaylist
(
LOW_LATENCY_SEGMENTS_AND_PARTS
);
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
hlsMediaPlaylistSegmentIterator
=
new
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
(
mediaPlaylist
.
baseUri
,
/* startOfPlaylistInPeriodUs= */
0
,
HlsChunkSource
.
getSegmentBaseList
(
mediaPlaylist
,
/* mediaSequence= */
10
,
/* partIndex= */
C
.
INDEX_UNSET
));
List
<
DataSpec
>
datasSpecs
=
new
ArrayList
<>();
while
(
hlsMediaPlaylistSegmentIterator
.
next
())
{
datasSpecs
.
add
(
hlsMediaPlaylistSegmentIterator
.
getDataSpec
());
}
assertThat
(
datasSpecs
).
hasSize
(
9
);
// The iterator starts with 6 segments.
assertThat
(
datasSpecs
.
get
(
0
).
uri
.
toString
()).
isEqualTo
(
"fileSequence10.ts"
);
// Followed by trailing parts.
assertThat
(
datasSpecs
.
get
(
6
).
uri
.
toString
()).
isEqualTo
(
"fileSequence16.0.ts"
);
// The preload part is the last.
assertThat
(
Iterables
.
getLast
(
datasSpecs
).
uri
.
toString
()).
isEqualTo
(
"fileSequence16.2.ts"
);
}
@Test
public
void
next_startIteratorAtFirstPartInaSegment_usesFullSegment
()
{
HlsMediaPlaylist
mediaPlaylist
=
getHlsMediaPlaylist
(
LOW_LATENCY_SEGMENTS_AND_PARTS
);
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
hlsMediaPlaylistSegmentIterator
=
new
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
(
mediaPlaylist
.
baseUri
,
/* startOfPlaylistInPeriodUs= */
0
,
HlsChunkSource
.
getSegmentBaseList
(
mediaPlaylist
,
/* mediaSequence= */
14
,
/* partIndex= */
0
));
List
<
DataSpec
>
datasSpecs
=
new
ArrayList
<>();
while
(
hlsMediaPlaylistSegmentIterator
.
next
())
{
datasSpecs
.
add
(
hlsMediaPlaylistSegmentIterator
.
getDataSpec
());
}
assertThat
(
datasSpecs
).
hasSize
(
5
);
// The iterator starts with 6 segments.
assertThat
(
datasSpecs
.
get
(
0
).
uri
.
toString
()).
isEqualTo
(
"fileSequence14.ts"
);
assertThat
(
datasSpecs
.
get
(
1
).
uri
.
toString
()).
isEqualTo
(
"fileSequence15.ts"
);
// Followed by trailing parts.
assertThat
(
datasSpecs
.
get
(
2
).
uri
.
toString
()).
isEqualTo
(
"fileSequence16.0.ts"
);
assertThat
(
datasSpecs
.
get
(
3
).
uri
.
toString
()).
isEqualTo
(
"fileSequence16.1.ts"
);
// The preload part is the last.
assertThat
(
Iterables
.
getLast
(
datasSpecs
).
uri
.
toString
()).
isEqualTo
(
"fileSequence16.2.ts"
);
}
@Test
public
void
next_startIteratorAtTrailingPart_correctElements
()
{
HlsMediaPlaylist
mediaPlaylist
=
getHlsMediaPlaylist
(
LOW_LATENCY_SEGMENTS_AND_PARTS
);
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
hlsMediaPlaylistSegmentIterator
=
new
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
(
mediaPlaylist
.
baseUri
,
/* startOfPlaylistInPeriodUs= */
0
,
HlsChunkSource
.
getSegmentBaseList
(
mediaPlaylist
,
/* mediaSequence= */
16
,
/* partIndex= */
1
));
List
<
DataSpec
>
datasSpecs
=
new
ArrayList
<>();
while
(
hlsMediaPlaylistSegmentIterator
.
next
())
{
datasSpecs
.
add
(
hlsMediaPlaylistSegmentIterator
.
getDataSpec
());
}
assertThat
(
datasSpecs
).
hasSize
(
2
);
// The iterator starts with 2 parts.
assertThat
(
datasSpecs
.
get
(
0
).
uri
.
toString
()).
isEqualTo
(
"fileSequence16.1.ts"
);
// The preload part is the last.
assertThat
(
Iterables
.
getLast
(
datasSpecs
).
uri
.
toString
()).
isEqualTo
(
"fileSequence16.2.ts"
);
}
@Test
public
void
next_startIteratorAtPartWithinSegment_correctElements
()
{
HlsMediaPlaylist
mediaPlaylist
=
getHlsMediaPlaylist
(
LOW_LATENCY_SEGMENTS_AND_PARTS
);
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
hlsMediaPlaylistSegmentIterator
=
new
HlsChunkSource
.
HlsMediaPlaylistSegmentIterator
(
mediaPlaylist
.
baseUri
,
/* startOfPlaylistInPeriodUs= */
0
,
HlsChunkSource
.
getSegmentBaseList
(
mediaPlaylist
,
/* mediaSequence= */
14
,
/* partIndex= */
1
));
List
<
DataSpec
>
datasSpecs
=
new
ArrayList
<>();
while
(
hlsMediaPlaylistSegmentIterator
.
next
())
{
datasSpecs
.
add
(
hlsMediaPlaylistSegmentIterator
.
getDataSpec
());
}
assertThat
(
datasSpecs
).
hasSize
(
7
);
// The iterator starts with 11 parts.
assertThat
(
datasSpecs
.
get
(
0
).
uri
.
toString
()).
isEqualTo
(
"fileSequence14.1.ts"
);
assertThat
(
datasSpecs
.
get
(
1
).
uri
.
toString
()).
isEqualTo
(
"fileSequence14.2.ts"
);
assertThat
(
datasSpecs
.
get
(
2
).
uri
.
toString
()).
isEqualTo
(
"fileSequence14.3.ts"
);
// Use a segment in between if possible.
assertThat
(
datasSpecs
.
get
(
3
).
uri
.
toString
()).
isEqualTo
(
"fileSequence15.ts"
);
// Then parts again.
assertThat
(
datasSpecs
.
get
(
4
).
uri
.
toString
()).
isEqualTo
(
"fileSequence16.0.ts"
);
assertThat
(
datasSpecs
.
get
(
5
).
uri
.
toString
()).
isEqualTo
(
"fileSequence16.1.ts"
);
assertThat
(
datasSpecs
.
get
(
6
).
uri
.
toString
()).
isEqualTo
(
"fileSequence16.2.ts"
);
}
private
static
HlsMediaPlaylist
getHlsMediaPlaylist
(
String
file
)
{
try
{
return
(
HlsMediaPlaylist
)
new
HlsPlaylistParser
()
.
parse
(
Uri
.
EMPTY
,
TestUtil
.
getInputStream
(
ApplicationProvider
.
getApplicationContext
(),
file
));
}
catch
(
IOException
e
)
{
fail
(
e
.
getMessage
());
}
return
null
;
}
}
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java
View file @
e3c725aa
...
...
@@ -27,6 +27,7 @@ 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
com.google.common.collect.Iterables
;
import
java.io.ByteArrayInputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
...
...
@@ -362,6 +363,7 @@ public class HlsMediaPlaylistParserTest {
assertThat
(
secondPart
.
byteRangeOffset
).
isEqualTo
(
1234
);
// Assert trailing parts.
HlsMediaPlaylist
.
Part
thirdPart
=
playlist
.
trailingParts
.
get
(
0
);
// Assert tailing parts.
assertThat
(
thirdPart
.
byteRangeLength
).
isEqualTo
(
1000
);
assertThat
(
thirdPart
.
byteRangeOffset
).
isEqualTo
(
1234
);
assertThat
(
thirdPart
.
relativeStartTimeUs
).
isEqualTo
(
8_000_000
);
...
...
@@ -545,6 +547,27 @@ public class HlsMediaPlaylistParserTest {
}
@Test
public
void
parseMediaPlaylist_withUnboundedPreloadHintTypePart_ignoresPreloadPart
()
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,BYTERANGE-START=0\"\n"
;
InputStream
inputStream
=
new
ByteArrayInputStream
(
Util
.
getUtf8Bytes
(
playlistString
));
HlsMediaPlaylist
playlist
=
(
HlsMediaPlaylist
)
new
HlsPlaylistParser
().
parse
(
playlistUri
,
inputStream
);
assertThat
(
playlist
.
trailingParts
).
hasSize
(
1
);
assertThat
(
Iterables
.
getLast
(
playlist
.
trailingParts
).
url
).
isEqualTo
(
"part267.1.ts"
);
assertThat
(
Iterables
.
getLast
(
playlist
.
trailingParts
).
isPreload
).
isFalse
();
}
@Test
public
void
parseMediaPlaylist_withPreloadHintTypePartAndAesPlayReadyKey_inheritsDrmInitData
()
throws
IOException
{
Uri
playlistUri
=
Uri
.
parse
(
"https://example.com/test.m3u8"
);
...
...
testdata/src/test/assets/media/m3u8/live_low_latency_segments_and_parts
0 → 100644
View file @
e3c725aa
#EXTM3U
#EXT-X-TARGETDURATION:4
#EXT-X-PART-INF:PART-TARGET=1.000400
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:10
#EXTINF:4.00000,
fileSequence10.ts
#EXTINF:4.00000,
fileSequence11.ts
#EXTINF:4.00000,
fileSequence12.ts
#EXTINF:4.00000,
fileSequence13.ts
#EXT-X-PART:DURATION=1.00000,URI="fileSequence14.0.ts"
#EXT-X-PART:DURATION=1.00000,URI="fileSequence14.1.ts"
#EXT-X-PART:DURATION=1.00000,URI="fileSequence14.2.ts"
#EXT-X-PART:DURATION=1.00000,URI="fileSequence14.3.ts"
#EXTINF:4.00000,
fileSequence14.ts
#EXT-X-PART:DURATION=1.00000,URI="fileSequence15.0.ts"
#EXT-X-PART:DURATION=1.00000,URI="fileSequence15.1.ts"
#EXT-X-PART:DURATION=1.00000,URI="fileSequence15.2.ts"
#EXT-X-PART:DURATION=1.00000,URI="fileSequence15.3.ts"
#EXTINF:4.00000,
fileSequence15.ts
#EXT-X-PART:DURATION=1.00000,URI="fileSequence16.0.ts"
#EXT-X-PART:DURATION=1.00000,URI="fileSequence16.1.ts"
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="fileSequence16.2.ts"
testdata/src/test/assets/media/m3u8/live_low_latency_segments_only
0 → 100644
View file @
e3c725aa
#EXTM3U
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:10
#EXTINF:4.00000,
fileSequence10.ts
#EXTINF:4.00000,
fileSequence11.ts
#EXTINF:4.00000,
fileSequence12.ts
#EXTINF:4.00000,
fileSequence13.ts
#EXTINF:4.00000,
fileSequence14.ts
#EXTINF:4.00000,
fileSequence15.ts
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