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
c04dd8b3
authored
Nov 03, 2020
by
bachinger
Committed by
Andrew Lewis
Nov 06, 2020
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Use blocking HLS media playlist reload for segments
Issue: #5011 PiperOrigin-RevId: 340477795
parent
5fd1601f
Hide whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
319 additions
and
169 deletions
library/hls/build.gradle
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTrackerTest.java
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_next
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_skip_dateranges
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_skip_skipped
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_skip_skipped_media_sequence_no_overlapping
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_skip_until
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_skip_until_and_block_reload
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_skip_until_and_block_reload_next
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_skip_until_and_block_reload_next_skipped
library/hls/build.gradle
View file @
c04dd8b3
...
...
@@ -32,6 +32,7 @@ dependencies {
testImplementation
project
(
modulePrefix
+
'robolectricutils'
)
testImplementation
project
(
modulePrefix
+
'testutils'
)
testImplementation
project
(
modulePrefix
+
'testdata'
)
testImplementation
'com.squareup.okhttp3:mockwebserver:'
+
mockWebServerVersion
testImplementation
'org.robolectric:robolectric:'
+
robolectricVersion
}
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java
View file @
c04dd8b3
...
...
@@ -16,6 +16,7 @@
package
com
.
google
.
android
.
exoplayer2
.
source
.
hls
.
playlist
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Util
.
castNonNull
;
import
static
java
.
lang
.
Math
.
max
;
import
android.net.Uri
;
...
...
@@ -31,6 +32,7 @@ import com.google.android.exoplayer2.source.hls.HlsDataSourceFactory;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist.Variant
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment
;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
com.google.android.exoplayer2.upstream.HttpDataSource
;
import
com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy
;
import
com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy.LoadErrorInfo
;
import
com.google.android.exoplayer2.upstream.Loader
;
...
...
@@ -216,7 +218,7 @@ public final class DefaultHlsPlaylistTracker
@Override
public
void
refreshPlaylist
(
Uri
url
)
{
playlistBundles
.
get
(
url
).
loadPlaylist
();
playlistBundles
.
get
(
url
).
loadPlaylist
(
url
);
}
@Override
...
...
@@ -241,7 +243,6 @@ public final class DefaultHlsPlaylistTracker
mediaPlaylistParser
=
playlistParserFactory
.
createPlaylistParser
(
masterPlaylist
);
primaryMediaPlaylistUrl
=
masterPlaylist
.
variants
.
get
(
0
).
url
;
createBundles
(
masterPlaylist
.
mediaPlaylistUrls
);
MediaPlaylistBundle
primaryBundle
=
playlistBundles
.
get
(
primaryMediaPlaylistUrl
);
LoadEventInfo
loadEventInfo
=
new
LoadEventInfo
(
loadable
.
loadTaskId
,
...
...
@@ -251,11 +252,12 @@ public final class DefaultHlsPlaylistTracker
elapsedRealtimeMs
,
loadDurationMs
,
loadable
.
bytesLoaded
());
MediaPlaylistBundle
primaryBundle
=
playlistBundles
.
get
(
primaryMediaPlaylistUrl
);
if
(
isMediaPlaylist
)
{
// We don't need to load the playlist again. We can use the same result.
primaryBundle
.
processLoadedPlaylist
((
HlsMediaPlaylist
)
result
,
loadEventInfo
);
}
else
{
primaryBundle
.
loadPlaylist
();
primaryBundle
.
loadPlaylist
(
primaryMediaPlaylistUrl
);
}
loadErrorHandlingPolicy
.
onLoadTaskConcluded
(
loadable
.
loadTaskId
);
eventDispatcher
.
loadCompleted
(
loadEventInfo
,
C
.
DATA_TYPE_MANIFEST
);
...
...
@@ -320,7 +322,7 @@ public final class DefaultHlsPlaylistTracker
MediaPlaylistBundle
bundle
=
playlistBundles
.
get
(
variants
.
get
(
i
).
url
);
if
(
currentTimeMs
>
bundle
.
excludeUntilMs
)
{
primaryMediaPlaylistUrl
=
bundle
.
playlistUrl
;
bundle
.
loadPlaylist
();
bundle
.
loadPlaylist
(
primaryMediaPlaylistUrl
);
return
true
;
}
}
...
...
@@ -336,7 +338,7 @@ public final class DefaultHlsPlaylistTracker
return
;
}
primaryMediaPlaylistUrl
=
url
;
playlistBundles
.
get
(
primaryMediaPlaylistUrl
).
loadPlaylist
();
playlistBundles
.
get
(
primaryMediaPlaylistUrl
).
loadPlaylist
(
url
);
}
/** Returns whether any of the variants in the master playlist have the specified playlist URL. */
...
...
@@ -460,8 +462,10 @@ public final class DefaultHlsPlaylistTracker
}
/** Holds all information related to a specific Media Playlist. */
private
final
class
MediaPlaylistBundle
implements
Loader
.
Callback
<
ParsingLoadable
<
HlsPlaylist
>>,
Runnable
{
private
final
class
MediaPlaylistBundle
implements
Loader
.
Callback
<
ParsingLoadable
<
HlsPlaylist
>>
{
private
static
final
String
BLOCK_MSN_PARAM
=
"_HLS_msn"
;
private
static
final
String
SKIP_PARAM
=
"_HLS_skip"
;
private
final
Uri
playlistUrl
;
private
final
Loader
mediaPlaylistLoader
;
...
...
@@ -502,7 +506,12 @@ public final class DefaultHlsPlaylistTracker
mediaPlaylistLoader
.
release
();
}
public
void
loadPlaylist
()
{
/**
* Loads the playlist.
*
* @param requestUri The URI to be used for loading the playlist.
*/
public
void
loadPlaylist
(
Uri
requestUri
)
{
excludeUntilMs
=
0
;
if
(
loadPending
||
mediaPlaylistLoader
.
isLoading
()
||
mediaPlaylistLoader
.
hasFatalError
())
{
// Load already pending, in progress, or a fatal error has been encountered. Do nothing.
...
...
@@ -511,9 +520,14 @@ public final class DefaultHlsPlaylistTracker
long
currentTimeMs
=
SystemClock
.
elapsedRealtime
();
if
(
currentTimeMs
<
earliestNextLoadTimeMs
)
{
loadPending
=
true
;
playlistRefreshHandler
.
postDelayed
(
this
,
earliestNextLoadTimeMs
-
currentTimeMs
);
playlistRefreshHandler
.
postDelayed
(
()
->
{
loadPending
=
false
;
loadPlaylistImmediately
(
requestUri
);
},
earliestNextLoadTimeMs
-
currentTimeMs
);
}
else
{
loadPlaylistImmediately
();
loadPlaylistImmediately
(
requestUri
);
}
}
...
...
@@ -585,6 +599,19 @@ public final class DefaultHlsPlaylistTracker
elapsedRealtimeMs
,
loadDurationMs
,
loadable
.
bytesLoaded
());
boolean
isBlockingRequest
=
loadable
.
getUri
().
getQueryParameter
(
BLOCK_MSN_PARAM
)
!=
null
;
if
(
isBlockingRequest
&&
error
instanceof
HttpDataSource
.
InvalidResponseCodeException
)
{
int
responseCode
=
((
HttpDataSource
.
InvalidResponseCodeException
)
error
).
responseCode
;
if
(
responseCode
==
400
||
responseCode
==
503
)
{
// Intercept bad request and service unavailable to force a full, non-blocking request
// (see RFC 8216, section 6.2.5.2).
earliestNextLoadTimeMs
=
SystemClock
.
elapsedRealtime
();
loadPlaylist
(
/* requestUri= */
playlistUrl
);
castNonNull
(
eventDispatcher
)
.
loadError
(
loadEventInfo
,
loadable
.
type
,
error
,
/* wasCanceled= */
true
);
return
Loader
.
DONT_RETRY
;
}
}
MediaLoadData
mediaLoadData
=
new
MediaLoadData
(
loadable
.
type
);
LoadErrorInfo
loadErrorInfo
=
new
LoadErrorInfo
(
loadEventInfo
,
mediaLoadData
,
error
,
errorCount
);
...
...
@@ -616,21 +643,13 @@ public final class DefaultHlsPlaylistTracker
return
loadErrorAction
;
}
// Runnable implementation.
@Override
public
void
run
()
{
loadPending
=
false
;
loadPlaylistImmediately
();
}
// Internal methods.
private
void
loadPlaylistImmediately
()
{
private
void
loadPlaylistImmediately
(
Uri
playlistRequestUri
)
{
ParsingLoadable
<
HlsPlaylist
>
mediaPlaylistLoadable
=
new
ParsingLoadable
<>(
mediaPlaylistDataSource
,
getMediaPlaylistUriForRequest
(
playlistUrl
,
playlistSnapshot
)
,
playlistRequestUri
,
C
.
DATA_TYPE_MANIFEST
,
mediaPlaylistParser
);
long
elapsedRealtime
=
...
...
@@ -685,31 +704,42 @@ public final class DefaultHlsPlaylistTracker
}
}
}
// Do not allow the playlist to load again within the target duration if we obtained a new
// snapshot, or half the target duration otherwise.
earliestNextLoadTimeMs
=
currentTimeMs
+
C
.
usToMs
(
playlistSnapshot
!=
oldPlaylist
?
playlistSnapshot
.
targetDurationUs
:
(
playlistSnapshot
.
targetDurationUs
/
2
));
long
durationUntilNextLoadUs
=
0L
;
if
(!
playlistSnapshot
.
serverControl
.
canBlockReload
)
{
// If blocking requests are not supported, do not allow the playlist to load again within
// the target duration if we obtained a new snapshot, or half the target duration otherwise.
durationUntilNextLoadUs
=
playlistSnapshot
!=
oldPlaylist
?
playlistSnapshot
.
targetDurationUs
:
(
playlistSnapshot
.
targetDurationUs
/
2
);
}
earliestNextLoadTimeMs
=
currentTimeMs
+
C
.
usToMs
(
durationUntilNextLoadUs
);
// Schedule a load if this is the primary playlist and it doesn't have an end tag. Else the
// next load will be scheduled when refreshPlaylist is called, or when this playlist becomes
// the primary.
if
(
playlistUrl
.
equals
(
primaryMediaPlaylistUrl
)
&&
!
playlistSnapshot
.
hasEndTag
)
{
loadPlaylist
();
loadPlaylist
(
getMediaPlaylistUriForReload
()
);
}
}
private
Uri
getMediaPlaylistUriForRequest
(
Uri
playlistUri
,
@Nullable
HlsMediaPlaylist
currentMediaPlaylist
)
{
if
(
currentMediaPlaylist
==
null
||
currentMediaPlaylist
.
serverControl
.
skipUntilUs
==
C
.
TIME_UNSET
)
{
return
playlistUri
;
private
Uri
getMediaPlaylistUriForReload
()
{
if
(
playlistSnapshot
==
null
||
(
playlistSnapshot
.
serverControl
.
skipUntilUs
==
C
.
TIME_UNSET
&&
!
playlistSnapshot
.
serverControl
.
canBlockReload
))
{
return
playlistUrl
;
}
Uri
.
Builder
uriBuilder
=
playlistUrl
.
buildUpon
();
if
(
playlistSnapshot
.
serverControl
.
skipUntilUs
!=
C
.
TIME_UNSET
)
{
uriBuilder
.
appendQueryParameter
(
SKIP_PARAM
,
playlistSnapshot
.
serverControl
.
canSkipDateRanges
?
"v2"
:
"YES"
);
}
if
(
playlistSnapshot
.
serverControl
.
canBlockReload
)
{
long
reloadMediaSequence
=
playlistSnapshot
.
mediaSequence
+
playlistSnapshot
.
segments
.
size
()
+
playlistSnapshot
.
skippedSegmentCount
;
uriBuilder
.
appendQueryParameter
(
BLOCK_MSN_PARAM
,
String
.
valueOf
(
reloadMediaSequence
));
}
Uri
.
Builder
uriBuilder
=
playlistUri
.
buildUpon
();
uriBuilder
.
appendQueryParameter
(
"_HLS_skip"
,
currentMediaPlaylist
.
serverControl
.
canSkipDateRanges
?
"v2"
:
"YES"
);
return
uriBuilder
.
build
();
}
...
...
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTrackerTest.java
View file @
c04dd8b3
...
...
@@ -15,32 +15,28 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
source
.
hls
.
playlist
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkArgument
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkState
;
import
static
com
.
google
.
common
.
truth
.
Truth
.
assertThat
;
import
android.net.Uri
;
import
androidx.annotation.Nullable
;
import
androidx.test.core.app.ApplicationProvider
;
import
androidx.test.ext.junit.runners.AndroidJUnit4
;
import
com.google.android.exoplayer2.robolectric.RobolectricUtil
;
import
com.google.android.exoplayer2.source.MediaSourceEventListener
;
import
com.google.android.exoplayer2.testutil.FakeDataSet
;
import
com.google.android.exoplayer2.testutil.FakeDataSource
;
import
com.google.android.exoplayer2.testutil.TestUtil
;
import
com.google.android.exoplayer2.upstream.ByteArrayDataSource
;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
com.google.android.exoplayer2.upstream.D
ataSpec
;
import
com.google.android.exoplayer2.upstream.D
efaultHttpDataSourceFactory
;
import
com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy
;
import
com.google.android.exoplayer2.upstream.TransferListener
;
import
java.io.IOException
;
import
java.util.ArrayDeque
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.Queue
;
import
java.util.concurrent.TimeoutException
;
import
java.util.concurrent.atomic.AtomicInteger
;
import
okhttp3.HttpUrl
;
import
okhttp3.mockwebserver.MockResponse
;
import
okhttp3.mockwebserver.MockWebServer
;
import
okio.Buffer
;
import
org.junit.After
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.junit.runner.RunWith
;
...
...
@@ -64,24 +60,51 @@ public class DefaultHlsPlaylistTrackerTest {
"media/m3u8/live_low_latency_media_can_not_skip"
;
private
static
final
String
SAMPLE_M3U8_LIVE_MEDIA_CAN_NOT_SKIP_NEXT
=
"media/m3u8/live_low_latency_media_can_not_skip_next"
;
private
static
final
String
SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD
=
"media/m3u8/live_low_latency_media_can_block_reload"
;
private
static
final
String
SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_NEXT
=
"media/m3u8/live_low_latency_media_can_block_reload_next"
;
private
static
final
String
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_AND_BLOCK_RELOAD
=
"media/m3u8/live_low_latency_media_can_skip_until_and_block_reload"
;
private
static
final
String
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_AND_BLOCK_RELOAD_NEXT
=
"media/m3u8/live_low_latency_media_can_skip_until_and_block_reload_next"
;
private
static
final
String
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_AND_BLOCK_RELOAD_NEXT_SKIPPED
=
"media/m3u8/live_low_latency_media_can_skip_until_and_block_reload_next_skipped"
;
private
MockWebServer
mockWebServer
;
private
int
enqueueCounter
;
private
int
assertedRequestCounter
;
@Before
public
void
setUp
()
{
mockWebServer
=
new
MockWebServer
();
enqueueCounter
=
0
;
assertedRequestCounter
=
0
;
}
@Test
public
void
start_playlistCanNotSkip_requestsFullUpdate
()
throws
IOException
,
TimeoutException
{
@After
public
void
tearDown
()
throws
IOException
{
assertThat
(
assertedRequestCounter
).
isEqualTo
(
enqueueCounter
);
mockWebServer
.
shutdown
();
}
Uri
masterPlaylistUri
=
Uri
.
parse
(
"fake://foo.bar/master.m3u8"
);
Queue
<
DataSource
>
dataSourceQueue
=
new
ArrayDeque
<>();
dataSourceQueue
.
add
(
new
ByteArrayDataSource
(
getBytes
(
SAMPLE_M3U8_LIVE_MASTER
)));
dataSourceQueue
.
add
(
new
DataSourceList
(
new
ByteArrayDataSource
(
getBytes
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_NOT_SKIP
)),
new
ByteArrayDataSource
(
getBytes
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_NOT_SKIP_NEXT
))));
@Test
public
void
start_playlistCanNotSkip_requestsFullUpdate
()
throws
IOException
,
TimeoutException
,
InterruptedException
{
List
<
HttpUrl
>
httpUrls
=
enqueueWebServerResponses
(
new
String
[]
{
"master.m3u8"
,
"/media0/playlist.m3u8"
,
"/media0/playlist.m3u8"
},
getMockResponse
(
SAMPLE_M3U8_LIVE_MASTER
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_NOT_SKIP
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_NOT_SKIP_NEXT
));
List
<
HlsMediaPlaylist
>
mediaPlaylists
=
runPlaylistTrackerAndCollectMediaPlaylists
(
/* dataSourceFactory= */
dataSourceQueue:
:
remove
,
masterPlaylistUri
,
new
DefaultHttpDataSourceFactory
()
,
Uri
.
parse
(
mockWebServer
.
url
(
"/master.m3u8"
).
toString
())
,
/* awaitedMediaPlaylistCount= */
2
);
assertRequestUrlsCalled
(
httpUrls
);
HlsMediaPlaylist
firstFullPlaylist
=
mediaPlaylists
.
get
(
0
);
assertThat
(
firstFullPlaylist
.
mediaSequence
).
isEqualTo
(
10
);
assertThat
(
firstFullPlaylist
.
segments
.
get
(
0
).
url
).
isEqualTo
(
"fileSequence10.ts"
);
...
...
@@ -98,22 +121,23 @@ public class DefaultHlsPlaylistTrackerTest {
@Test
public
void
start_playlistCanSkip_requestsDeltaUpdateAndExpandsSkippedSegments
()
throws
IOException
,
TimeoutException
{
Uri
masterPlaylistUri
=
Uri
.
parse
(
"fake://foo.bar/master.m3u8"
);
Uri
mediaPlaylistUri
=
Uri
.
parse
(
"fake://foo.bar/media0/playlist.m3u8"
);
Uri
mediaPlaylistSkippedUri
=
Uri
.
parse
(
mediaPlaylistUri
+
"?_HLS_skip=YES"
);
FakeDataSet
fakeDataSet
=
new
FakeDataSet
()
.
setData
(
masterPlaylistUri
,
getBytes
(
SAMPLE_M3U8_LIVE_MASTER
))
.
setData
(
mediaPlaylistUri
,
getBytes
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL
))
.
setData
(
mediaPlaylistSkippedUri
,
getBytes
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED
));
throws
IOException
,
TimeoutException
,
InterruptedException
{
List
<
HttpUrl
>
httpUrls
=
enqueueWebServerResponses
(
new
String
[]
{
"/master.m3u8"
,
"/media0/playlist.m3u8"
,
"/media0/playlist.m3u8?_HLS_skip=YES"
},
getMockResponse
(
SAMPLE_M3U8_LIVE_MASTER
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED
));
List
<
HlsMediaPlaylist
>
mediaPlaylists
=
runPlaylistTrackerAndCollectMediaPlaylists
(
new
FakeDataSource
.
Factory
().
setFakeDataSet
(
fakeDataSet
),
masterPlaylistUri
,
new
DefaultHttpDataSourceFactory
(
),
Uri
.
parse
(
mockWebServer
.
url
(
"/master.m3u8"
).
toString
())
,
/* awaitedMediaPlaylistCount= */
2
);
assertRequestUrlsCalled
(
httpUrls
);
HlsMediaPlaylist
initialPlaylistWithAllSegments
=
mediaPlaylists
.
get
(
0
);
assertThat
(
initialPlaylistWithAllSegments
.
mediaSequence
).
isEqualTo
(
10
);
assertThat
(
initialPlaylistWithAllSegments
.
segments
).
hasSize
(
6
);
...
...
@@ -131,24 +155,23 @@ public class DefaultHlsPlaylistTrackerTest {
@Test
public
void
start_playlistCanSkip_missingSegments_correctedMediaSequence
()
throws
IOException
,
TimeoutException
{
Uri
masterPlaylistUri
=
Uri
.
parse
(
"fake://foo.bar/master.m3u8"
);
Uri
mediaPlaylistUri
=
Uri
.
parse
(
"fake://foo.bar/media0/playlist.m3u8"
);
Uri
mediaPlaylistSkippedUri
=
Uri
.
parse
(
mediaPlaylistUri
+
"?_HLS_skip=YES"
);
FakeDataSet
fakeDataSet
=
new
FakeDataSet
()
.
setData
(
masterPlaylistUri
,
getBytes
(
SAMPLE_M3U8_LIVE_MASTER
))
.
setData
(
mediaPlaylistUri
,
getBytes
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL
))
.
setData
(
mediaPlaylistSkippedUri
,
getBytes
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED_MEDIA_SEQUENCE_NO_OVERLAPPING
));
throws
IOException
,
TimeoutException
,
InterruptedException
{
List
<
HttpUrl
>
httpUrls
=
enqueueWebServerResponses
(
new
String
[]
{
"/master.m3u8"
,
"/media0/playlist.m3u8"
,
"/media0/playlist.m3u8?_HLS_skip=YES"
},
getMockResponse
(
SAMPLE_M3U8_LIVE_MASTER
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED_MEDIA_SEQUENCE_NO_OVERLAPPING
));
List
<
HlsMediaPlaylist
>
mediaPlaylists
=
runPlaylistTrackerAndCollectMediaPlaylists
(
new
FakeDataSource
.
Factory
().
setFakeDataSet
(
fakeDataSet
),
masterPlaylistUri
,
new
DefaultHttpDataSourceFactory
(
),
Uri
.
parse
(
mockWebServer
.
url
(
"/master.m3u8"
).
toString
())
,
/* awaitedMediaPlaylistCount= */
2
);
assertRequestUrlsCalled
(
httpUrls
);
HlsMediaPlaylist
initialPlaylistWithAllSegments
=
mediaPlaylists
.
get
(
0
);
assertThat
(
initialPlaylistWithAllSegments
.
mediaSequence
).
isEqualTo
(
10
);
assertThat
(
initialPlaylistWithAllSegments
.
segments
).
hasSize
(
6
);
...
...
@@ -160,23 +183,23 @@ public class DefaultHlsPlaylistTrackerTest {
@Test
public
void
start_playlistCanSkipDataRanges_requestsDeltaUpdateV2
()
throws
IOException
,
TimeoutException
{
Uri
masterPlaylistUri
=
Uri
.
parse
(
"fake://foo.bar/master.m3u8"
);
Uri
mediaPlaylistUri
=
Uri
.
parse
(
"fake://foo.bar/media0/playlist.m3u8"
);
// Expect _HLS_skip parameter with value v2.
Uri
mediaPlaylistSkippedUri
=
Uri
.
parse
(
mediaPlaylistUri
+
"?_HLS_skip=v2"
);
FakeDataSet
fakeDataSet
=
new
FakeDataSet
()
.
setData
(
masterPlaylistUri
,
getBytes
(
SAMPLE_M3U8_LIVE_MASTER
))
.
setData
(
mediaPlaylistUri
,
getBytes
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_DATERANGES
))
.
setData
(
mediaPlaylistSkippedUri
,
getBytes
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED
));
throws
IOException
,
TimeoutException
,
InterruptedException
{
List
<
HttpUrl
>
httpUrls
=
enqueueWebServerResponses
(
new
String
[]
{
"/master.m3u8"
,
"/media0/playlist.m3u8"
,
"/media0/playlist.m3u8?_HLS_skip=v2"
},
getMockResponse
(
SAMPLE_M3U8_LIVE_MASTER
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_DATERANGES
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED
));
List
<
HlsMediaPlaylist
>
mediaPlaylists
=
runPlaylistTrackerAndCollectMediaPlaylists
(
new
FakeDataSource
.
Factory
().
setFakeDataSet
(
fakeDataSet
),
masterPlaylistUri
,
new
DefaultHttpDataSourceFactory
(
),
Uri
.
parse
(
mockWebServer
.
url
(
"/master.m3u8"
).
toString
())
,
/* awaitedMediaPlaylistCount= */
2
);
assertRequestUrlsCalled
(
httpUrls
);
// Finding the media sequence of the second playlist request asserts that the second request has
// been made with the correct uri parameter appended.
assertThat
(
mediaPlaylists
.
get
(
1
).
mediaSequence
).
isEqualTo
(
11
);
...
...
@@ -184,29 +207,104 @@ public class DefaultHlsPlaylistTrackerTest {
@Test
public
void
start_playlistCanSkipAndUriWithParams_preservesOriginalParams
()
throws
IOException
,
TimeoutException
{
Uri
masterPlaylistUri
=
Uri
.
parse
(
"fake://foo.bar/master.m3u8"
);
Uri
mediaPlaylistUri
=
Uri
.
parse
(
"fake://foo.bar/media0/playlist.m3u8?param1=1¶m2=2"
);
// Expect _HLS_skip parameter appended with an ampersand.
Uri
mediaPlaylistSkippedUri
=
Uri
.
parse
(
mediaPlaylistUri
+
"&_HLS_skip=YES"
);
FakeDataSet
fakeDataSet
=
new
FakeDataSet
()
.
setData
(
masterPlaylistUri
,
getBytes
(
SAMPLE_M3U8_LIVE_MASTER_MEDIA_URI_WITH_PARAM
))
.
setData
(
mediaPlaylistUri
,
getBytes
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL
))
.
setData
(
mediaPlaylistSkippedUri
,
getBytes
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED
));
throws
IOException
,
TimeoutException
,
InterruptedException
{
List
<
HttpUrl
>
httpUrls
=
enqueueWebServerResponses
(
new
String
[]
{
"/master.m3u8"
,
"/media0/playlist.m3u8?param1=1¶m2=2"
,
"/media0/playlist.m3u8?param1=1¶m2=2&_HLS_skip=YES"
},
getMockResponse
(
SAMPLE_M3U8_LIVE_MASTER_MEDIA_URI_WITH_PARAM
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED
));
List
<
HlsMediaPlaylist
>
mediaPlaylists
=
runPlaylistTrackerAndCollectMediaPlaylists
(
new
FakeDataSource
.
Factory
().
setFakeDataSet
(
fakeDataSet
),
masterPlaylistUri
,
new
DefaultHttpDataSourceFactory
(
),
Uri
.
parse
(
mockWebServer
.
url
(
"/master.m3u8"
).
toString
())
,
/* awaitedMediaPlaylistCount= */
2
);
assertRequestUrlsCalled
(
httpUrls
);
// Finding the media sequence of the second playlist request asserts that the second request has
// been made with the original uri parameters preserved and the additional param concatenated
// correctly.
assertThat
(
mediaPlaylists
.
get
(
1
).
mediaSequence
).
isEqualTo
(
11
);
}
@Test
public
void
start_playlistCanBlockReload_requestBlockingReloadWithCorrectMediaSequence
()
throws
IOException
,
TimeoutException
,
InterruptedException
{
List
<
HttpUrl
>
httpUrls
=
enqueueWebServerResponses
(
new
String
[]
{
"/master.m3u8"
,
"/media0/playlist.m3u8"
,
"/media0/playlist.m3u8?_HLS_msn=14"
},
getMockResponse
(
SAMPLE_M3U8_LIVE_MASTER
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_BLOCK_RELOAD_NEXT
));
List
<
HlsMediaPlaylist
>
mediaPlaylists
=
runPlaylistTrackerAndCollectMediaPlaylists
(
new
DefaultHttpDataSourceFactory
(),
Uri
.
parse
(
mockWebServer
.
url
(
"/master.m3u8"
).
toString
()),
/* awaitedMediaPlaylistCount= */
2
);
assertRequestUrlsCalled
(
httpUrls
);
assertThat
(
mediaPlaylists
.
get
(
0
).
mediaSequence
).
isEqualTo
(
10
);
assertThat
(
mediaPlaylists
.
get
(
1
).
mediaSequence
).
isEqualTo
(
11
);
}
@Test
public
void
start_httpBadRequest_forcesFullNonBlockingPlaylistRequest
()
throws
IOException
,
TimeoutException
,
InterruptedException
{
List
<
HttpUrl
>
httpUrls
=
enqueueWebServerResponses
(
new
String
[]
{
"/master.m3u8"
,
"/media0/playlist.m3u8"
,
"/media0/playlist.m3u8?_HLS_skip=YES&_HLS_msn=16"
,
"/media0/playlist.m3u8"
,
"/media0/playlist.m3u8?_HLS_skip=YES&_HLS_msn=17"
},
getMockResponse
(
SAMPLE_M3U8_LIVE_MASTER
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_AND_BLOCK_RELOAD
),
new
MockResponse
().
setResponseCode
(
400
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_AND_BLOCK_RELOAD_NEXT
),
getMockResponse
(
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL_AND_BLOCK_RELOAD_NEXT_SKIPPED
));
List
<
HlsMediaPlaylist
>
mediaPlaylists
=
runPlaylistTrackerAndCollectMediaPlaylists
(
/* dataSourceFactory= */
new
DefaultHttpDataSourceFactory
(),
Uri
.
parse
(
mockWebServer
.
url
(
"/master.m3u8"
).
toString
()),
/* awaitedMediaPlaylistCount= */
3
);
assertRequestUrlsCalled
(
httpUrls
);
assertThat
(
mediaPlaylists
.
get
(
0
).
mediaSequence
).
isEqualTo
(
10
);
assertThat
(
mediaPlaylists
.
get
(
1
).
mediaSequence
).
isEqualTo
(
11
);
assertThat
(
mediaPlaylists
.
get
(
2
).
mediaSequence
).
isEqualTo
(
12
);
}
private
List
<
HttpUrl
>
enqueueWebServerResponses
(
String
[]
paths
,
MockResponse
...
mockResponses
)
{
assertThat
(
paths
).
hasLength
(
mockResponses
.
length
);
for
(
MockResponse
mockResponse
:
mockResponses
)
{
enqueueCounter
++;
mockWebServer
.
enqueue
(
mockResponse
);
}
List
<
HttpUrl
>
urls
=
new
ArrayList
<>();
for
(
String
path
:
paths
)
{
urls
.
add
(
mockWebServer
.
url
(
path
));
}
return
urls
;
}
private
void
assertRequestUrlsCalled
(
List
<
HttpUrl
>
httpUrls
)
throws
InterruptedException
{
for
(
HttpUrl
url
:
httpUrls
)
{
assertedRequestCounter
++;
assertThat
(
url
.
toString
()).
endsWith
(
mockWebServer
.
takeRequest
().
getPath
());
}
}
private
static
List
<
HlsMediaPlaylist
>
runPlaylistTrackerAndCollectMediaPlaylists
(
DataSource
.
Factory
dataSourceFactory
,
Uri
masterPlaylistUri
,
int
awaitedMediaPlaylistCount
)
throws
TimeoutException
{
...
...
@@ -227,70 +325,17 @@ public class DefaultHlsPlaylistTrackerTest {
playlistCounter
.
addAndGet
(
1
);
});
RobolectricUtil
.
runMainLooperUntil
(()
->
playlistCounter
.
get
()
=
=
awaitedMediaPlaylistCount
);
RobolectricUtil
.
runMainLooperUntil
(()
->
playlistCounter
.
get
()
>
=
awaitedMediaPlaylistCount
);
defaultHlsPlaylistTracker
.
stop
();
return
mediaPlaylists
;
}
private
static
byte
[]
getBytes
(
String
filenam
e
)
throws
IOException
{
return
TestUtil
.
getByteArray
(
ApplicationProvider
.
getApplicationContext
(),
filename
);
private
static
MockResponse
getMockResponse
(
String
assetFil
e
)
throws
IOException
{
return
new
MockResponse
().
setResponseCode
(
200
).
setBody
(
new
Buffer
().
write
(
getBytes
(
assetFile
))
);
}
private
static
final
class
DataSourceList
implements
DataSource
{
private
final
DataSource
[]
dataSources
;
private
DataSource
delegate
;
private
int
index
;
/**
* Creates an instance.
*
* @param dataSources The data sources to delegate to.
*/
public
DataSourceList
(
DataSource
...
dataSources
)
{
checkArgument
(
dataSources
.
length
>
0
);
this
.
dataSources
=
dataSources
;
delegate
=
dataSources
[
index
++];
}
@Override
public
void
addTransferListener
(
TransferListener
transferListener
)
{
for
(
DataSource
dataSource
:
dataSources
)
{
dataSource
.
addTransferListener
(
transferListener
);
}
}
@Override
public
long
open
(
DataSpec
dataSpec
)
throws
IOException
{
checkState
(
index
<=
dataSources
.
length
);
return
delegate
.
open
(
dataSpec
);
}
@Override
public
int
read
(
byte
[]
buffer
,
int
offset
,
int
readLength
)
throws
IOException
{
return
delegate
.
read
(
buffer
,
offset
,
readLength
);
}
@Override
@Nullable
public
Uri
getUri
()
{
return
delegate
.
getUri
();
}
@Override
public
Map
<
String
,
List
<
String
>>
getResponseHeaders
()
{
return
delegate
.
getResponseHeaders
();
}
@Override
public
void
close
()
throws
IOException
{
delegate
.
close
();
if
(
index
<
dataSources
.
length
)
{
delegate
=
dataSources
[
index
];
}
index
++;
}
private
static
byte
[]
getBytes
(
String
filename
)
throws
IOException
{
return
TestUtil
.
getByteArray
(
ApplicationProvider
.
getApplicationContext
(),
filename
);
}
}
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload
0 → 100644
View file @
c04dd8b3
#EXTM3U
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES
#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
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_block_reload_next
0 → 100644
View file @
c04dd8b3
#EXTM3U
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:11
#EXTINF:4.00000,
fileSequence11.ts
#EXTINF:4.00000,
fileSequence12.ts
#EXTINF:4.00000,
fileSequence13.ts
#EXTINF:4.00000,
fileSequence14.ts
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_skip_dateranges
View file @
c04dd8b3
#EXTM3U
#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24,CAN-SKIP-DATERANGES=YES
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:10
...
...
@@ -14,4 +15,3 @@ fileSequence13.ts
fileSequence14.ts
#EXTINF:4.00000,
fileSequence15.ts
#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24,CAN-SKIP-DATERANGES=YES
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_skip_skipped
View file @
c04dd8b3
#EXTM3U
#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:9
#EXT-X-MEDIA-SEQUENCE:11
...
...
@@ -11,4 +12,3 @@ fileSequence14.ts
fileSequence15.ts
#EXTINF:4.00000,
fileSequence16.ts
#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_skip_skipped_media_sequence_no_overlapping
View file @
c04dd8b3
#EXTM3U
#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:9
#EXT-X-MEDIA-SEQUENCE:20
...
...
@@ -11,4 +12,3 @@ fileSequence23.ts
fileSequence24.ts
#EXTINF:4.00000,
fileSequence25.ts
#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_skip_until
View file @
c04dd8b3
#EXTM3U
#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:10
...
...
@@ -14,4 +15,3 @@ fileSequence13.ts
fileSequence14.ts
#EXTINF:4.00000,
fileSequence15.ts
#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_skip_until_and_block_reload
0 → 100644
View file @
c04dd8b3
#EXTM3U
#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24,CAN-BLOCK-RELOAD=YES
#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
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_skip_until_and_block_reload_next
0 → 100644
View file @
c04dd8b3
#EXTM3U
#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24,CAN-BLOCK-RELOAD=YES
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:11
#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
#EXTINF:4.00000,
fileSequence16.ts
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_skip_until_and_block_reload_next_skipped
0 → 100644
View file @
c04dd8b3
#EXTM3U
#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24,CAN-BLOCK-RELOAD=YES
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:3
#EXT-X-SKIP:SKIPPED-SEGMENTS=2
#EXT-X-MEDIA-SEQUENCE:12
#EXTINF:4.00000,
fileSequence14.ts
#EXTINF:4.00000,
fileSequence15.ts
#EXTINF:4.00000,
fileSequence16.ts
#EXTINF:4.00000,
fileSequence17.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