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
80eb5d42
authored
May 21, 2020
by
tonihei
Browse files
Options
_('Browse Files')
Download
Plain Diff
Merge pull request #7244 from tvarga-dss:cancel-hls-chunk-download-and-discard-upstream
PiperOrigin-RevId: 312679454
parents
d487170e
e4cb7405
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
133 additions
and
17 deletions
RELEASENOTES.md
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java
RELEASENOTES.md
View file @
80eb5d42
...
@@ -168,6 +168,9 @@
...
@@ -168,6 +168,9 @@
*
Enable support for embedded CEA-708.
*
Enable support for embedded CEA-708.
*
Fix assertion failure in
`SampleQueue`
when playing DASH streams with
*
Fix assertion failure in
`SampleQueue`
when playing DASH streams with
EMSG tracks (
[
#7273
](
https://github.com/google/ExoPlayer/issues/7273
)
).
EMSG tracks (
[
#7273
](
https://github.com/google/ExoPlayer/issues/7273
)
).
*
HLS:
*
Add support for upstream discard including cancelation of ongoing load
(
[
#6322
](
https://github.com/google/ExoPlayer/issues/6322
)
).
*
MP3:
*
MP3:
*
Add
`IndexSeeker`
for accurate seeks in VBR streams
*
Add
`IndexSeeker`
for accurate seeks in VBR streams
(
[
#6787
](
https://github.com/google/ExoPlayer/issues/6787
)
). This seeker
(
[
#6787
](
https://github.com/google/ExoPlayer/issues/6787
)
). This seeker
...
...
library/core/src/main/java/com/google/android/exoplayer2/source/chunk/ChunkSampleStream.java
View file @
80eb5d42
...
@@ -628,7 +628,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
...
@@ -628,7 +628,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
return
;
return
;
}
}
int
newQueueSize
=
currentQueueSize
;
int
newQueueSize
=
Integer
.
MAX_VALUE
;
for
(
int
i
=
preferredQueueSize
;
i
<
currentQueueSize
;
i
++)
{
for
(
int
i
=
preferredQueueSize
;
i
<
currentQueueSize
;
i
++)
{
if
(!
haveReadFromMediaChunk
(
i
))
{
if
(!
haveReadFromMediaChunk
(
i
))
{
newQueueSize
=
i
;
newQueueSize
=
i
;
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsChunkSource.java
View file @
80eb5d42
...
@@ -451,6 +451,24 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
...
@@ -451,6 +451,24 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return
chunkIterators
;
return
chunkIterators
;
}
}
/**
* Evaluates whether {@link MediaChunk MediaChunks} should be removed from the back of the queue.
*
* <p>Removing {@link MediaChunk MediaChunks} from the back of the queue can be useful if they
* could be replaced with chunks of a significantly higher quality (e.g. because the available
* bandwidth has substantially increased).
*
* @param playbackPositionUs The current playback position, in microseconds.
* @param queue The queue of buffered {@link MediaChunk MediaChunks}.
* @return The preferred queue size.
*/
public
int
getPreferredQueueSize
(
long
playbackPositionUs
,
List
<?
extends
MediaChunk
>
queue
)
{
if
(
fatalError
!=
null
||
trackSelection
.
length
()
<
2
)
{
return
queue
.
size
();
}
return
trackSelection
.
evaluateQueueSize
(
playbackPositionUs
,
queue
);
}
// Private methods.
// Private methods.
/**
/**
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsMediaChunk.java
View file @
80eb5d42
...
@@ -27,6 +27,7 @@ import com.google.android.exoplayer2.extractor.PositionHolder;
...
@@ -27,6 +27,7 @@ import com.google.android.exoplayer2.extractor.PositionHolder;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.Metadata
;
import
com.google.android.exoplayer2.metadata.id3.Id3Decoder
;
import
com.google.android.exoplayer2.metadata.id3.Id3Decoder
;
import
com.google.android.exoplayer2.metadata.id3.PrivFrame
;
import
com.google.android.exoplayer2.metadata.id3.PrivFrame
;
import
com.google.android.exoplayer2.source.SampleQueue
;
import
com.google.android.exoplayer2.source.chunk.MediaChunk
;
import
com.google.android.exoplayer2.source.chunk.MediaChunk
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
;
import
com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist
;
import
com.google.android.exoplayer2.upstream.DataSource
;
import
com.google.android.exoplayer2.upstream.DataSource
;
...
@@ -36,6 +37,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
...
@@ -36,6 +37,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
import
com.google.android.exoplayer2.util.TimestampAdjuster
;
import
com.google.android.exoplayer2.util.TimestampAdjuster
;
import
com.google.android.exoplayer2.util.UriUtil
;
import
com.google.android.exoplayer2.util.UriUtil
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.android.exoplayer2.util.Util
;
import
com.google.common.collect.ImmutableMap
;
import
java.io.EOFException
;
import
java.io.EOFException
;
import
java.io.IOException
;
import
java.io.IOException
;
import
java.io.InterruptedIOException
;
import
java.io.InterruptedIOException
;
...
@@ -131,11 +133,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...
@@ -131,11 +133,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
Id3Decoder
id3Decoder
;
Id3Decoder
id3Decoder
;
ParsableByteArray
scratchId3Data
;
ParsableByteArray
scratchId3Data
;
boolean
shouldSpliceIn
;
boolean
shouldSpliceIn
;
ImmutableMap
<
SampleQueue
,
Integer
>
sampleQueueDiscardFromIndices
=
ImmutableMap
.
of
();
if
(
previousChunk
!=
null
)
{
if
(
previousChunk
!=
null
)
{
id3Decoder
=
previousChunk
.
id3Decoder
;
id3Decoder
=
previousChunk
.
id3Decoder
;
scratchId3Data
=
previousChunk
.
scratchId3Data
;
scratchId3Data
=
previousChunk
.
scratchId3Data
;
shouldSpliceIn
=
shouldSpliceIn
=
!
playlistUrl
.
equals
(
previousChunk
.
playlistUrl
)
||
!
previousChunk
.
loadCompleted
;
!
playlistUrl
.
equals
(
previousChunk
.
playlistUrl
)
||
!
previousChunk
.
loadCompleted
;
if
(
shouldSpliceIn
)
{
sampleQueueDiscardFromIndices
=
previousChunk
.
sampleQueueDiscardFromIndices
;
}
previousExtractor
=
previousExtractor
=
previousChunk
.
isExtractorReusable
previousChunk
.
isExtractorReusable
&&
previousChunk
.
discontinuitySequenceNumber
==
discontinuitySequenceNumber
&&
previousChunk
.
discontinuitySequenceNumber
==
discontinuitySequenceNumber
...
@@ -172,7 +178,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...
@@ -172,7 +178,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
previousExtractor
,
previousExtractor
,
id3Decoder
,
id3Decoder
,
scratchId3Data
,
scratchId3Data
,
shouldSpliceIn
);
shouldSpliceIn
,
sampleQueueDiscardFromIndices
);
}
}
public
static
final
String
PRIV_TIMESTAMP_FRAME_OWNER
=
public
static
final
String
PRIV_TIMESTAMP_FRAME_OWNER
=
...
@@ -194,9 +201,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...
@@ -194,9 +201,6 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/** The url of the playlist from which this chunk was obtained. */
/** The url of the playlist from which this chunk was obtained. */
public
final
Uri
playlistUrl
;
public
final
Uri
playlistUrl
;
/** Whether the samples parsed from this chunk should be spliced into already queued samples. */
public
final
boolean
shouldSpliceIn
;
@Nullable
private
final
DataSource
initDataSource
;
@Nullable
private
final
DataSource
initDataSource
;
@Nullable
private
final
DataSpec
initDataSpec
;
@Nullable
private
final
DataSpec
initDataSpec
;
@Nullable
private
final
Extractor
previousExtractor
;
@Nullable
private
final
Extractor
previousExtractor
;
...
@@ -211,6 +215,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...
@@ -211,6 +215,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private
final
ParsableByteArray
scratchId3Data
;
private
final
ParsableByteArray
scratchId3Data
;
private
final
boolean
mediaSegmentEncrypted
;
private
final
boolean
mediaSegmentEncrypted
;
private
final
boolean
initSegmentEncrypted
;
private
final
boolean
initSegmentEncrypted
;
private
final
boolean
shouldSpliceIn
;
private
@MonotonicNonNull
Extractor
extractor
;
private
@MonotonicNonNull
Extractor
extractor
;
private
boolean
isExtractorReusable
;
private
boolean
isExtractorReusable
;
...
@@ -221,6 +226,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...
@@ -221,6 +226,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private
boolean
initDataLoadRequired
;
private
boolean
initDataLoadRequired
;
private
volatile
boolean
loadCanceled
;
private
volatile
boolean
loadCanceled
;
private
boolean
loadCompleted
;
private
boolean
loadCompleted
;
private
ImmutableMap
<
SampleQueue
,
Integer
>
sampleQueueDiscardFromIndices
;
private
HlsMediaChunk
(
private
HlsMediaChunk
(
HlsExtractorFactory
extractorFactory
,
HlsExtractorFactory
extractorFactory
,
...
@@ -246,7 +252,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...
@@ -246,7 +252,8 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Nullable
Extractor
previousExtractor
,
@Nullable
Extractor
previousExtractor
,
Id3Decoder
id3Decoder
,
Id3Decoder
id3Decoder
,
ParsableByteArray
scratchId3Data
,
ParsableByteArray
scratchId3Data
,
boolean
shouldSpliceIn
)
{
boolean
shouldSpliceIn
,
ImmutableMap
<
SampleQueue
,
Integer
>
sampleQueueDiscardFromIndices
)
{
super
(
super
(
mediaDataSource
,
mediaDataSource
,
dataSpec
,
dataSpec
,
...
@@ -273,17 +280,43 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...
@@ -273,17 +280,43 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
this
.
id3Decoder
=
id3Decoder
;
this
.
id3Decoder
=
id3Decoder
;
this
.
scratchId3Data
=
scratchId3Data
;
this
.
scratchId3Data
=
scratchId3Data
;
this
.
shouldSpliceIn
=
shouldSpliceIn
;
this
.
shouldSpliceIn
=
shouldSpliceIn
;
this
.
sampleQueueDiscardFromIndices
=
sampleQueueDiscardFromIndices
;
uid
=
uidSource
.
getAndIncrement
();
uid
=
uidSource
.
getAndIncrement
();
}
}
/**
/**
* Initializes the chunk for loading, setting the {@link HlsSampleStreamWrapper} that will receive
* Initializes the chunk for loading.
* samples as they are loaded.
*
*
* @param output The output that will receive the loaded samples.
* @param output The {@link HlsSampleStreamWrapper} that will receive the loaded samples.
* @param sampleQueues The {@link SampleQueue sampleQueues} with already loaded samples.
*/
*/
public
void
init
(
HlsSampleStreamWrapper
output
)
{
public
void
init
(
HlsSampleStreamWrapper
output
,
SampleQueue
[]
sampleQueues
)
{
this
.
output
=
output
;
this
.
output
=
output
;
if
(
shouldSpliceIn
)
{
for
(
SampleQueue
sampleQueue
:
sampleQueues
)
{
sampleQueue
.
splice
();
}
// sampleQueueDiscardFromIndices already set to values of previous chunk in constructor.
}
else
{
ImmutableMap
.
Builder
<
SampleQueue
,
Integer
>
mapBuilder
=
ImmutableMap
.
builder
();
for
(
SampleQueue
sampleQueue
:
sampleQueues
)
{
mapBuilder
.
put
(
sampleQueue
,
sampleQueue
.
getWriteIndex
());
}
sampleQueueDiscardFromIndices
=
mapBuilder
.
build
();
}
}
/**
* Returns the absolute index from which samples need to be discarded in the given {@link
* SampleQueue} when this media chunk is discarded.
*
* @param sampleQueue The {@link SampleQueue}.
* @return The absolute index from which samples need to be discarded.
*/
int
getSampleQueueDiscardFromIndex
(
SampleQueue
sampleQueue
)
{
// If the sample queue was created by this chunk or a later chunk, return 0 to discard the whole
// stream from the beginning.
return
sampleQueueDiscardFromIndices
.
getOrDefault
(
sampleQueue
,
/* defaultValue= */
0
);
}
}
@Override
@Override
...
...
library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java
View file @
80eb5d42
...
@@ -146,6 +146,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...
@@ -146,6 +146,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private
@MonotonicNonNull
Format
upstreamTrackFormat
;
private
@MonotonicNonNull
Format
upstreamTrackFormat
;
@Nullable
private
Format
downstreamTrackFormat
;
@Nullable
private
Format
downstreamTrackFormat
;
private
boolean
released
;
private
boolean
released
;
private
int
pendingDiscardUpstreamQueueSize
;
// Tracks are complicated in HLS. See documentation of buildTracksFromSampleStreams for details.
// Tracks are complicated in HLS. See documentation of buildTracksFromSampleStreams for details.
// Indexed by track (as exposed by this source).
// Indexed by track (as exposed by this source).
...
@@ -229,6 +230,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...
@@ -229,6 +230,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
handler
=
Util
.
createHandler
();
handler
=
Util
.
createHandler
();
lastSeekPositionUs
=
positionUs
;
lastSeekPositionUs
=
positionUs
;
pendingResetPositionUs
=
positionUs
;
pendingResetPositionUs
=
positionUs
;
pendingDiscardUpstreamQueueSize
=
C
.
LENGTH_UNSET
;
}
}
public
void
continuePreparing
()
{
public
void
continuePreparing
()
{
...
@@ -696,7 +698,21 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...
@@ -696,7 +698,21 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Override
@Override
public
void
reevaluateBuffer
(
long
positionUs
)
{
public
void
reevaluateBuffer
(
long
positionUs
)
{
// Do nothing.
if
(
loader
.
hasFatalError
()
||
isPendingReset
())
{
return
;
}
int
currentQueueSize
=
mediaChunks
.
size
();
int
preferredQueueSize
=
chunkSource
.
getPreferredQueueSize
(
positionUs
,
readOnlyMediaChunks
);
if
(
currentQueueSize
<=
preferredQueueSize
)
{
return
;
}
if
(
loader
.
isLoading
())
{
pendingDiscardUpstreamQueueSize
=
preferredQueueSize
;
loader
.
cancelLoading
();
}
else
{
discardUpstream
(
preferredQueueSize
);
}
}
}
// Loader.Callback implementation.
// Loader.Callback implementation.
...
@@ -753,7 +769,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...
@@ -753,7 +769,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
loadable
.
startTimeUs
,
loadable
.
startTimeUs
,
loadable
.
endTimeUs
);
loadable
.
endTimeUs
);
if
(!
released
)
{
if
(!
released
)
{
resetSampleQueues
();
if
(
pendingDiscardUpstreamQueueSize
!=
C
.
LENGTH_UNSET
)
{
discardUpstream
(
pendingDiscardUpstreamQueueSize
);
pendingDiscardUpstreamQueueSize
=
C
.
LENGTH_UNSET
;
}
else
{
resetSampleQueues
();
}
if
(
enabledTrackGroupCount
>
0
)
{
if
(
enabledTrackGroupCount
>
0
)
{
callback
.
onContinueLoadingRequested
(
this
);
callback
.
onContinueLoadingRequested
(
this
);
}
}
...
@@ -851,16 +872,36 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...
@@ -851,16 +872,36 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
upstreamTrackFormat
=
chunk
.
trackFormat
;
upstreamTrackFormat
=
chunk
.
trackFormat
;
pendingResetPositionUs
=
C
.
TIME_UNSET
;
pendingResetPositionUs
=
C
.
TIME_UNSET
;
mediaChunks
.
add
(
chunk
);
mediaChunks
.
add
(
chunk
);
chunk
.
init
(
/* output= */
this
,
sampleQueues
);
chunk
.
init
(
this
);
for
(
HlsSampleQueue
sampleQueue
:
sampleQueues
)
{
for
(
HlsSampleQueue
sampleQueue
:
sampleQueues
)
{
sampleQueue
.
setSourceChunk
(
chunk
);
sampleQueue
.
setSourceChunk
(
chunk
);
}
}
if
(
chunk
.
shouldSpliceIn
)
{
}
for
(
SampleQueue
sampleQueue
:
sampleQueues
)
{
sampleQueue
.
splice
();
private
void
discardUpstream
(
int
preferredQueueSize
)
{
Assertions
.
checkState
(!
loader
.
isLoading
());
int
currentQueueSize
=
mediaChunks
.
size
();
int
newQueueSize
=
Integer
.
MAX_VALUE
;
for
(
int
i
=
preferredQueueSize
;
i
<
currentQueueSize
;
i
++)
{
if
(!
haveReadFromMediaChunkDiscardRange
(
i
))
{
newQueueSize
=
i
;
break
;
}
}
}
}
if
(
newQueueSize
>=
currentQueueSize
)
{
return
;
}
long
endTimeUs
=
getLastMediaChunk
().
endTimeUs
;
HlsMediaChunk
firstRemovedChunk
=
discardUpstreamMediaChunksFromIndex
(
newQueueSize
);
if
(
mediaChunks
.
isEmpty
())
{
pendingResetPositionUs
=
lastSeekPositionUs
;
}
loadingFinished
=
false
;
eventDispatcher
.
upstreamDiscarded
(
primarySampleQueueType
,
firstRemovedChunk
.
startTimeUs
,
endTimeUs
);
}
}
// ExtractorOutput implementation. Called by the loading thread.
// ExtractorOutput implementation. Called by the loading thread.
...
@@ -1061,6 +1102,27 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
...
@@ -1061,6 +1102,27 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return
true
;
return
true
;
}
}
private
boolean
haveReadFromMediaChunkDiscardRange
(
int
mediaChunkIndex
)
{
HlsMediaChunk
mediaChunk
=
mediaChunks
.
get
(
mediaChunkIndex
);
for
(
SampleQueue
sampleQueue
:
sampleQueues
)
{
int
discardFromIndex
=
mediaChunk
.
getSampleQueueDiscardFromIndex
(
sampleQueue
);
if
(
sampleQueue
.
getReadIndex
()
>
discardFromIndex
)
{
return
true
;
}
}
return
false
;
}
private
HlsMediaChunk
discardUpstreamMediaChunksFromIndex
(
int
chunkIndex
)
{
HlsMediaChunk
firstRemovedChunk
=
mediaChunks
.
get
(
chunkIndex
);
Util
.
removeRange
(
mediaChunks
,
/* fromIndex= */
chunkIndex
,
/* toIndex= */
mediaChunks
.
size
());
for
(
SampleQueue
sampleQueue
:
sampleQueues
)
{
int
discardFromIndex
=
firstRemovedChunk
.
getSampleQueueDiscardFromIndex
(
sampleQueue
);
sampleQueue
.
discardUpstreamSamples
(
discardFromIndex
);
}
return
firstRemovedChunk
;
}
private
void
resetSampleQueues
()
{
private
void
resetSampleQueues
()
{
for
(
SampleQueue
sampleQueue
:
sampleQueues
)
{
for
(
SampleQueue
sampleQueue
:
sampleQueues
)
{
sampleQueue
.
reset
(
pendingResetUpstreamFormats
);
sampleQueue
.
reset
(
pendingResetUpstreamFormats
);
...
...
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