Commit 57d51601 by Steve Mayhew

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.
parent bab89754
...@@ -451,6 +451,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -451,6 +451,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
return chunkIterators; 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. // Private methods.
/** /**
......
...@@ -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;
...@@ -222,6 +223,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -222,6 +223,12 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
private volatile boolean loadCanceled; private volatile boolean loadCanceled;
private boolean loadCompleted; 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( private HlsMediaChunk(
HlsExtractorFactory extractorFactory, HlsExtractorFactory extractorFactory,
DataSource mediaDataSource, DataSource mediaDataSource,
...@@ -291,6 +298,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -291,6 +298,15 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
return loadCompleted; 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 // Loadable implementation
@Override @Override
...@@ -308,6 +324,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -308,6 +324,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
initDataLoadRequired = false; initDataLoadRequired = false;
output.init(uid, shouldSpliceIn, /* reusingExtractor= */ true); output.init(uid, shouldSpliceIn, /* reusingExtractor= */ true);
} }
firstSampleIndex = output.getPrimaryTrackWritePosition();
maybeLoadInitData(); maybeLoadInitData();
if (!loadCanceled) { if (!loadCanceled) {
if (!hasGapTag) { if (!hasGapTag) {
......
...@@ -696,9 +696,65 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -696,9 +696,65 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@Override @Override
public void reevaluateBuffer(long positionUs) { 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. // Loader.Callback implementation.
@Override @Override
...@@ -959,6 +1015,22 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull; ...@@ -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. // Internal methods.
private void updateSampleStreams(@NullableType SampleStream[] streams) { private void updateSampleStreams(@NullableType SampleStream[] streams) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment