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
42a2b923
authored
Nov 02, 2020
by
christosts
Committed by
Oliver Woodman
Nov 02, 2020
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
HLS: populate targetLiveOffset in MediaItem from server control
Issue: #5011 PiperOrigin-RevId: 340260636
parent
e1211f92
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
459 additions
and
15 deletions
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaSourceTest.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaSource.java
View file @
42a2b923
...
@@ -16,12 +16,13 @@
...
@@ -16,12 +16,13 @@
package
com
.
google
.
android
.
exoplayer2
.
source
.
hls
;
package
com
.
google
.
android
.
exoplayer2
.
source
.
hls
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
static
java
.
lang
.
Math
.
max
;
import
static
java
.
lang
.
annotation
.
RetentionPolicy
.
SOURCE
;
import
static
java
.
lang
.
annotation
.
RetentionPolicy
.
SOURCE
;
import
android.net.Uri
;
import
android.net.Uri
;
import
android.os.SystemClock
;
import
androidx.annotation.IntDef
;
import
androidx.annotation.IntDef
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.VisibleForTesting
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.ExoPlayerLibraryInfo
;
import
com.google.android.exoplayer2.ExoPlayerLibraryInfo
;
import
com.google.android.exoplayer2.MediaItem
;
import
com.google.android.exoplayer2.MediaItem
;
...
@@ -52,6 +53,7 @@ import com.google.android.exoplayer2.upstream.HttpDataSource;
...
@@ -52,6 +53,7 @@ import com.google.android.exoplayer2.upstream.HttpDataSource;
import
com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy
;
import
com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy
;
import
com.google.android.exoplayer2.upstream.TransferListener
;
import
com.google.android.exoplayer2.upstream.TransferListener
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.MimeTypes
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.lang.annotation.Documented
;
import
java.lang.annotation.Documented
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.Retention
;
...
@@ -87,7 +89,6 @@ public final class HlsMediaSource extends BaseMediaSource
...
@@ -87,7 +89,6 @@ public final class HlsMediaSource extends BaseMediaSource
public
static
final
int
METADATA_TYPE_ID3
=
1
;
public
static
final
int
METADATA_TYPE_ID3
=
1
;
/** Type for ESMG metadata in HLS streams. */
/** Type for ESMG metadata in HLS streams. */
public
static
final
int
METADATA_TYPE_EMSG
=
3
;
public
static
final
int
METADATA_TYPE_EMSG
=
3
;
/** Factory for {@link HlsMediaSource}s. */
/** Factory for {@link HlsMediaSource}s. */
public
static
final
class
Factory
implements
MediaSourceFactory
{
public
static
final
class
Factory
implements
MediaSourceFactory
{
...
@@ -105,6 +106,7 @@ public final class HlsMediaSource extends BaseMediaSource
...
@@ -105,6 +106,7 @@ public final class HlsMediaSource extends BaseMediaSource
private
boolean
useSessionKeys
;
private
boolean
useSessionKeys
;
private
List
<
StreamKey
>
streamKeys
;
private
List
<
StreamKey
>
streamKeys
;
@Nullable
private
Object
tag
;
@Nullable
private
Object
tag
;
private
long
elapsedRealTimeOffsetMs
;
/**
/**
* Creates a new factory for {@link HlsMediaSource}s.
* Creates a new factory for {@link HlsMediaSource}s.
...
@@ -133,6 +135,7 @@ public final class HlsMediaSource extends BaseMediaSource
...
@@ -133,6 +135,7 @@ public final class HlsMediaSource extends BaseMediaSource
compositeSequenceableLoaderFactory
=
new
DefaultCompositeSequenceableLoaderFactory
();
compositeSequenceableLoaderFactory
=
new
DefaultCompositeSequenceableLoaderFactory
();
metadataType
=
METADATA_TYPE_ID3
;
metadataType
=
METADATA_TYPE_ID3
;
streamKeys
=
Collections
.
emptyList
();
streamKeys
=
Collections
.
emptyList
();
elapsedRealTimeOffsetMs
=
C
.
TIME_UNSET
;
}
}
/**
/**
...
@@ -316,6 +319,20 @@ public final class HlsMediaSource extends BaseMediaSource
...
@@ -316,6 +319,20 @@ public final class HlsMediaSource extends BaseMediaSource
return
this
;
return
this
;
}
}
/**
* Sets the offset between {@link SystemClock#elapsedRealtime()} and the time since the Unix
* epoch. By default, is it set to {@link C#TIME_UNSET}.
*
* @param elapsedRealTimeOffsetMs The offset between {@link SystemClock#elapsedRealtime()} and
* the time since the Unix epoch, in milliseconds.
* @return This factory, for convenience.
*/
@VisibleForTesting
/* package */
Factory
setElapsedRealTimeOffsetMs
(
long
elapsedRealTimeOffsetMs
)
{
this
.
elapsedRealTimeOffsetMs
=
elapsedRealTimeOffsetMs
;
return
this
;
}
/** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */
/** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */
@SuppressWarnings
(
"deprecation"
)
@SuppressWarnings
(
"deprecation"
)
@Deprecated
@Deprecated
...
@@ -364,6 +381,7 @@ public final class HlsMediaSource extends BaseMediaSource
...
@@ -364,6 +381,7 @@ public final class HlsMediaSource extends BaseMediaSource
loadErrorHandlingPolicy
,
loadErrorHandlingPolicy
,
playlistTrackerFactory
.
createTracker
(
playlistTrackerFactory
.
createTracker
(
hlsDataSourceFactory
,
loadErrorHandlingPolicy
,
playlistParserFactory
),
hlsDataSourceFactory
,
loadErrorHandlingPolicy
,
playlistParserFactory
),
elapsedRealTimeOffsetMs
,
allowChunklessPreparation
,
allowChunklessPreparation
,
metadataType
,
metadataType
,
useSessionKeys
);
useSessionKeys
);
...
@@ -376,7 +394,6 @@ public final class HlsMediaSource extends BaseMediaSource
...
@@ -376,7 +394,6 @@ public final class HlsMediaSource extends BaseMediaSource
}
}
private
final
HlsExtractorFactory
extractorFactory
;
private
final
HlsExtractorFactory
extractorFactory
;
private
final
MediaItem
mediaItem
;
private
final
MediaItem
.
PlaybackProperties
playbackProperties
;
private
final
MediaItem
.
PlaybackProperties
playbackProperties
;
private
final
HlsDataSourceFactory
dataSourceFactory
;
private
final
HlsDataSourceFactory
dataSourceFactory
;
private
final
CompositeSequenceableLoaderFactory
compositeSequenceableLoaderFactory
;
private
final
CompositeSequenceableLoaderFactory
compositeSequenceableLoaderFactory
;
...
@@ -386,7 +403,9 @@ public final class HlsMediaSource extends BaseMediaSource
...
@@ -386,7 +403,9 @@ public final class HlsMediaSource extends BaseMediaSource
private
final
@MetadataType
int
metadataType
;
private
final
@MetadataType
int
metadataType
;
private
final
boolean
useSessionKeys
;
private
final
boolean
useSessionKeys
;
private
final
HlsPlaylistTracker
playlistTracker
;
private
final
HlsPlaylistTracker
playlistTracker
;
private
final
long
elapsedRealTimeOffsetMs
;
private
MediaItem
mediaItem
;
@Nullable
private
TransferListener
mediaTransferListener
;
@Nullable
private
TransferListener
mediaTransferListener
;
private
HlsMediaSource
(
private
HlsMediaSource
(
...
@@ -397,6 +416,7 @@ public final class HlsMediaSource extends BaseMediaSource
...
@@ -397,6 +416,7 @@ public final class HlsMediaSource extends BaseMediaSource
DrmSessionManager
drmSessionManager
,
DrmSessionManager
drmSessionManager
,
LoadErrorHandlingPolicy
loadErrorHandlingPolicy
,
LoadErrorHandlingPolicy
loadErrorHandlingPolicy
,
HlsPlaylistTracker
playlistTracker
,
HlsPlaylistTracker
playlistTracker
,
long
elapsedRealTimeOffsetMs
,
boolean
allowChunklessPreparation
,
boolean
allowChunklessPreparation
,
@MetadataType
int
metadataType
,
@MetadataType
int
metadataType
,
boolean
useSessionKeys
)
{
boolean
useSessionKeys
)
{
...
@@ -408,6 +428,7 @@ public final class HlsMediaSource extends BaseMediaSource
...
@@ -408,6 +428,7 @@ public final class HlsMediaSource extends BaseMediaSource
this
.
drmSessionManager
=
drmSessionManager
;
this
.
drmSessionManager
=
drmSessionManager
;
this
.
loadErrorHandlingPolicy
=
loadErrorHandlingPolicy
;
this
.
loadErrorHandlingPolicy
=
loadErrorHandlingPolicy
;
this
.
playlistTracker
=
playlistTracker
;
this
.
playlistTracker
=
playlistTracker
;
this
.
elapsedRealTimeOffsetMs
=
elapsedRealTimeOffsetMs
;
this
.
allowChunklessPreparation
=
allowChunklessPreparation
;
this
.
allowChunklessPreparation
=
allowChunklessPreparation
;
this
.
metadataType
=
metadataType
;
this
.
metadataType
=
metadataType
;
this
.
useSessionKeys
=
useSessionKeys
;
this
.
useSessionKeys
=
useSessionKeys
;
...
@@ -491,25 +512,28 @@ public final class HlsMediaSource extends BaseMediaSource
...
@@ -491,25 +512,28 @@ public final class HlsMediaSource extends BaseMediaSource
HlsManifest
manifest
=
HlsManifest
manifest
=
new
HlsManifest
(
checkNotNull
(
playlistTracker
.
getMasterPlaylist
()),
playlist
);
new
HlsManifest
(
checkNotNull
(
playlistTracker
.
getMasterPlaylist
()),
playlist
);
if
(
playlistTracker
.
isLive
())
{
if
(
playlistTracker
.
isLive
())
{
long
liveEdgeOffsetUs
=
getLiveEdgeOffsetUs
(
playlist
);
long
targetLiveOffsetUs
=
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
!=
C
.
TIME_UNSET
?
C
.
msToUs
(
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
)
:
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
=
long
offsetFromInitialStartTimeUs
=
playlist
.
startTimeUs
-
playlistTracker
.
getInitialStartTimeUs
();
playlist
.
startTimeUs
-
playlistTracker
.
getInitialStartTimeUs
();
long
periodDurationUs
=
long
periodDurationUs
=
playlist
.
hasEndTag
?
offsetFromInitialStartTimeUs
+
playlist
.
durationUs
:
C
.
TIME_UNSET
;
playlist
.
hasEndTag
?
offsetFromInitialStartTimeUs
+
playlist
.
durationUs
:
C
.
TIME_UNSET
;
List
<
HlsMediaPlaylist
.
Segment
>
segments
=
playlist
.
segments
;
List
<
HlsMediaPlaylist
.
Segment
>
segments
=
playlist
.
segments
;
if
(
windowDefaultStartPositionUs
==
C
.
TIME_UNSET
)
{
if
(!
segments
.
isEmpty
())
{
windowDefaultStartPositionUs
=
getWindowDefaultStartPosition
(
playlist
,
liveEdgeOffsetUs
);
}
else
if
(
windowDefaultStartPositionUs
==
C
.
TIME_UNSET
)
{
windowDefaultStartPositionUs
=
0
;
windowDefaultStartPositionUs
=
0
;
if
(!
segments
.
isEmpty
())
{
int
defaultStartSegmentIndex
=
max
(
0
,
segments
.
size
()
-
3
);
// We attempt to set the default start position to be at least twice the target duration
// behind the live edge.
long
minStartPositionUs
=
playlist
.
durationUs
-
playlist
.
targetDurationUs
*
2
;
while
(
defaultStartSegmentIndex
>
0
&&
segments
.
get
(
defaultStartSegmentIndex
).
relativeStartTimeUs
>
minStartPositionUs
)
{
defaultStartSegmentIndex
--;
}
windowDefaultStartPositionUs
=
segments
.
get
(
defaultStartSegmentIndex
).
relativeStartTimeUs
;
}
}
}
timeline
=
timeline
=
new
SinglePeriodTimeline
(
new
SinglePeriodTimeline
(
presentationStartTimeMs
,
presentationStartTimeMs
,
...
@@ -545,4 +569,47 @@ public final class HlsMediaSource extends BaseMediaSource
...
@@ -545,4 +569,47 @@ public final class HlsMediaSource extends BaseMediaSource
}
}
refreshSourceInfo
(
timeline
);
refreshSourceInfo
(
timeline
);
}
}
private
long
getLiveEdgeOffsetUs
(
HlsMediaPlaylist
playlist
)
{
return
playlist
.
hasProgramDateTime
?
C
.
msToUs
(
Util
.
getNowUnixTimeMs
(
elapsedRealTimeOffsetMs
))
-
playlist
.
getEndTimeUs
()
:
0
;
}
private
long
getWindowDefaultStartPosition
(
HlsMediaPlaylist
playlist
,
long
liveEdgeOffsetUs
)
{
List
<
HlsMediaPlaylist
.
Segment
>
segments
=
playlist
.
segments
;
int
segmentIndex
=
segments
.
size
()
-
1
;
long
minStartPositionUs
=
playlist
.
durationUs
+
liveEdgeOffsetUs
-
C
.
msToUs
(
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
);
while
(
segmentIndex
>
0
&&
segments
.
get
(
segmentIndex
).
relativeStartTimeUs
>
minStartPositionUs
)
{
segmentIndex
--;
}
return
segments
.
get
(
segmentIndex
).
relativeStartTimeUs
;
}
private
void
maybeUpdateMediaItem
(
long
targetLiveOffsetUs
)
{
long
targetLiveOffsetMs
=
C
.
usToMs
(
targetLiveOffsetUs
);
if
(
targetLiveOffsetMs
!=
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
)
{
mediaItem
=
mediaItem
.
buildUpon
().
setLiveTargetOffsetMs
(
targetLiveOffsetMs
).
build
();
}
}
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
;
if
(
serverControl
.
partHoldBackUs
!=
C
.
TIME_UNSET
&&
playlist
.
partTargetDurationUs
!=
C
.
TIME_UNSET
)
{
offsetToEndOfPlaylistUs
=
serverControl
.
partHoldBackUs
;
}
else
if
(
serverControl
.
holdBackUs
!=
C
.
TIME_UNSET
)
{
offsetToEndOfPlaylistUs
=
serverControl
.
holdBackUs
;
}
else
{
// Fallback, see RFC 8216, Section 4.4.3.8.
offsetToEndOfPlaylistUs
=
3
*
playlist
.
targetDurationUs
;
}
return
offsetToEndOfPlaylistUs
+
liveEdgeOffsetUs
;
}
}
}
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/HlsMediaSourceTest.java
View file @
42a2b923
...
@@ -15,15 +15,33 @@
...
@@ -15,15 +15,33 @@
*/
*/
package
com
.
google
.
android
.
exoplayer2
.
source
.
hls
;
package
com
.
google
.
android
.
exoplayer2
.
source
.
hls
;
import
static
com
.
google
.
android
.
exoplayer2
.
robolectric
.
RobolectricUtil
.
runMainLooperUntil
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
static
org
.
mockito
.
Mockito
.
mock
;
import
static
org
.
mockito
.
Mockito
.
mock
;
import
android.net.Uri
;
import
android.os.SystemClock
;
import
androidx.annotation.Nullable
;
import
androidx.annotation.Nullable
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.C
;
import
com.google.android.exoplayer2.MediaItem
;
import
com.google.android.exoplayer2.MediaItem
;
import
com.google.android.exoplayer2.ParserException
;
import
com.google.android.exoplayer2.Timeline
;
import
com.google.android.exoplayer2.offline.StreamKey
;
import
com.google.android.exoplayer2.offline.StreamKey
;
import
com.google.android.exoplayer2.source.MediaSource
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser
;
import
com.google.android.exoplayer2.testutil.FakeDataSet
;
import
com.google.android.exoplayer2.testutil.FakeDataSource
;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
com.google.android.exoplayer2.util.Util
;
import
java.io.ByteArrayInputStream
;
import
java.io.IOException
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.concurrent.TimeoutException
;
import
java.util.concurrent.atomic.AtomicReference
;
import
org.junit.Test
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
import
org.junit.runner.RunWith
;
...
@@ -132,4 +150,363 @@ public class HlsMediaSourceTest {
...
@@ -132,4 +150,363 @@ public class HlsMediaSourceTest {
assertThat
(
hlsMediaItem
.
playbackProperties
.
uri
).
isEqualTo
(
mediaItem
.
playbackProperties
.
uri
);
assertThat
(
hlsMediaItem
.
playbackProperties
.
uri
).
isEqualTo
(
mediaItem
.
playbackProperties
.
uri
);
assertThat
(
hlsMediaItem
.
playbackProperties
.
streamKeys
).
containsExactly
(
mediaItemStreamKey
);
assertThat
(
hlsMediaItem
.
playbackProperties
.
streamKeys
).
containsExactly
(
mediaItemStreamKey
);
}
}
@Test
public
void
loadPlaylist_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.
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"
+
"#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:CAN-SKIP-UNTIL=24"
;
// The playlist finishes 1 second before the the current time, therefore there's a live edge
// offset of 1 second.
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 target duration (3 * 4 = 12 seconds) and then expressed
// in relation to the live edge (12 + 1 seconds).
assertThat
(
window
.
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
).
isEqualTo
(
13000
);
assertThat
(
window
.
defaultPositionUs
).
isEqualTo
(
4000000
);
}
@Test
public
void
loadPlaylist_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.
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"
+
"#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"
;
// The playlist finishes 1 second before the the current time, therefore there's a live edge
// offset of 1 second.
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 hold back and then expressed in relation to the live
// edge (+1 seconds).
assertThat
(
window
.
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
).
isEqualTo
(
13000
);
assertThat
(
window
.
defaultPositionUs
).
isEqualTo
(
4000000
);
}
@Test
public
void
loadPlaylist_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.
// The duration of the playlist is 16 seconds so that the defined hold back is within the live
// window.
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"
+
"#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"
;
// The playlist finishes 1 second before the 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 hold back and then expressed in relation to the live
// edge (+1 seconds).
assertThat
(
window
.
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
).
isEqualTo
(
13000
);
assertThat
(
window
.
defaultPositionUs
).
isEqualTo
(
4000000
);
}
@Test
public
void
loadPlaylist_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.
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-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 the current time.
SystemClock
.
setCurrentTimeMillis
(
Util
.
parseXsDateTime
(
"2020-01-01T00:00:05.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
.
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
).
isEqualTo
(
4000
);
assertThat
(
window
.
defaultPositionUs
).
isEqualTo
(
0
);
}
@Test
public
void
loadPlaylist_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.
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=3"
;
// The playlist finishes 1 second before the the current time. This should not affect the target
// live offset set in the media item.
SystemClock
.
setCurrentTimeMillis
(
Util
.
parseXsDateTime
(
"2020-01-01T00:00:05.0+00:00"
));
HlsMediaSource
.
Factory
factory
=
createHlsMediaSourceFactory
(
playlistUri
,
playlist
);
MediaItem
mediaItem
=
new
MediaItem
.
Builder
().
setUri
(
playlistUri
).
setLiveTargetOffsetMs
(
1000
).
build
();
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 the media item and not adjusted.
assertThat
(
window
.
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
).
isEqualTo
(
1000
);
assertThat
(
window
.
defaultPositionUs
).
isEqualTo
(
0
);
}
@Test
public
void
loadPlaylist_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
=
"#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"
+
"#EXTINF:4.00000,\n"
+
"fileSequence1.ts\n"
+
"#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24"
;
// The playlist finishes 1 second before the live edge, therefore the live window duration is
// 9 seconds (8 + 1).
SystemClock
.
setCurrentTimeMillis
(
Util
.
parseXsDateTime
(
"2020-01-01T00:00:09.0+00:00"
));
HlsMediaSource
.
Factory
factory
=
createHlsMediaSourceFactory
(
playlistUri
,
playlist
);
MediaItem
mediaItem
=
new
MediaItem
.
Builder
().
setUri
(
playlistUri
).
setLiveTargetOffsetMs
(
20_000
).
build
();
HlsMediaSource
mediaSource
=
factory
.
createMediaSource
(
mediaItem
);
Timeline
timeline
=
prepareAndWaitForTimeline
(
mediaSource
);
Timeline
.
Window
window
=
timeline
.
getWindow
(
0
,
new
Timeline
.
Window
());
assertThat
(
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
)
.
isGreaterThan
(
C
.
usToMs
(
window
.
durationUs
));
assertThat
(
window
.
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
).
isEqualTo
(
9000
);
}
@Test
public
void
loadPlaylist_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.
String
playlist
=
"#EXTM3U\n"
+
"#EXT-X-TARGETDURATION:4\n"
+
"#EXT-X-VERSION:3\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"
;
// The playlist finishes 8 seconds before the current time.
SystemClock
.
setCurrentTimeMillis
(
20000
);
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
.
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
).
isEqualTo
(
12000
);
assertThat
(
window
.
defaultPositionUs
).
isEqualTo
(
4000000
);
}
@Test
public
void
refreshPlaylist_targetLiveOffsetRemainsInWindow
()
throws
TimeoutException
,
IOException
{
String
playlistUri1
=
"fake://foo.bar/media0/playlist1.m3u8"
;
// The playlist has a duration of 16 seconds and a hold back of 12 seconds.
String
playlist1
=
"#EXTM3U\n"
+
"#EXT-X-TARGETDURATION:4\n"
+
"#EXT-X-VERSION:3\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"
;
// The second playlist defines a different hold back.
String
playlistUri2
=
"fake://foo.bar/media0/playlist2.m3u8"
;
String
playlist2
=
"#EXTM3U\n"
+
"#EXT-X-TARGETDURATION:4\n"
+
"#EXT-X-VERSION:3\n"
+
"#EXT-X-MEDIA-SEQUENCE:4\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence4.ts\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence5.ts\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence6.ts\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence7.ts\n"
+
"#EXT-X-SERVER-CONTROL:HOLD-BACK:14"
;
// The third playlist has a duration of 8 seconds.
String
playlistUri3
=
"fake://foo.bar/media0/playlist3.m3u8"
;
String
playlist3
=
"#EXTM3U\n"
+
"#EXT-X-TARGETDURATION:4\n"
+
"#EXT-X-VERSION:3\n"
+
"#EXT-X-MEDIA-SEQUENCE:4\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence8.ts\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence9.ts\n"
+
"#EXTINF:4.00000,\n"
+
"#EXT-X-SERVER-CONTROL:HOLD-BACK:12"
;
// The third playlist has a duration of 16 seconds but the target live offset should remain at
// 8 seconds.
String
playlistUri4
=
"fake://foo.bar/media0/playlist4.m3u8"
;
String
playlist4
=
"#EXTM3U\n"
+
"#EXT-X-TARGETDURATION:4\n"
+
"#EXT-X-VERSION:3\n"
+
"#EXT-X-MEDIA-SEQUENCE:4\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence10.ts\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence11.ts\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence12.ts\n"
+
"#EXTINF:4.00000,\n"
+
"fileSequence13.ts\n"
+
"#EXTINF:4.00000,\n"
+
"#EXT-X-SERVER-CONTROL:HOLD-BACK:12"
;
HlsMediaSource
.
Factory
factory
=
createHlsMediaSourceFactory
(
playlistUri1
,
playlist1
);
MediaItem
mediaItem
=
new
MediaItem
.
Builder
().
setUri
(
playlistUri1
).
build
();
HlsMediaSource
mediaSource
=
factory
.
createMediaSource
(
mediaItem
);
HlsMediaPlaylist
secondPlaylist
=
parseHlsMediaPlaylist
(
playlistUri2
,
playlist2
);
HlsMediaPlaylist
thirdPlaylist
=
parseHlsMediaPlaylist
(
playlistUri3
,
playlist3
);
HlsMediaPlaylist
fourthPlaylist
=
parseHlsMediaPlaylist
(
playlistUri4
,
playlist4
);
List
<
Timeline
>
timelines
=
new
ArrayList
<>();
MediaSource
.
MediaSourceCaller
mediaSourceCaller
=
(
source
,
timeline
)
->
timelines
.
add
(
timeline
);
mediaSource
.
prepareSource
(
mediaSourceCaller
,
null
);
runMainLooperUntil
(()
->
timelines
.
size
()
==
1
);
mediaSource
.
onPrimaryPlaylistRefreshed
(
secondPlaylist
);
runMainLooperUntil
(()
->
timelines
.
size
()
==
2
);
mediaSource
.
onPrimaryPlaylistRefreshed
(
thirdPlaylist
);
runMainLooperUntil
(()
->
timelines
.
size
()
==
3
);
mediaSource
.
onPrimaryPlaylistRefreshed
(
fourthPlaylist
);
runMainLooperUntil
(()
->
timelines
.
size
()
==
4
);
Timeline
.
Window
window
=
new
Timeline
.
Window
();
assertThat
(
timelines
.
get
(
0
).
getWindow
(
0
,
window
).
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
)
.
isEqualTo
(
12000
);
assertThat
(
timelines
.
get
(
1
).
getWindow
(
0
,
window
).
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
)
.
isEqualTo
(
12000
);
assertThat
(
timelines
.
get
(
2
).
getWindow
(
0
,
window
).
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
)
.
isEqualTo
(
8000
);
assertThat
(
timelines
.
get
(
3
).
getWindow
(
0
,
window
).
mediaItem
.
liveConfiguration
.
targetLiveOffsetMs
)
.
isEqualTo
(
8000
);
}
private
static
HlsMediaSource
.
Factory
createHlsMediaSourceFactory
(
String
playlistUri
,
String
playlist
)
{
FakeDataSet
fakeDataSet
=
new
FakeDataSet
().
setData
(
playlistUri
,
Util
.
getUtf8Bytes
(
playlist
));
return
new
HlsMediaSource
.
Factory
(
dataType
->
new
FakeDataSource
.
Factory
().
setFakeDataSet
(
fakeDataSet
).
createDataSource
())
.
setElapsedRealTimeOffsetMs
(
0
);
}
/** Prepares the media source and waits until the timeline is updated. */
private
static
Timeline
prepareAndWaitForTimeline
(
HlsMediaSource
mediaSource
)
throws
TimeoutException
{
AtomicReference
<
Timeline
>
receivedTimeline
=
new
AtomicReference
<>();
mediaSource
.
prepareSource
(
(
source
,
timeline
)
->
receivedTimeline
.
set
(
timeline
),
/* mediaTransferListener= */
null
);
runMainLooperUntil
(()
->
receivedTimeline
.
get
()
!=
null
);
return
receivedTimeline
.
get
();
}
private
static
HlsMediaPlaylist
parseHlsMediaPlaylist
(
String
playlistUri
,
String
playlist
)
throws
IOException
{
return
(
HlsMediaPlaylist
)
new
HlsPlaylistParser
()
.
parse
(
Uri
.
parse
(
playlistUri
),
new
ByteArrayInputStream
(
Util
.
getUtf8Bytes
(
playlist
)));
}
}
}
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