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
949e26d1
authored
Oct 26, 2020
by
bachinger
Committed by
Oliver Woodman
Nov 02, 2020
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
Support delta updates for media playlists
Issue: #5011 PiperOrigin-RevId: 339093145
parent
78940445
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
517 additions
and
19 deletions
library/hls/build.gradle
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.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/playlist/DefaultHlsPlaylistTrackerTest.java
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java
testdata/src/test/assets/media/m3u8/live_low_latency_master
testdata/src/test/assets/media/m3u8/live_low_latency_master_media_uri_with_param
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_not_skip
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_not_skip_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
library/hls/build.gradle
View file @
949e26d1
...
...
@@ -29,6 +29,7 @@ dependencies {
compileOnly
'org.checkerframework:checker-compat-qual:'
+
checkerframeworkCompatVersion
compileOnly
'org.jetbrains.kotlin:kotlin-annotations-jvm:'
+
kotlinAnnotationsVersion
implementation
project
(
modulePrefix
+
'library-core'
)
testImplementation
project
(
modulePrefix
+
'robolectricutils'
)
testImplementation
project
(
modulePrefix
+
'testutils'
)
testImplementation
project
(
modulePrefix
+
'testdata'
)
testImplementation
'org.robolectric:robolectric:'
+
robolectricVersion
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTracker.java
View file @
949e26d1
...
...
@@ -15,6 +15,7 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
source
.
hls
.
playlist
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkNotNull
;
import
static
java
.
lang
.
Math
.
max
;
import
android.net.Uri
;
...
...
@@ -163,7 +164,7 @@ public final class DefaultHlsPlaylistTracker
@Override
public
void
addListener
(
PlaylistEventListener
listener
)
{
Assertions
.
checkNotNull
(
listener
);
checkNotNull
(
listener
);
listeners
.
add
(
listener
);
}
...
...
@@ -390,7 +391,7 @@ public final class DefaultHlsPlaylistTracker
}
private
HlsMediaPlaylist
getLatestPlaylistSnapshot
(
HlsMediaPlaylist
oldPlaylist
,
HlsMediaPlaylist
loadedPlaylist
)
{
@Nullable
HlsMediaPlaylist
oldPlaylist
,
HlsMediaPlaylist
loadedPlaylist
)
{
if
(!
loadedPlaylist
.
isNewerThan
(
oldPlaylist
))
{
if
(
loadedPlaylist
.
hasEndTag
)
{
// If the loaded playlist has an end tag but is not newer than the old playlist then we have
...
...
@@ -408,7 +409,7 @@ public final class DefaultHlsPlaylistTracker
}
private
long
getLoadedPlaylistStartTimeUs
(
HlsMediaPlaylist
oldPlaylist
,
HlsMediaPlaylist
loadedPlaylist
)
{
@Nullable
HlsMediaPlaylist
oldPlaylist
,
HlsMediaPlaylist
loadedPlaylist
)
{
if
(
loadedPlaylist
.
hasProgramDateTime
)
{
return
loadedPlaylist
.
startTimeUs
;
}
...
...
@@ -430,7 +431,7 @@ public final class DefaultHlsPlaylistTracker
}
private
int
getLoadedPlaylistDiscontinuitySequence
(
HlsMediaPlaylist
oldPlaylist
,
HlsMediaPlaylist
loadedPlaylist
)
{
@Nullable
HlsMediaPlaylist
oldPlaylist
,
HlsMediaPlaylist
loadedPlaylist
)
{
if
(
loadedPlaylist
.
hasDiscontinuitySequence
)
{
return
loadedPlaylist
.
discontinuitySequence
;
}
...
...
@@ -464,7 +465,7 @@ public final class DefaultHlsPlaylistTracker
private
final
Uri
playlistUrl
;
private
final
Loader
mediaPlaylistLoader
;
private
final
ParsingLoadable
<
HlsPlaylist
>
mediaPlaylistLoadabl
e
;
private
final
DataSource
mediaPlaylistDataSourc
e
;
@Nullable
private
HlsMediaPlaylist
playlistSnapshot
;
private
long
lastSnapshotLoadMs
;
...
...
@@ -477,12 +478,7 @@ public final class DefaultHlsPlaylistTracker
public
MediaPlaylistBundle
(
Uri
playlistUrl
)
{
this
.
playlistUrl
=
playlistUrl
;
mediaPlaylistLoader
=
new
Loader
(
"DefaultHlsPlaylistTracker:MediaPlaylist"
);
mediaPlaylistLoadable
=
new
ParsingLoadable
<>(
dataSourceFactory
.
createDataSource
(
C
.
DATA_TYPE_MANIFEST
),
playlistUrl
,
C
.
DATA_TYPE_MANIFEST
,
mediaPlaylistParser
);
mediaPlaylistDataSource
=
dataSourceFactory
.
createDataSource
(
C
.
DATA_TYPE_MANIFEST
);
}
@Nullable
...
...
@@ -533,7 +529,7 @@ public final class DefaultHlsPlaylistTracker
@Override
public
void
onLoadCompleted
(
ParsingLoadable
<
HlsPlaylist
>
loadable
,
long
elapsedRealtimeMs
,
long
loadDurationMs
)
{
HlsPlaylist
result
=
loadable
.
getResult
();
@Nullable
HlsPlaylist
result
=
loadable
.
getResult
();
LoadEventInfo
loadEventInfo
=
new
LoadEventInfo
(
loadable
.
loadTaskId
,
...
...
@@ -631,6 +627,12 @@ public final class DefaultHlsPlaylistTracker
// Internal methods.
private
void
loadPlaylistImmediately
()
{
ParsingLoadable
<
HlsPlaylist
>
mediaPlaylistLoadable
=
new
ParsingLoadable
<>(
mediaPlaylistDataSource
,
getMediaPlaylistUriForRequest
(
playlistUrl
,
playlistSnapshot
),
C
.
DATA_TYPE_MANIFEST
,
mediaPlaylistParser
);
long
elapsedRealtime
=
mediaPlaylistLoader
.
startLoading
(
mediaPlaylistLoadable
,
...
...
@@ -644,7 +646,11 @@ public final class DefaultHlsPlaylistTracker
private
void
processLoadedPlaylist
(
HlsMediaPlaylist
loadedPlaylist
,
LoadEventInfo
loadEventInfo
)
{
HlsMediaPlaylist
oldPlaylist
=
playlistSnapshot
;
@Nullable
HlsMediaPlaylist
oldPlaylist
=
playlistSnapshot
;
loadedPlaylist
=
loadedPlaylist
.
skippedSegmentCount
>
0
?
loadedPlaylist
.
expandSkippedSegments
(
checkNotNull
(
playlistSnapshot
))
:
loadedPlaylist
;
long
currentTimeMs
=
SystemClock
.
elapsedRealtime
();
lastSnapshotLoadMs
=
currentTimeMs
;
playlistSnapshot
=
getLatestPlaylistSnapshot
(
oldPlaylist
,
loadedPlaylist
);
...
...
@@ -695,6 +701,18 @@ public final class DefaultHlsPlaylistTracker
}
}
private
Uri
getMediaPlaylistUriForRequest
(
Uri
playlistUri
,
@Nullable
HlsMediaPlaylist
currentMediaPlaylist
)
{
if
(
currentMediaPlaylist
==
null
||
currentMediaPlaylist
.
serverControl
.
skipUntilUs
==
C
.
TIME_UNSET
)
{
return
playlistUri
;
}
Uri
.
Builder
uriBuilder
=
playlistUri
.
buildUpon
();
uriBuilder
.
appendQueryParameter
(
"_HLS_skip"
,
currentMediaPlaylist
.
serverControl
.
canSkipDateRanges
?
"v2"
:
"YES"
);
return
uriBuilder
.
build
();
}
/**
* Excludes the playlist.
*
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylist.java
View file @
949e26d1
...
...
@@ -15,6 +15,8 @@
*/
package
com
.
google
.
android
.
exoplayer2
.
source
.
hls
.
playlist
;
import
static
com
.
google
.
android
.
exoplayer2
.
util
.
Assertions
.
checkArgument
;
import
androidx.annotation.IntDef
;
import
androidx.annotation.Nullable
;
import
com.google.android.exoplayer2.C
;
...
...
@@ -23,6 +25,7 @@ import com.google.android.exoplayer2.offline.StreamKey;
import
java.lang.annotation.Documented
;
import
java.lang.annotation.Retention
;
import
java.lang.annotation.RetentionPolicy
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.List
;
...
...
@@ -275,9 +278,9 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* The list of segments in the playlist.
*/
public
final
List
<
Segment
>
segments
;
/**
* The total duration of the playlist in microseconds.
*/
/**
The number of skipped segments. */
public
int
skippedSegmentCount
;
/** The total duration of the playlist in microseconds.
*/
public
final
long
durationUs
;
/** The attributes of the #EXT-X-SERVER-CONTROL header. */
public
final
ServerControl
serverControl
;
...
...
@@ -317,6 +320,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
boolean
hasProgramDateTime
,
@Nullable
DrmInitData
protectionSchemes
,
List
<
Segment
>
segments
,
int
skippedSegmentCount
,
ServerControl
serverControl
)
{
super
(
baseUri
,
tags
,
hasIndependentSegments
);
this
.
playlistType
=
playlistType
;
...
...
@@ -331,6 +335,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
this
.
hasProgramDateTime
=
hasProgramDateTime
;
this
.
protectionSchemes
=
protectionSchemes
;
this
.
segments
=
Collections
.
unmodifiableList
(
segments
);
this
.
skippedSegmentCount
=
skippedSegmentCount
;
if
(!
segments
.
isEmpty
())
{
Segment
last
=
segments
.
get
(
segments
.
size
()
-
1
);
durationUs
=
last
.
relativeStartTimeUs
+
last
.
durationUs
;
...
...
@@ -353,7 +358,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
* @param other The playlist to compare.
* @return Whether this playlist is newer than {@code other}.
*/
public
boolean
isNewerThan
(
HlsMediaPlaylist
other
)
{
public
boolean
isNewerThan
(
@Nullable
HlsMediaPlaylist
other
)
{
if
(
other
==
null
||
mediaSequence
>
other
.
mediaSequence
)
{
return
true
;
}
...
...
@@ -361,8 +366,8 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
return
false
;
}
// The media sequences are equal.
int
segmentCount
=
segments
.
size
();
int
otherSegmentCount
=
other
.
segments
.
size
();
int
segmentCount
=
segments
.
size
()
+
skippedSegmentCount
;
int
otherSegmentCount
=
other
.
segments
.
size
()
+
other
.
skippedSegmentCount
;
return
segmentCount
>
otherSegmentCount
||
(
segmentCount
==
otherSegmentCount
&&
hasEndTag
&&
!
other
.
hasEndTag
);
}
...
...
@@ -375,6 +380,50 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
}
/**
* Merges the skipped segments of the previous playlist and returns a copy with a {@link
* #skippedSegmentCount} of 0.
*
* @param previousPlaylist The previous playlist with a {@link #skippedSegmentCount} of zero.
* @return A new playlist with a complete list of segments.
*/
public
HlsMediaPlaylist
expandSkippedSegments
(
HlsMediaPlaylist
previousPlaylist
)
{
if
(
skippedSegmentCount
==
0
)
{
return
this
;
}
checkArgument
(
previousPlaylist
.
skippedSegmentCount
==
0
);
List
<
Segment
>
mergedSegments
=
new
ArrayList
<>();
long
mediaSequence
=
this
.
mediaSequence
;
int
startIndex
=
(
int
)
(
mediaSequence
-
previousPlaylist
.
mediaSequence
);
int
endIndex
=
startIndex
+
skippedSegmentCount
;
if
(
startIndex
>=
0
&&
endIndex
<=
previousPlaylist
.
segments
.
size
())
{
mergedSegments
.
addAll
(
previousPlaylist
.
segments
.
subList
(
startIndex
,
endIndex
));
}
else
{
// Adjust the media sequence if the old playlist doesn't contain all of the skipped segments.
mediaSequence
+=
skippedSegmentCount
;
}
mergedSegments
.
addAll
(
segments
);
return
new
HlsMediaPlaylist
(
playlistType
,
baseUri
,
tags
,
startOffsetUs
,
startTimeUs
,
hasDiscontinuitySequence
,
discontinuitySequence
,
mediaSequence
,
version
,
targetDurationUs
,
partTargetDurationUs
,
hasIndependentSegments
,
hasEndTag
,
hasProgramDateTime
,
protectionSchemes
,
mergedSegments
,
/* skippedSegmentCount= */
0
,
serverControl
);
}
/**
* Returns a playlist identical to this one except for the start time, the discontinuity sequence
* and {@code hasDiscontinuitySequence} values. The first two are set to the specified values,
* {@code hasDiscontinuitySequence} is set to true.
...
...
@@ -401,6 +450,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
hasProgramDateTime
,
protectionSchemes
,
segments
,
skippedSegmentCount
,
serverControl
);
}
...
...
@@ -429,6 +479,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
hasProgramDateTime
,
protectionSchemes
,
segments
,
skippedSegmentCount
,
serverControl
);
}
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/playlist/HlsPlaylistParser.java
View file @
949e26d1
...
...
@@ -90,6 +90,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private
static
final
String
TAG_SESSION_KEY
=
"#EXT-X-SESSION-KEY"
;
private
static
final
String
TAG_BYTERANGE
=
"#EXT-X-BYTERANGE"
;
private
static
final
String
TAG_GAP
=
"#EXT-X-GAP"
;
private
static
final
String
TAG_SKIP
=
"#EXT-X-SKIP"
;
private
static
final
String
TYPE_AUDIO
=
"AUDIO"
;
private
static
final
String
TYPE_VIDEO
=
"VIDEO"
;
...
...
@@ -135,6 +136,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
Pattern
.
compile
(
"CAN-SKIP-UNTIL=([\\d\\.]+)\\b"
);
private
static
final
Pattern
REGEX_CAN_SKIP_DATE_RANGES
=
compileBooleanAttrPattern
(
"CAN-SKIP-DATERANGES"
);
private
static
final
Pattern
REGEX_SKIPPED_SEGMENTS
=
Pattern
.
compile
(
"SKIPPED-SEGMENTS=(\\d+)\\b"
);
private
static
final
Pattern
REGEX_HOLD_BACK
=
Pattern
.
compile
(
"[:|,]HOLD-BACK=([\\d\\.]+)\\b"
);
private
static
final
Pattern
REGEX_PART_HOLD_BACK
=
Pattern
.
compile
(
"PART-HOLD-BACK=([\\d\\.]+)\\b"
);
...
...
@@ -609,6 +612,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
/* holdBackUs= */
C
.
TIME_UNSET
,
/* partHoldBackUs= */
C
.
TIME_UNSET
,
/* canBlockReload= */
false
);
int
skippedSegmentCount
=
0
;
DrmInitData
playlistProtectionSchemes
=
null
;
String
fullSegmentEncryptionKeyUri
=
null
;
...
...
@@ -692,6 +696,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
segmentDurationUs
=
(
long
)
(
parseDoubleAttr
(
line
,
REGEX_MEDIA_DURATION
)
*
C
.
MICROS_PER_SECOND
);
segmentTitle
=
parseOptionalStringAttr
(
line
,
REGEX_MEDIA_TITLE
,
""
,
variableDefinitions
);
}
else
if
(
line
.
startsWith
(
TAG_SKIP
))
{
skippedSegmentCount
=
parseIntAttr
(
line
,
REGEX_SKIPPED_SEGMENTS
);
}
else
if
(
line
.
startsWith
(
TAG_KEY
))
{
String
method
=
parseStringAttr
(
line
,
REGEX_METHOD
,
variableDefinitions
);
String
keyFormat
=
...
...
@@ -832,6 +838,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
/* hasProgramDateTime= */
playlistStartTimeUs
!=
0
,
playlistProtectionSchemes
,
segments
,
skippedSegmentCount
,
serverControl
);
}
...
...
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/DefaultHlsPlaylistTrackerTest.java
0 → 100644
View file @
949e26d1
/*
* Copyright 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package
com
.
google
.
android
.
exoplayer2
.
source
.
hls
.
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.DataSpec
;
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
org.junit.Test
;
import
org.junit.runner.RunWith
;
/** Unit test for {@link DefaultHlsPlaylistTracker}. */
@RunWith
(
AndroidJUnit4
.
class
)
public
class
DefaultHlsPlaylistTrackerTest
{
private
static
final
String
SAMPLE_M3U8_LIVE_MASTER
=
"media/m3u8/live_low_latency_master"
;
private
static
final
String
SAMPLE_M3U8_LIVE_MASTER_MEDIA_URI_WITH_PARAM
=
"media/m3u8/live_low_latency_master_media_uri_with_param"
;
private
static
final
String
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_UNTIL
=
"media/m3u8/live_low_latency_media_can_skip_until"
;
private
static
final
String
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_DATERANGES
=
"media/m3u8/live_low_latency_media_can_skip_dateranges"
;
private
static
final
String
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED
=
"media/m3u8/live_low_latency_media_can_skip_skipped"
;
private
static
final
String
SAMPLE_M3U8_LIVE_MEDIA_CAN_SKIP_SKIPPED_MEDIA_SEQUENCE_NO_OVERLAPPING
=
"media/m3u8/live_low_latency_media_can_skip_skipped_media_sequence_no_overlapping"
;
private
static
final
String
SAMPLE_M3U8_LIVE_MEDIA_CAN_NOT_SKIP
=
"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"
;
@Test
public
void
start_playlistCanNotSkip_requestsFullUpdate
()
throws
IOException
,
TimeoutException
{
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
))));
List
<
HlsMediaPlaylist
>
mediaPlaylists
=
runPlaylistTrackerAndCollectMediaPlaylists
(
/* dataSourceFactory= */
dataSourceQueue:
:
remove
,
masterPlaylistUri
,
/* awaitedMediaPlaylistCount= */
2
);
HlsMediaPlaylist
firstFullPlaylist
=
mediaPlaylists
.
get
(
0
);
assertThat
(
firstFullPlaylist
.
mediaSequence
).
isEqualTo
(
10
);
assertThat
(
firstFullPlaylist
.
segments
.
get
(
0
).
url
).
isEqualTo
(
"fileSequence10.ts"
);
assertThat
(
firstFullPlaylist
.
segments
.
get
(
5
).
url
).
isEqualTo
(
"fileSequence15.ts"
);
assertThat
(
firstFullPlaylist
.
segments
).
hasSize
(
6
);
HlsMediaPlaylist
secondFullPlaylist
=
mediaPlaylists
.
get
(
1
);
assertThat
(
secondFullPlaylist
.
mediaSequence
).
isEqualTo
(
11
);
assertThat
(
secondFullPlaylist
.
skippedSegmentCount
).
isEqualTo
(
0
);
assertThat
(
secondFullPlaylist
.
segments
.
get
(
0
).
url
).
isEqualTo
(
"fileSequence11.ts"
);
assertThat
(
secondFullPlaylist
.
segments
.
get
(
5
).
url
).
isEqualTo
(
"fileSequence16.ts"
);
assertThat
(
secondFullPlaylist
.
segments
).
hasSize
(
6
);
assertThat
(
secondFullPlaylist
.
segments
).
containsNoneIn
(
firstFullPlaylist
.
segments
);
}
@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
));
List
<
HlsMediaPlaylist
>
mediaPlaylists
=
runPlaylistTrackerAndCollectMediaPlaylists
(
new
FakeDataSource
.
Factory
().
setFakeDataSet
(
fakeDataSet
),
masterPlaylistUri
,
/* awaitedMediaPlaylistCount= */
2
);
HlsMediaPlaylist
initialPlaylistWithAllSegments
=
mediaPlaylists
.
get
(
0
);
assertThat
(
initialPlaylistWithAllSegments
.
mediaSequence
).
isEqualTo
(
10
);
assertThat
(
initialPlaylistWithAllSegments
.
segments
).
hasSize
(
6
);
HlsMediaPlaylist
mergedPlaylist
=
mediaPlaylists
.
get
(
1
);
assertThat
(
mergedPlaylist
.
mediaSequence
).
isEqualTo
(
11
);
assertThat
(
mergedPlaylist
.
skippedSegmentCount
).
isEqualTo
(
0
);
assertThat
(
mergedPlaylist
.
segments
).
hasSize
(
6
);
// First 2 segments of the merged playlist need to be copied from the previous playlist.
assertThat
(
mergedPlaylist
.
segments
.
subList
(
0
,
2
))
.
containsExactlyElementsIn
(
initialPlaylistWithAllSegments
.
segments
.
subList
(
1
,
3
))
.
inOrder
();
assertThat
(
mergedPlaylist
.
segments
.
get
(
2
).
url
)
.
isEqualTo
(
initialPlaylistWithAllSegments
.
segments
.
get
(
3
).
url
);
}
@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
));
List
<
HlsMediaPlaylist
>
mediaPlaylists
=
runPlaylistTrackerAndCollectMediaPlaylists
(
new
FakeDataSource
.
Factory
().
setFakeDataSet
(
fakeDataSet
),
masterPlaylistUri
,
/* awaitedMediaPlaylistCount= */
2
);
HlsMediaPlaylist
initialPlaylistWithAllSegments
=
mediaPlaylists
.
get
(
0
);
assertThat
(
initialPlaylistWithAllSegments
.
mediaSequence
).
isEqualTo
(
10
);
assertThat
(
initialPlaylistWithAllSegments
.
segments
).
hasSize
(
6
);
HlsMediaPlaylist
mergedPlaylist
=
mediaPlaylists
.
get
(
1
);
assertThat
(
mergedPlaylist
.
mediaSequence
).
isEqualTo
(
22
);
assertThat
(
mergedPlaylist
.
skippedSegmentCount
).
isEqualTo
(
0
);
assertThat
(
mergedPlaylist
.
segments
).
hasSize
(
4
);
}
@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
));
List
<
HlsMediaPlaylist
>
mediaPlaylists
=
runPlaylistTrackerAndCollectMediaPlaylists
(
new
FakeDataSource
.
Factory
().
setFakeDataSet
(
fakeDataSet
),
masterPlaylistUri
,
/* awaitedMediaPlaylistCount= */
2
);
// 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
);
}
@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
));
List
<
HlsMediaPlaylist
>
mediaPlaylists
=
runPlaylistTrackerAndCollectMediaPlaylists
(
new
FakeDataSource
.
Factory
().
setFakeDataSet
(
fakeDataSet
),
masterPlaylistUri
,
/* awaitedMediaPlaylistCount= */
2
);
// 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
);
}
private
static
List
<
HlsMediaPlaylist
>
runPlaylistTrackerAndCollectMediaPlaylists
(
DataSource
.
Factory
dataSourceFactory
,
Uri
masterPlaylistUri
,
int
awaitedMediaPlaylistCount
)
throws
TimeoutException
{
DefaultHlsPlaylistTracker
defaultHlsPlaylistTracker
=
new
DefaultHlsPlaylistTracker
(
dataType
->
dataSourceFactory
.
createDataSource
(),
new
DefaultLoadErrorHandlingPolicy
(),
new
DefaultHlsPlaylistParserFactory
());
List
<
HlsMediaPlaylist
>
mediaPlaylists
=
new
ArrayList
<>();
AtomicInteger
playlistCounter
=
new
AtomicInteger
();
defaultHlsPlaylistTracker
.
start
(
masterPlaylistUri
,
new
MediaSourceEventListener
.
EventDispatcher
(),
mediaPlaylist
->
{
mediaPlaylists
.
add
(
mediaPlaylist
);
playlistCounter
.
addAndGet
(
1
);
});
RobolectricUtil
.
runMainLooperUntil
(()
->
playlistCounter
.
get
()
==
awaitedMediaPlaylistCount
);
defaultHlsPlaylistTracker
.
stop
();
return
mediaPlaylists
;
}
private
static
byte
[]
getBytes
(
String
filename
)
throws
IOException
{
return
TestUtil
.
getByteArray
(
ApplicationProvider
.
getApplicationContext
(),
filename
);
}
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
++;
}
}
}
library/hls/src/test/java/com/google/android/exoplayer2/source/hls/playlist/HlsMediaPlaylistParserTest.java
View file @
949e26d1
...
...
@@ -300,6 +300,27 @@ public class HlsMediaPlaylistParserTest {
}
@Test
public
void
parseMediaPlaylist_withSkippedSegments_parsesNumberOfSkippedSegments
()
throws
IOException
{
Uri
playlistUri
=
Uri
.
parse
(
"https://example.com/test.m3u8"
);
String
playlistString
=
"#EXTM3U\n"
+
"#EXT-X-TARGETDURATION:4\n"
+
"#EXT-X-VERSION:6\n"
+
"#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24.0\n"
+
"#EXT-X-MEDIA-SEQUENCE:266\n"
+
"#EXT-X-SKIP:SKIPPED-SEGMENTS=1234\n"
+
"#EXTINF:4.00008,\n"
+
"fileSequence266.mp4"
;
InputStream
inputStream
=
new
ByteArrayInputStream
(
Util
.
getUtf8Bytes
(
playlistString
));
HlsMediaPlaylist
playlist
=
(
HlsMediaPlaylist
)
new
HlsPlaylistParser
().
parse
(
playlistUri
,
inputStream
);
assertThat
(
playlist
.
skippedSegmentCount
).
isEqualTo
(
1234
);
}
@Test
public
void
multipleExtXKeysForSingleSegment
()
throws
Exception
{
Uri
playlistUri
=
Uri
.
parse
(
"https://example.com/test.m3u8"
);
String
playlistString
=
...
...
testdata/src/test/assets/media/m3u8/live_low_latency_master
0 → 100644
View file @
949e26d1
#EXTM3U
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-STREAM-INF:BANDWIDTH=2000000,CODECS="avc1.640028,mp4a.40.2"
media0/playlist.m3u8
testdata/src/test/assets/media/m3u8/live_low_latency_master_media_uri_with_param
0 → 100644
View file @
949e26d1
#EXTM3U
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-STREAM-INF:BANDWIDTH=2000000,CODECS="avc1.640028,mp4a.40.2"
media0/playlist.m3u8?param1=1¶m2=2
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_not_skip
0 → 100644
View file @
949e26d1
#EXTM3U
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:10
#EXTINF:4.00000,
fileSequence10.ts
#EXTINF:4.00000,
fileSequence11.ts
#EXTINF:4.00000,
fileSequence12.ts
#EXTINF:4.00000,
fileSequence13.ts
#EXTINF:4.00000,
fileSequence14.ts
#EXTINF:4.00000,
fileSequence15.ts
testdata/src/test/assets/media/m3u8/live_low_latency_media_can_not_skip_next
0 → 100644
View file @
949e26d1
#EXTM3U
#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_dateranges
0 → 100644
View file @
949e26d1
#EXTM3U
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:10
#EXTINF:4.00000,
fileSequence10.ts
#EXTINF:4.00000,
fileSequence11.ts
#EXTINF:4.00000,
fileSequence12.ts
#EXTINF:4.00000,
fileSequence13.ts
#EXTINF:4.00000,
fileSequence14.ts
#EXTINF:4.00000,
fileSequence15.ts
#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
0 → 100644
View file @
949e26d1
#EXTM3U
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:9
#EXT-X-MEDIA-SEQUENCE:11
#EXT-X-SKIP:SKIPPED-SEGMENTS=2
#EXTINF:4.00000,
fileSequence13.ts
#EXTINF:4.00000,
fileSequence14.ts
#EXTINF:4.00000,
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
0 → 100644
View file @
949e26d1
#EXTM3U
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:9
#EXT-X-MEDIA-SEQUENCE:20
#EXT-X-SKIP:SKIPPED-SEGMENTS=2
#EXTINF:4.00000,
fileSequence22.ts
#EXTINF:4.00000,
fileSequence23.ts
#EXTINF:4.00000,
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
0 → 100644
View file @
949e26d1
#EXTM3U
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:10
#EXTINF:4.00000,
fileSequence10.ts
#EXTINF:4.00000,
fileSequence11.ts
#EXTINF:4.00000,
fileSequence12.ts
#EXTINF:4.00000,
fileSequence13.ts
#EXTINF:4.00000,
fileSequence14.ts
#EXTINF:4.00000,
fileSequence15.ts
#EXT-X-SERVER-CONTROL:CAN-SKIP-UNTIL=24
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