add support SequenceableLoader.reevaluateBuffer() for HLS

DASH implements this feature, extend the feature for HLS as well.  First change just drops video samples.
For demuxed audio the audio samples will continue to play out to match the
dropped video, so need to keep indexes in all the sample queues related to a chunk and discard them all.
This commit is contained in:
Steve Mayhew 2019-08-16 15:56:32 -07:00
parent bab8975438
commit 57d5160161
3 changed files with 97 additions and 1 deletions

View file

@ -451,6 +451,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return chunkIterators;
}
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.
/**

View file

@ -27,6 +27,7 @@ import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.id3.Id3Decoder;
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.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.upstream.DataSource;
@ -222,6 +223,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private volatile boolean loadCanceled;
private boolean loadCompleted;
/**
* Index of first sample written to the SampleQueue for the primary track from
* this segment.
*/
private int firstSampleIndex = C.INDEX_UNSET;
private HlsMediaChunk(
HlsExtractorFactory extractorFactory,
DataSource mediaDataSource,
@ -291,6 +298,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return loadCompleted;
}
/**
* Return the index of the first sample from the primary sample stream for this media chunk
*
* @return sample index {@link SampleQueue#getWriteIndex()}
*/
public int getFirstPrimarySampleIndex() {
return firstSampleIndex;
}
// Loadable implementation
@Override
@ -308,6 +324,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
initDataLoadRequired = false;
output.init(uid, shouldSpliceIn, /* reusingExtractor= */ true);
}
firstSampleIndex = output.getPrimaryTrackWritePosition();
maybeLoadInitData();
if (!loadCanceled) {
if (!hasGapTag) {

View file

@ -696,9 +696,65 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Override
public void reevaluateBuffer(long positionUs) {
// Do nothing.
if (loader.isLoading() || isPendingReset()) {
return;
}
int currentQueueSize = mediaChunks.size();
int preferredQueueSize = chunkSource.getPreferredQueueSize(positionUs, readOnlyMediaChunks);
if (currentQueueSize > preferredQueueSize) {
Log.d(TAG, "reevaluateBuffer() - position: " + positionUs + " preferredQueueSize: "
+ preferredQueueSize + " current size: "+ currentQueueSize);
long firstRemovedStartTimeUs = discardMediaChunks(preferredQueueSize);
long endTimeUs = getLastMediaChunk().endTimeUs;
eventDispatcher.upstreamDiscarded(primarySampleQueueType, firstRemovedStartTimeUs, endTimeUs);
}
}
/**
* Discards HlsMediaChunks, after currently playing chunk {@see #haveReadFromMediaChunk}, that have
* not yet started to play to allow (hopefully) higher quality chunks to replace them
*
* @param preferredQueueSize - desired media chunk queue size (always < mediaChunks.size())
* @return endTimeUs of first chunk removed
*/
private long discardMediaChunks(int preferredQueueSize) {
Log.d(TAG, "discardChunksToIndex() - preferredQueueSize " + preferredQueueSize
+ " currentSize " + mediaChunks.size()
+ " write: "+sampleQueues[primarySampleQueueIndex].getWriteIndex()
+ " read: "+sampleQueues[primarySampleQueueIndex].getReadIndex()
);
for (int i=0; i<mediaChunks.size(); i++) {
HlsMediaChunk chunk = mediaChunks.get(i);
Log.d(TAG, "chunk " + chunk.uid + " reading: " + haveReadFromMediaChunk(i)
+ " start/end: " + chunk.startTimeUs + "/" + chunk.endTimeUs + " sample index: "
+ chunk.getFirstPrimarySampleIndex() + " format: "+chunk.trackFormat);
}
// Preserve current playing chunk and/or enough following it to reach the desired queue size
int firstRemovedChunkIndex = 0;
while (haveReadFromMediaChunk(firstRemovedChunkIndex) || preferredQueueSize > 0) {
firstRemovedChunkIndex++;
preferredQueueSize--;
}
HlsMediaChunk firstRemovedChunk = mediaChunks.get(firstRemovedChunkIndex);
Util.removeRange(mediaChunks, firstRemovedChunkIndex, mediaChunks.size() - 1);
Log.d(TAG, "discardChunksToIndex() - discard from: " + firstRemovedChunk.getFirstPrimarySampleIndex());
sampleQueues[primarySampleQueueIndex].discardUpstreamSamples(firstRemovedChunk.getFirstPrimarySampleIndex());
return firstRemovedChunk.endTimeUs;
}
/** Returns whether samples have been read from primary sample queue of the indicated chunk */
private boolean haveReadFromMediaChunk(int mediaChunkIndex) {
HlsMediaChunk mediaChunk = mediaChunks.get(mediaChunkIndex);
return sampleQueues[primarySampleQueueIndex].getReadIndex() > mediaChunk.getFirstPrimarySampleIndex();
}
// Loader.Callback implementation.
@Override
@ -959,6 +1015,22 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
}
}
/**
* Get the SampleQueue write position index. Used to associate group of
* samples with a MediaChunk.
*
* @return write position {@link SampleQueue#getWriteIndex()}, or 0 (safe bet) if sample queues not created
*/
int getPrimaryTrackWritePosition() {
int indexValue = 0;
if (primaryTrackGroupIndex != C.INDEX_UNSET && prepared) {
int sampleQueueIndex = trackGroupToSampleQueueIndex[primaryTrackGroupIndex];
indexValue = sampleQueues[sampleQueueIndex].getWriteIndex();
}
return indexValue;
}
// Internal methods.
private void updateSampleStreams(@NullableType SampleStream[] streams) {