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
e20ea797
authored
May 12, 2021
by
Oliver Woodman
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #8767 from uvjustin:hls-start-from-independent-part
PiperOrigin-RevId: 373343326
parents
497eb72c
b3c8ffeb
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
381 additions
and
89 deletions
RELEASENOTES.md
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
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/HlsMediaSourceTest.java
RELEASENOTES.md
View file @
e20ea797
...
...
@@ -41,6 +41,9 @@
*
Ad playback:
*
Support changing ad break positions in the player logic
(
[
#5067
](
https://github.com/google/ExoPlayer/issues/5067
)
.
*
HLS
*
Use the PRECISE attribute in EXT-X-START to select the default start
position.
### 2.14.0 (2021-05-13)
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
View file @
e20ea797
...
...
@@ -503,7 +503,6 @@ public final class HlsMediaSource extends BaseMediaSource
@Override
public
void
onPrimaryPlaylistRefreshed
(
HlsMediaPlaylist
playlist
)
{
SinglePeriodTimeline
timeline
;
long
windowStartTimeMs
=
playlist
.
hasProgramDateTime
?
C
.
usToMs
(
playlist
.
startTimeUs
)
:
C
.
TIME_UNSET
;
// For playlist types EVENT and VOD we know segments are never removed, so the presentation
...
...
@@ -513,66 +512,90 @@ public final class HlsMediaSource extends BaseMediaSource
||
playlist
.
playlistType
==
HlsMediaPlaylist
.
PLAYLIST_TYPE_VOD
?
windowStartTimeMs
:
C
.
TIME_UNSET
;
long
windowDefaultStartPositionUs
=
playlist
.
startOffsetUs
;
// masterPlaylist is non-null because the first playlist has been fetched by now.
// The master playlist is non-null because the first playlist has been fetched by now.
HlsManifest
manifest
=
new
HlsManifest
(
checkNotNull
(
playlistTracker
.
getMasterPlaylist
()),
playlist
);
if
(
playlistTracker
.
isLive
())
{
long
liveEdgeOffsetUs
=
getLiveEdgeOffsetUs
(
playlist
);
long
targetLiveOffsetUs
=
liveConfiguration
.
targetOffsetMs
!=
C
.
TIME_UNSET
?
C
.
msToUs
(
liveConfiguration
.
targetOffsetMs
)
:
getTargetLiveOffsetUs
(
playlist
,
liveEdgeOffsetUs
);
// Ensure target live offset is within the live window and greater than the live edge offset.
targetLiveOffsetUs
=
Util
.
constrainValue
(
targetLiveOffsetUs
,
liveEdgeOffsetUs
,
playlist
.
durationUs
+
liveEdgeOffsetUs
);
maybeUpdateMediaItem
(
targetLiveOffsetUs
);
long
offsetFromInitialStartTimeUs
=
playlist
.
startTimeUs
-
playlistTracker
.
getInitialStartTimeUs
();
long
periodDurationUs
=
playlist
.
hasEndTag
?
offsetFromInitialStartTimeUs
+
playlist
.
durationUs
:
C
.
TIME_UNSET
;
List
<
HlsMediaPlaylist
.
Segment
>
segments
=
playlist
.
segments
;
if
(!
segments
.
isEmpty
())
{
windowDefaultStartPositionUs
=
getWindowDefaultStartPosition
(
playlist
,
liveEdgeOffsetUs
);
}
else
if
(
windowDefaultStartPositionUs
==
C
.
TIME_UNSET
)
{
windowDefaultStartPositionUs
=
0
;
}
timeline
=
new
SinglePeriodTimeline
(
presentationStartTimeMs
,
windowStartTimeMs
,
/* elapsedRealtimeEpochOffsetMs= */
C
.
TIME_UNSET
,
periodDurationUs
,
/* windowDurationUs= */
playlist
.
durationUs
,
/* windowPositionInPeriodUs= */
offsetFromInitialStartTimeUs
,
windowDefaultStartPositionUs
,
/* isSeekable= */
true
,
/* isDynamic= */
!
playlist
.
hasEndTag
,
manifest
,
mediaItem
,
liveConfiguration
);
}
else
/* not live */
{
if
(
windowDefaultStartPositionUs
==
C
.
TIME_UNSET
)
{
windowDefaultStartPositionUs
=
0
;
SinglePeriodTimeline
timeline
=
playlistTracker
.
isLive
()
?
createTimelineForLive
(
playlist
,
presentationStartTimeMs
,
windowStartTimeMs
,
manifest
)
:
createTimelineForOnDemand
(
playlist
,
presentationStartTimeMs
,
windowStartTimeMs
,
manifest
);
refreshSourceInfo
(
timeline
);
}
private
SinglePeriodTimeline
createTimelineForLive
(
HlsMediaPlaylist
playlist
,
long
presentationStartTimeMs
,
long
windowStartTimeMs
,
HlsManifest
manifest
)
{
long
offsetFromInitialStartTimeUs
=
playlist
.
startTimeUs
-
playlistTracker
.
getInitialStartTimeUs
();
long
periodDurationUs
=
playlist
.
hasEndTag
?
offsetFromInitialStartTimeUs
+
playlist
.
durationUs
:
C
.
TIME_UNSET
;
long
liveEdgeOffsetUs
=
getLiveEdgeOffsetUs
(
playlist
);
long
targetLiveOffsetUs
;
if
(
liveConfiguration
.
targetOffsetMs
!=
C
.
TIME_UNSET
)
{
// Media item has a defined target offset.
targetLiveOffsetUs
=
C
.
msToUs
(
liveConfiguration
.
targetOffsetMs
);
}
else
{
// Decide target offset from playlist.
targetLiveOffsetUs
=
getTargetLiveOffsetUs
(
playlist
,
liveEdgeOffsetUs
);
}
// Ensure target live offset is within the live window and greater than the live edge offset.
targetLiveOffsetUs
=
Util
.
constrainValue
(
targetLiveOffsetUs
,
liveEdgeOffsetUs
,
playlist
.
durationUs
+
liveEdgeOffsetUs
);
maybeUpdateLiveConfiguration
(
targetLiveOffsetUs
);
long
windowDefaultStartPositionUs
=
getLiveWindowDefaultStartPositionUs
(
playlist
,
liveEdgeOffsetUs
);
return
new
SinglePeriodTimeline
(
presentationStartTimeMs
,
windowStartTimeMs
,
/* elapsedRealtimeEpochOffsetMs= */
C
.
TIME_UNSET
,
periodDurationUs
,
/* windowDurationUs= */
playlist
.
durationUs
,
/* windowPositionInPeriodUs= */
offsetFromInitialStartTimeUs
,
windowDefaultStartPositionUs
,
/* isSeekable= */
true
,
/* isDynamic= */
!
playlist
.
hasEndTag
,
manifest
,
mediaItem
,
liveConfiguration
);
}
private
SinglePeriodTimeline
createTimelineForOnDemand
(
HlsMediaPlaylist
playlist
,
long
presentationStartTimeMs
,
long
windowStartTimeMs
,
HlsManifest
manifest
)
{
long
windowDefaultStartPositionUs
;
if
(
playlist
.
startOffsetUs
==
C
.
TIME_UNSET
||
playlist
.
segments
.
isEmpty
())
{
windowDefaultStartPositionUs
=
0
;
}
else
{
// From RFC 8216, section 4.4.2.2: if playlist.startOffsetUs is negative, it indicates the
// beginning of the Playlist, whereas if it is beyond the playlist duration it indicates the
// end of the playlist.
long
startOffsetUs
=
Util
.
constrainValue
(
playlist
.
startOffsetUs
,
0
,
playlist
.
durationUs
);
if
(
playlist
.
preciseStart
||
startOffsetUs
==
playlist
.
durationUs
)
{
windowDefaultStartPositionUs
=
startOffsetUs
;
}
else
{
windowDefaultStartPositionUs
=
findClosestPrecedingSegment
(
playlist
.
segments
,
startOffsetUs
).
relativeStartTimeUs
;
}
timeline
=
new
SinglePeriodTimeline
(
presentationStartTimeMs
,
windowStartTimeMs
,
/* elapsedRealtimeEpochOffsetMs= */
C
.
TIME_UNSET
,
/* periodDurationUs= */
playlist
.
durationUs
,
/* windowDurationUs= */
playlist
.
durationUs
,
/* windowPositionInPeriodUs= */
0
,
windowDefaultStartPositionUs
,
/* isSeekable= */
true
,
/* isDynamic= */
false
,
manifest
,
mediaItem
,
/* liveConfiguration= */
null
);
}
refreshSourceInfo
(
timeline
);
return
new
SinglePeriodTimeline
(
presentationStartTimeMs
,
windowStartTimeMs
,
/* elapsedRealtimeEpochOffsetMs= */
C
.
TIME_UNSET
,
/* periodDurationUs= */
playlist
.
durationUs
,
/* windowDurationUs= */
playlist
.
durationUs
,
/* windowPositionInPeriodUs= */
0
,
windowDefaultStartPositionUs
,
/* isSeekable= */
true
,
/* isDynamic= */
false
,
manifest
,
mediaItem
,
/* liveConfiguration= */
null
);
}
private
long
getLiveEdgeOffsetUs
(
HlsMediaPlaylist
playlist
)
{
...
...
@@ -581,19 +604,35 @@ public final class HlsMediaSource extends BaseMediaSource
:
0
;
}
private
long
getWindowDefaultStartPosition
(
HlsMediaPlaylist
playlist
,
long
liveEdgeOffsetUs
)
{
List
<
HlsMediaPlaylist
.
Segment
>
segments
=
playlist
.
segments
;
int
segmentIndex
=
segments
.
size
()
-
1
;
long
minStartPositionUs
=
private
long
getLiveWindowDefaultStartPositionUs
(
HlsMediaPlaylist
playlist
,
long
liveEdgeOffsetUs
)
{
if
(
playlist
.
startOffsetUs
!=
C
.
TIME_UNSET
&&
playlist
.
preciseStart
)
{
// From RFC 8216, section 4.4.2.2: if playlist.startOffsetUs is negative, it indicates the
// beginning of the Playlist, whereas if it is beyond the playlist duration it indicates the
// end of the playlist.
return
Util
.
constrainValue
(
playlist
.
startOffsetUs
,
0
,
playlist
.
durationUs
);
}
long
maxStartPositionUs
=
playlist
.
durationUs
+
liveEdgeOffsetUs
-
C
.
msToUs
(
liveConfiguration
.
targetOffsetMs
);
while
(
segmentIndex
>
0
&&
segments
.
get
(
segmentIndex
).
relativeStartTimeUs
>
minStartPositionUs
)
{
segmentIndex
--;
@Nullable
HlsMediaPlaylist
.
Part
part
=
findClosestPrecedingIndependentPart
(
playlist
.
trailingParts
,
maxStartPositionUs
);
if
(
part
!=
null
)
{
return
part
.
relativeStartTimeUs
;
}
if
(
playlist
.
segments
.
isEmpty
())
{
return
0
;
}
HlsMediaPlaylist
.
Segment
segment
=
findClosestPrecedingSegment
(
playlist
.
segments
,
maxStartPositionUs
);
part
=
findClosestPrecedingIndependentPart
(
segment
.
parts
,
maxStartPositionUs
);
if
(
part
!=
null
)
{
return
part
.
relativeStartTimeUs
;
}
return
segment
s
.
get
(
segmentIndex
)
.
relativeStartTimeUs
;
return
segment
.
relativeStartTimeUs
;
}
private
void
maybeUpdate
MediaItem
(
long
targetLiveOffsetUs
)
{
private
void
maybeUpdate
LiveConfiguration
(
long
targetLiveOffsetUs
)
{
long
targetLiveOffsetMs
=
C
.
usToMs
(
targetLiveOffsetUs
);
if
(
targetLiveOffsetMs
!=
liveConfiguration
.
targetOffsetMs
)
{
liveConfiguration
=
...
...
@@ -601,21 +640,68 @@ public final class HlsMediaSource extends BaseMediaSource
}
}
/**
* Gets the target live offset, in microseconds, for a live playlist.
*
* <p>The target offset is derived by checking the following in this order:
*
* <ol>
* <li>The playlist defines a start offset.
* <li>The playlist defines a part hold back in server control and has part duration.
* <li>The playlist defines a hold back in server control.
* <li>Fallback to {@code 3 x target duration}.
* </ol>
*
* @param playlist The playlist.
* @param liveEdgeOffsetUs The current live edge offset.
* @return The selected target live offset, in microseconds.
*/
private
static
long
getTargetLiveOffsetUs
(
HlsMediaPlaylist
playlist
,
long
liveEdgeOffsetUs
)
{
HlsMediaPlaylist
.
ServerControl
serverControl
=
playlist
.
serverControl
;
// Select part hold back only if the playlist has a part target duration.
long
offsetToEndOfPlaylistUs
;
long
targetOffsetUs
;
if
(
playlist
.
startOffsetUs
!=
C
.
TIME_UNSET
)
{
offsetToEndOfPlaylistUs
=
playlist
.
durationUs
-
playlist
.
startOffsetUs
;
// From RFC 8216, section 4.4.2.2: if playlist.startOffsetUs is negative, it indicates the
// beginning of the Playlist, whereas if it is beyond the playlist duration it indicates the
// end of the playlist.
long
startOffsetUs
=
Util
.
constrainValue
(
playlist
.
startOffsetUs
,
0
,
playlist
.
durationUs
);
targetOffsetUs
=
playlist
.
durationUs
-
startOffsetUs
;
}
else
if
(
serverControl
.
partHoldBackUs
!=
C
.
TIME_UNSET
&&
playlist
.
partTargetDurationUs
!=
C
.
TIME_UNSET
)
{
offsetToEndOfPlaylistUs
=
serverControl
.
partHoldBackUs
;
// Select part hold back only if the playlist has a part target duration.
targetOffsetUs
=
serverControl
.
partHoldBackUs
;
}
else
if
(
serverControl
.
holdBackUs
!=
C
.
TIME_UNSET
)
{
offsetToEndOfPlaylis
tUs
=
serverControl
.
holdBackUs
;
targetOffse
tUs
=
serverControl
.
holdBackUs
;
}
else
{
// Fallback, see RFC 8216, Section 4.4.3.8.
offsetToEndOfPlaylis
tUs
=
3
*
playlist
.
targetDurationUs
;
targetOffse
tUs
=
3
*
playlist
.
targetDurationUs
;
}
return
offsetToEndOfPlaylistUs
+
liveEdgeOffsetUs
;
return
targetOffsetUs
+
liveEdgeOffsetUs
;
}
@Nullable
private
static
HlsMediaPlaylist
.
Part
findClosestPrecedingIndependentPart
(
List
<
HlsMediaPlaylist
.
Part
>
parts
,
long
positionUs
)
{
@Nullable
HlsMediaPlaylist
.
Part
closestPart
=
null
;
for
(
int
i
=
0
;
i
<
parts
.
size
();
i
++)
{
HlsMediaPlaylist
.
Part
part
=
parts
.
get
(
i
);
if
(
part
.
relativeStartTimeUs
<=
positionUs
&&
part
.
isIndependent
)
{
closestPart
=
part
;
}
else
if
(
part
.
relativeStartTimeUs
>
positionUs
)
{
break
;
}
}
return
closestPart
;
}
/**
* Gets the segment that contains {@code positionUs}, or the last sent if the position is beyond
* the segments list.
*/
private
static
HlsMediaPlaylist
.
Segment
findClosestPrecedingSegment
(
List
<
HlsMediaPlaylist
.
Segment
>
segments
,
long
positionUs
)
{
int
segmentIndex
=
Util
.
binarySearchFloor
(
segments
,
positionUs
,
/* inclusive= */
true
,
/* stayInBounds= */
true
);
return
segments
.
get
(
segmentIndex
);
}
}
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java
View file @
e20ea797
...
...
@@ -391,8 +391,13 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
/** The type of the playlist. See {@link PlaylistType}. */
@PlaylistType
public
final
int
playlistType
;
/** The start offset in microseconds, as defined by #EXT-X-START. */
/**
* The start offset in microseconds, as defined by #EXT-X-START, or {@link C#TIME_UNSET} if
* undefined.
*/
public
final
long
startOffsetUs
;
/** Whether the start position should be precise, as defined by #EXT-X-START. */
public
final
boolean
preciseStart
;
/**
* If {@link #hasProgramDateTime} is true, contains the datetime as microseconds since epoch.
* Otherwise, contains the aggregated duration of removed segments up to this snapshot of the
...
...
@@ -467,6 +472,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
String
baseUri
,
List
<
String
>
tags
,
long
startOffsetUs
,
boolean
preciseStart
,
long
startTimeUs
,
boolean
hasDiscontinuitySequence
,
int
discontinuitySequence
,
...
...
@@ -485,6 +491,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
super
(
baseUri
,
tags
,
hasIndependentSegments
);
this
.
playlistType
=
playlistType
;
this
.
startTimeUs
=
startTimeUs
;
this
.
preciseStart
=
preciseStart
;
this
.
hasDiscontinuitySequence
=
hasDiscontinuitySequence
;
this
.
discontinuitySequence
=
discontinuitySequence
;
this
.
mediaSequence
=
mediaSequence
;
...
...
@@ -562,6 +569,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
baseUri
,
tags
,
startOffsetUs
,
preciseStart
,
startTimeUs
,
/* hasDiscontinuitySequence= */
true
,
discontinuitySequence
,
...
...
@@ -592,6 +600,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
baseUri
,
tags
,
startOffsetUs
,
preciseStart
,
startTimeUs
,
hasDiscontinuitySequence
,
discontinuitySequence
,
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
View file @
e20ea797
...
...
@@ -217,6 +217,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
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_PRECISE
=
compileBooleanAttrPattern
(
"PRECISE"
);
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
=
...
...
@@ -652,6 +653,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
int
relativeDiscontinuitySequence
=
0
;
long
playlistStartTimeUs
=
0
;
long
segmentStartTimeUs
=
0
;
boolean
preciseStart
=
false
;
long
segmentByteRangeOffset
=
0
;
long
segmentByteRangeLength
=
C
.
LENGTH_UNSET
;
long
partStartTimeUs
=
0
;
...
...
@@ -694,6 +696,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
isIFrameOnly
=
true
;
}
else
if
(
line
.
startsWith
(
TAG_START
))
{
startOffsetUs
=
(
long
)
(
parseDoubleAttr
(
line
,
REGEX_TIME_OFFSET
)
*
C
.
MICROS_PER_SECOND
);
preciseStart
=
parseOptionalBooleanAttribute
(
line
,
REGEX_PRECISE
,
/* defaultValue= */
false
);
}
else
if
(
line
.
startsWith
(
TAG_SERVER_CONTROL
))
{
serverControl
=
parseServerControl
(
line
);
}
else
if
(
line
.
startsWith
(
TAG_PART_INF
))
{
...
...
@@ -1024,6 +1028,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
baseUri
,
tags
,
startOffsetUs
,
preciseStart
,
playlistStartTimeUs
,
hasDiscontinuitySequence
,
playlistDiscontinuitySequence
,
...
...
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaSourceTest.java
View file @
e20ea797
...
...
@@ -122,7 +122,7 @@ public class HlsMediaSourceTest {
}
@Test
public
void
loadPlaylist_noTargetLiveOffsetDefined_fallbackToThreeTargetDuration
()
public
void
load
Live
Playlist_noTargetLiveOffsetDefined_fallbackToThreeTargetDuration
()
throws
TimeoutException
,
ParserException
{
String
playlistUri
=
"fake://foo.bar/media0/playlist.m3u8"
;
// The playlist has a duration of 16 seconds but not hold back or part hold back.
...
...
@@ -158,7 +158,7 @@ public class HlsMediaSourceTest {
}
@Test
public
void
loadPlaylist_holdBackInPlaylist_targetLiveOffsetFromHoldBack
()
public
void
load
Live
Playlist_holdBackInPlaylist_targetLiveOffsetFromHoldBack
()
throws
TimeoutException
,
ParserException
{
String
playlistUri
=
"fake://foo.bar/media0/playlist.m3u8"
;
// The playlist has a duration of 16 seconds and a hold back of 12 seconds.
...
...
@@ -195,7 +195,7 @@ public class HlsMediaSourceTest {
@Test
public
void
loadPlaylist_partHoldBackWithoutPartInformationInPlaylist_targetLiveOffsetFromHoldBack
()
load
Live
Playlist_partHoldBackWithoutPartInformationInPlaylist_targetLiveOffsetFromHoldBack
()
throws
TimeoutException
,
ParserException
{
String
playlistUri
=
"fake://foo.bar/media0/playlist.m3u8"
;
// The playlist has a part hold back but not EXT-X-PART-INF. We should pick up the hold back.
...
...
@@ -233,7 +233,7 @@ public class HlsMediaSourceTest {
@Test
public
void
loadPlaylist_partHoldBackWithPartInformationInPlaylist_targetLiveOffsetFromPartHoldBack
()
load
Live
Playlist_partHoldBackWithPartInformationInPlaylist_targetLiveOffsetFromPartHoldBack
()
throws
TimeoutException
,
ParserException
{
String
playlistUri
=
"fake://foo.bar/media0/playlist.m3u8"
;
// The playlist has a duration of 4 seconds, part hold back and EXT-X-PART-INF defined.
...
...
@@ -263,7 +263,44 @@ public class HlsMediaSourceTest {
}
@Test
public
void
loadPlaylist_withPlaylistStartTime_targetLiveOffsetFromStartTime
()
public
void
loadLivePlaylist_withParts_defaultPositionPointsAtClosestIndependentPart
()
throws
TimeoutException
,
ParserException
{
String
playlistUri
=
"fake://foo.bar/media0/playlist.m3u8"
;
// The playlist has a duration of 7 seconds, part hold back and EXT-X-PART-INF defined.
String
playlist
=
"#EXTM3U\n"
+
"#EXT-X-PROGRAM-DATE-TIME:2020-01-01T00:00:00.0+00:00\n"
+
"#EXT-X-TARGETDURATION:4\n"
+
"#EXT-X-VERSION:3\n"
+
"#EXT-X-MEDIA-SEQUENCE:0\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence0.ts\n"
+
"#EXT-X-SERVER-CONTROL:HOLD-BACK=12,PART-HOLD-BACK=2\n"
+
"#EXT-X-PART-INF:PART-TARGET=0.5\n"
+
"#EXT-X-PART:DURATION=0.5000,URI=\"fileSequence1.0.ts\",INDEPENDENT=YES\n"
+
"#EXT-X-PART:DURATION=0.5000,URI=\"fileSequence1.1.ts\"\n"
+
"#EXT-X-PART:DURATION=0.5000,URI=\"fileSequence1.2.ts\",INDEPENDENT=YES\n"
+
"#EXT-X-PART:DURATION=0.5000,URI=\"fileSequence1.3.ts\"\n"
+
"#EXT-X-PART:DURATION=0.5000,URI=\"fileSequence1.4.ts\",INDEPENDENT=YES\n"
+
"#EXT-X-PART:DURATION=0.5000,URI=\"fileSequence1.5.ts\""
;
// The playlist finishes 1 second before the current time.
SystemClock
.
setCurrentTimeMillis
(
Util
.
parseXsDateTime
(
"2020-01-01T00:00:08.0+00:00"
));
HlsMediaSource
.
Factory
factory
=
createHlsMediaSourceFactory
(
playlistUri
,
playlist
);
MediaItem
mediaItem
=
MediaItem
.
fromUri
(
playlistUri
);
HlsMediaSource
mediaSource
=
factory
.
createMediaSource
(
mediaItem
);
Timeline
timeline
=
prepareAndWaitForTimeline
(
mediaSource
);
Timeline
.
Window
window
=
timeline
.
getWindow
(
0
,
new
Timeline
.
Window
());
// The target live offset is picked from part hold back and then expressed in relation to the
// live edge (+1 seconds).
assertThat
(
window
.
liveConfiguration
.
targetOffsetMs
).
isEqualTo
(
3000
);
// The default position points the closest preceding independent part.
assertThat
(
window
.
defaultPositionUs
).
isEqualTo
(
5000000
);
}
@Test
public
void
loadLivePlaylist_withNonPreciseStartTime_targetLiveOffsetFromStartTime
()
throws
TimeoutException
,
ParserException
{
String
playlistUri
=
"fake://foo.bar/media0/playlist.m3u8"
;
// The playlist has a duration of 16 seconds, and part hold back, hold back and start time
...
...
@@ -273,7 +310,45 @@ public class HlsMediaSourceTest {
+
"#EXT-X-PROGRAM-DATE-TIME:2020-01-01T00:00:00.0+00:00\n"
+
"#EXT-X-TARGETDURATION:4\n"
+
"#EXT-X-VERSION:3\n"
+
"#EXT-X-START:TIME-OFFSET=-15"
+
"#EXT-X-START:TIME-OFFSET=-10\n"
+
"#EXT-X-MEDIA-SEQUENCE:0\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence0.ts\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence1.ts\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence2.ts\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence3.ts\n"
+
"#EXT-X-SERVER-CONTROL:HOLD-BACK=12,PART-HOLD-BACK=3\n"
;
// The playlist finishes 1 second before the current time.
SystemClock
.
setCurrentTimeMillis
(
Util
.
parseXsDateTime
(
"2020-01-01T00:00:17.0+00:00"
));
HlsMediaSource
.
Factory
factory
=
createHlsMediaSourceFactory
(
playlistUri
,
playlist
);
MediaItem
mediaItem
=
MediaItem
.
fromUri
(
playlistUri
);
HlsMediaSource
mediaSource
=
factory
.
createMediaSource
(
mediaItem
);
Timeline
timeline
=
prepareAndWaitForTimeline
(
mediaSource
);
Timeline
.
Window
window
=
timeline
.
getWindow
(
0
,
new
Timeline
.
Window
());
// The target live offset is picked from start time (16 - 10 = 6) and then expressed in relation
// to the live edge (17 - 6 = 11 seconds).
assertThat
(
window
.
liveConfiguration
.
targetOffsetMs
).
isEqualTo
(
11000
);
// The default position points to the segment containing the start time.
assertThat
(
window
.
defaultPositionUs
).
isEqualTo
(
4000000
);
}
@Test
public
void
loadLivePlaylist_withPreciseStartTime_targetLiveOffsetFromStartTime
()
throws
TimeoutException
,
ParserException
{
String
playlistUri
=
"fake://foo.bar/media0/playlist.m3u8"
;
// The playlist has a duration of 16 seconds, and part hold back, hold back and start time
// defined.
String
playlist
=
"#EXTM3U\n"
+
"#EXT-X-PROGRAM-DATE-TIME:2020-01-01T00:00:00.0+00:00\n"
+
"#EXT-X-TARGETDURATION:4\n"
+
"#EXT-X-VERSION:3\n"
+
"#EXT-X-START:TIME-OFFSET=-10,PRECISE=YES\n"
+
"#EXT-X-MEDIA-SEQUENCE:0\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence0.ts\n"
...
...
@@ -283,7 +358,6 @@ public class HlsMediaSourceTest {
+
"fileSequence2.ts\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence3.ts\n"
+
"#EXT-X-PART-INF:PART-TARGET=0.5\n"
+
"#EXT-X-SERVER-CONTROL:HOLD-BACK=12,PART-HOLD-BACK=3"
;
// The playlist finishes 1 second before the current time.
SystemClock
.
setCurrentTimeMillis
(
Util
.
parseXsDateTime
(
"2020-01-01T00:00:17.0+00:00"
));
...
...
@@ -294,14 +368,15 @@ public class HlsMediaSourceTest {
Timeline
timeline
=
prepareAndWaitForTimeline
(
mediaSource
);
Timeline
.
Window
window
=
timeline
.
getWindow
(
0
,
new
Timeline
.
Window
());
// The target live offset is picked from start time and then expressed in relation to the live
// edge (+1 seconds).
assertThat
(
window
.
liveConfiguration
.
targetOffsetMs
).
isEqualTo
(
16000
);
assertThat
(
window
.
defaultPositionUs
).
isEqualTo
(
0
);
// The target live offset is picked from start time (16 - 10 = 6) and then expressed in relation
// to the live edge (17 - 7 = 11 seconds).
assertThat
(
window
.
liveConfiguration
.
targetOffsetMs
).
isEqualTo
(
11000
);
// The default position points to the start time.
assertThat
(
window
.
defaultPositionUs
).
isEqualTo
(
6000000
);
}
@Test
public
void
loadPlaylist_targetLiveOffsetInMediaItem_targetLiveOffsetPickedFromMediaItem
()
public
void
load
Live
Playlist_targetLiveOffsetInMediaItem_targetLiveOffsetPickedFromMediaItem
()
throws
TimeoutException
,
ParserException
{
String
playlistUri
=
"fake://foo.bar/media0/playlist.m3u8"
;
// The playlist has a hold back of 12 seconds and a part hold back of 3 seconds.
...
...
@@ -331,8 +406,9 @@ public class HlsMediaSourceTest {
}
@Test
public
void
loadPlaylist_targetLiveOffsetLargerThanLiveWindow_targetLiveOffsetIsWithinLiveWindow
()
throws
TimeoutException
,
ParserException
{
public
void
loadLivePlaylist_targetLiveOffsetLargerThanLiveWindow_targetLiveOffsetIsWithinLiveWindow
()
throws
TimeoutException
,
ParserException
{
String
playlistUri
=
"fake://foo.bar/media0/playlist.m3u8"
;
// The playlist has a duration of 8 seconds and a hold back of 12 seconds.
String
playlist
=
...
...
@@ -364,7 +440,7 @@ public class HlsMediaSourceTest {
@Test
public
void
loadPlaylist_withoutProgramDateTime_targetLiveOffsetFromPlaylistNotAdjustedToLiveEdge
()
load
Live
Playlist_withoutProgramDateTime_targetLiveOffsetFromPlaylistNotAdjustedToLiveEdge
()
throws
TimeoutException
{
String
playlistUri
=
"fake://foo.bar/media0/playlist.m3u8"
;
// The playlist has a duration of 16 seconds and a hold back of 12 seconds.
...
...
@@ -398,6 +474,119 @@ public class HlsMediaSourceTest {
}
@Test
public
void
loadOnDemandPlaylist_withPreciseStartTime_setsDefaultPosition
()
throws
TimeoutException
{
String
playlistUri
=
"fake://foo.bar/media0/playlist.m3u8"
;
String
playlist
=
"#EXTM3U\n"
+
"#EXT-X-PLAYLIST-TYPE:VOD\n"
+
"#EXT-X-TARGETDURATION:10\n"
+
"#EXT-X-VERSION:4\n"
+
"#EXT-X-START:TIME-OFFSET=15.000,PRECISE=YES"
+
"#EXT-X-MEDIA-SEQUENCE:0\n"
+
"#EXTINF:10.0,\n"
+
"fileSequence1.ts\n"
+
"#EXTINF:10.0,\n"
+
"fileSequence2.ts\n"
+
"#EXT-X-ENDLIST"
;
HlsMediaSource
.
Factory
factory
=
createHlsMediaSourceFactory
(
playlistUri
,
playlist
);
MediaItem
mediaItem
=
new
MediaItem
.
Builder
().
setUri
(
playlistUri
).
build
();
HlsMediaSource
mediaSource
=
factory
.
createMediaSource
(
mediaItem
);
Timeline
timeline
=
prepareAndWaitForTimeline
(
mediaSource
);
Timeline
.
Window
window
=
timeline
.
getWindow
(
0
,
new
Timeline
.
Window
());
// The target live offset is not adjusted to the live edge because the list does not have
// program date time.
assertThat
(
window
.
liveConfiguration
).
isNull
();
assertThat
(
window
.
defaultPositionUs
).
isEqualTo
(
15000000
);
}
@Test
public
void
loadOnDemandPlaylist_withNonPreciseStartTime_setsDefaultPosition
()
throws
TimeoutException
{
String
playlistUri
=
"fake://foo.bar/media0/playlist.m3u8"
;
String
playlist
=
"#EXTM3U\n"
+
"#EXT-X-PLAYLIST-TYPE:VOD\n"
+
"#EXT-X-TARGETDURATION:10\n"
+
"#EXT-X-VERSION:4\n"
+
"#EXT-X-START:TIME-OFFSET=15.000"
+
"#EXT-X-MEDIA-SEQUENCE:0\n"
+
"#EXTINF:10.0,\n"
+
"fileSequence1.ts\n"
+
"#EXTINF:10.0,\n"
+
"fileSequence2.ts\n"
+
"#EXT-X-ENDLIST"
;
HlsMediaSource
.
Factory
factory
=
createHlsMediaSourceFactory
(
playlistUri
,
playlist
);
MediaItem
mediaItem
=
new
MediaItem
.
Builder
().
setUri
(
playlistUri
).
build
();
HlsMediaSource
mediaSource
=
factory
.
createMediaSource
(
mediaItem
);
Timeline
timeline
=
prepareAndWaitForTimeline
(
mediaSource
);
Timeline
.
Window
window
=
timeline
.
getWindow
(
0
,
new
Timeline
.
Window
());
// The target live offset is not adjusted to the live edge because the list does not have
// program date time.
assertThat
(
window
.
liveConfiguration
).
isNull
();
assertThat
(
window
.
defaultPositionUs
).
isEqualTo
(
10000000
);
}
@Test
public
void
loadOnDemandPlaylist_withStartTimeBeforeTheBeginning_setsDefaultPositionToTheBeginning
()
throws
TimeoutException
{
String
playlistUri
=
"fake://foo.bar/media0/playlist.m3u8"
;
String
playlist
=
"#EXTM3U\n"
+
"#EXT-X-PLAYLIST-TYPE:VOD\n"
+
"#EXT-X-TARGETDURATION:10\n"
+
"#EXT-X-VERSION:4\n"
+
"#EXT-X-START:TIME-OFFSET=-35.000"
+
"#EXT-X-MEDIA-SEQUENCE:0\n"
+
"#EXTINF:10.0,\n"
+
"fileSequence1.ts\n"
+
"#EXTINF:10.0,\n"
+
"fileSequence2.ts\n"
+
"#EXT-X-ENDLIST"
;
HlsMediaSource
.
Factory
factory
=
createHlsMediaSourceFactory
(
playlistUri
,
playlist
);
MediaItem
mediaItem
=
new
MediaItem
.
Builder
().
setUri
(
playlistUri
).
build
();
HlsMediaSource
mediaSource
=
factory
.
createMediaSource
(
mediaItem
);
Timeline
timeline
=
prepareAndWaitForTimeline
(
mediaSource
);
Timeline
.
Window
window
=
timeline
.
getWindow
(
0
,
new
Timeline
.
Window
());
assertThat
(
window
.
liveConfiguration
).
isNull
();
assertThat
(
window
.
defaultPositionUs
).
isEqualTo
(
0
);
}
@Test
public
void
loadOnDemandPlaylist_withStartTimeAfterTheNed_setsDefaultPositionToTheEnd
()
throws
TimeoutException
{
String
playlistUri
=
"fake://foo.bar/media0/playlist.m3u8"
;
String
playlist
=
"#EXTM3U\n"
+
"#EXT-X-PLAYLIST-TYPE:VOD\n"
+
"#EXT-X-TARGETDURATION:10\n"
+
"#EXT-X-VERSION:4\n"
+
"#EXT-X-START:TIME-OFFSET=35.000"
+
"#EXT-X-MEDIA-SEQUENCE:0\n"
+
"#EXTINF:10.0,\n"
+
"fileSequence1.ts\n"
+
"#EXTINF:10.0,\n"
+
"fileSequence2.ts\n"
+
"#EXT-X-ENDLIST"
;
HlsMediaSource
.
Factory
factory
=
createHlsMediaSourceFactory
(
playlistUri
,
playlist
);
MediaItem
mediaItem
=
new
MediaItem
.
Builder
().
setUri
(
playlistUri
).
build
();
HlsMediaSource
mediaSource
=
factory
.
createMediaSource
(
mediaItem
);
Timeline
timeline
=
prepareAndWaitForTimeline
(
mediaSource
);
Timeline
.
Window
window
=
timeline
.
getWindow
(
0
,
new
Timeline
.
Window
());
assertThat
(
window
.
liveConfiguration
).
isNull
();
assertThat
(
window
.
defaultPositionUs
).
isEqualTo
(
20000000
);
}
@Test
public
void
refreshPlaylist_targetLiveOffsetRemainsInWindow
()
throws
TimeoutException
,
IOException
{
String
playlistUri1
=
"fake://foo.bar/media0/playlist1.m3u8"
;
...
...
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