mirror of
https://github.com/samsonjs/media.git
synced 2026-04-01 10:35:48 +00:00
Decouple next chunk evaluation and queue trimming.
------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=118925372
This commit is contained in:
parent
1581b915d3
commit
9282710f04
8 changed files with 196 additions and 210 deletions
|
|
@ -16,20 +16,9 @@
|
|||
package com.google.android.exoplayer.chunk;
|
||||
|
||||
/**
|
||||
* Holds a chunk operation, which consists of a either:
|
||||
* <ul>
|
||||
* <li>The number of {@link MediaChunk}s that should be retained on the queue ({@link #queueSize})
|
||||
* together with the next {@link Chunk} to load ({@link #chunk}). {@link #chunk} may be null if the
|
||||
* next chunk cannot be provided yet.</li>
|
||||
* <li>A flag indicating that the end of the stream has been reached ({@link #endOfStream}).</li>
|
||||
* </ul>
|
||||
* Holds a chunk or an indication that the end of the stream has been reached.
|
||||
*/
|
||||
public final class ChunkOperationHolder {
|
||||
|
||||
/**
|
||||
* The number of {@link MediaChunk}s to retain in a queue.
|
||||
*/
|
||||
public int queueSize;
|
||||
public final class ChunkHolder {
|
||||
|
||||
/**
|
||||
* The chunk.
|
||||
|
|
@ -45,7 +34,6 @@ public final class ChunkOperationHolder {
|
|||
* Clears the holder.
|
||||
*/
|
||||
public void clear() {
|
||||
queueSize = 0;
|
||||
chunk = null;
|
||||
endOfStream = false;
|
||||
}
|
||||
|
|
@ -55,7 +55,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
private final Loader loader;
|
||||
private final LoadControl loadControl;
|
||||
private final ChunkSource chunkSource;
|
||||
private final ChunkOperationHolder currentLoadableHolder;
|
||||
private final ChunkHolder nextChunkHolder;
|
||||
private final LinkedList<BaseMediaChunk> mediaChunks;
|
||||
private final List<BaseMediaChunk> readOnlyMediaChunks;
|
||||
private final DefaultTrackOutput sampleQueue;
|
||||
|
|
@ -66,7 +66,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
private long downstreamPositionUs;
|
||||
private long lastSeekPositionUs;
|
||||
private long pendingResetPositionUs;
|
||||
private long lastPerformedBufferOperation;
|
||||
private long lastPreferredQueueSizeEvaluationTimeMs;
|
||||
private boolean pendingReset;
|
||||
private boolean loadControlRegistered;
|
||||
|
||||
|
|
@ -76,6 +76,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
private boolean trackEnabled;
|
||||
private long currentLoadStartTimeMs;
|
||||
|
||||
private Chunk currentLoadable;
|
||||
private Format downstreamFormat;
|
||||
private Format downstreamSampleFormat;
|
||||
|
||||
|
|
@ -124,7 +125,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
this.bufferSizeContribution = bufferSizeContribution;
|
||||
loader = new Loader("Loader:ChunkSampleSource", minLoadableRetryCount);
|
||||
eventDispatcher = new EventDispatcher(eventHandler, eventListener, eventSourceId);
|
||||
currentLoadableHolder = new ChunkOperationHolder();
|
||||
nextChunkHolder = new ChunkHolder();
|
||||
mediaChunks = new LinkedList<>();
|
||||
readOnlyMediaChunks = Collections.unmodifiableList(mediaChunks);
|
||||
sampleQueue = new DefaultTrackOutput(loadControl.getAllocator());
|
||||
|
|
@ -267,9 +268,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
@Override
|
||||
public void maybeThrowError() throws IOException {
|
||||
loader.maybeThrowError();
|
||||
if (currentLoadableHolder.chunk == null) {
|
||||
chunkSource.maybeThrowError();
|
||||
}
|
||||
chunkSource.maybeThrowError();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -336,7 +335,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
public void onLoadCompleted(Loadable loadable) {
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
long loadDurationMs = now - currentLoadStartTimeMs;
|
||||
Chunk currentLoadable = currentLoadableHolder.chunk;
|
||||
chunkSource.onChunkLoadCompleted(currentLoadable);
|
||||
if (isMediaChunk(currentLoadable)) {
|
||||
BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable;
|
||||
|
|
@ -353,7 +351,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
|
||||
@Override
|
||||
public void onLoadCanceled(Loadable loadable) {
|
||||
Chunk currentLoadable = currentLoadableHolder.chunk;
|
||||
eventDispatcher.loadCanceled(currentLoadable.bytesLoaded());
|
||||
if (trackEnabled) {
|
||||
restartFrom(pendingResetPositionUs);
|
||||
|
|
@ -365,7 +362,6 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
|
||||
@Override
|
||||
public int onLoadError(Loadable loadable, IOException e) {
|
||||
Chunk currentLoadable = currentLoadableHolder.chunk;
|
||||
long bytesLoaded = currentLoadable.bytesLoaded();
|
||||
boolean isMediaChunk = isMediaChunk(currentLoadable);
|
||||
boolean cancelable = !isMediaChunk || bytesLoaded == 0 || mediaChunks.size() > 1;
|
||||
|
|
@ -420,7 +416,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
}
|
||||
|
||||
private void clearCurrentLoadable() {
|
||||
currentLoadableHolder.chunk = null;
|
||||
currentLoadable = null;
|
||||
}
|
||||
|
||||
private void maybeStartLoading() {
|
||||
|
|
@ -429,44 +425,38 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
}
|
||||
|
||||
long now = SystemClock.elapsedRealtime();
|
||||
if (now - lastPreferredQueueSizeEvaluationTimeMs > 5000) {
|
||||
int queueSize = chunkSource.getPreferredQueueSize(downstreamPositionUs, readOnlyMediaChunks);
|
||||
// Never discard the first chunk.
|
||||
discardUpstreamMediaChunks(Math.max(1, queueSize));
|
||||
lastPreferredQueueSizeEvaluationTimeMs = now;
|
||||
}
|
||||
|
||||
long nextLoadPositionUs = getNextLoadPositionUs();
|
||||
|
||||
// Evaluate the operation if (a) we don't have the next chunk yet and we're not finished, or (b)
|
||||
// if the last evaluation was over 2000ms ago.
|
||||
if ((currentLoadableHolder.chunk == null && nextLoadPositionUs != -1)
|
||||
|| (now - lastPerformedBufferOperation > 2000)) {
|
||||
// Perform the evaluation.
|
||||
currentLoadableHolder.endOfStream = false;
|
||||
currentLoadableHolder.queueSize = readOnlyMediaChunks.size();
|
||||
long playbackPositionUs = pendingResetPositionUs != NO_RESET_PENDING ? pendingResetPositionUs
|
||||
: downstreamPositionUs;
|
||||
chunkSource.getChunkOperation(readOnlyMediaChunks, playbackPositionUs, currentLoadableHolder);
|
||||
loadingFinished = currentLoadableHolder.endOfStream;
|
||||
boolean chunksDiscarded = discardUpstreamMediaChunks(currentLoadableHolder.queueSize);
|
||||
lastPerformedBufferOperation = now;
|
||||
// Update the next load position as appropriate.
|
||||
if (currentLoadableHolder.chunk == null) {
|
||||
// Set loadPosition to -1 to indicate that we don't have anything to load.
|
||||
nextLoadPositionUs = -1;
|
||||
} else if (chunksDiscarded) {
|
||||
// Chunks were discarded, so we need to re-evaluate the load position.
|
||||
nextLoadPositionUs = getNextLoadPositionUs();
|
||||
}
|
||||
}
|
||||
|
||||
boolean nextLoader = loadControl.update(this, downstreamPositionUs, nextLoadPositionUs, false);
|
||||
if (!nextLoader) {
|
||||
// We're not allowed to start loading yet.
|
||||
boolean isNext = loadControl.update(this, downstreamPositionUs, nextLoadPositionUs, false);
|
||||
if (!isNext) {
|
||||
return;
|
||||
}
|
||||
|
||||
Chunk currentLoadable = currentLoadableHolder.chunk;
|
||||
if (currentLoadable == null) {
|
||||
// We're allowed to start loading, but have nothing to load.
|
||||
chunkSource.getNextChunk(mediaChunks.isEmpty() ? null : mediaChunks.getLast(),
|
||||
pendingResetPositionUs != NO_RESET_PENDING ? pendingResetPositionUs : downstreamPositionUs,
|
||||
nextChunkHolder);
|
||||
boolean endOfStream = nextChunkHolder.endOfStream;
|
||||
Chunk nextLoadable = nextChunkHolder.chunk;
|
||||
nextChunkHolder.clear();
|
||||
|
||||
if (endOfStream) {
|
||||
loadingFinished = true;
|
||||
loadControl.update(this, downstreamPositionUs, -1, false);
|
||||
return;
|
||||
}
|
||||
|
||||
currentLoadStartTimeMs = SystemClock.elapsedRealtime();
|
||||
if (nextLoadable == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
currentLoadStartTimeMs = now;
|
||||
currentLoadable = nextLoadable;
|
||||
if (isMediaChunk(currentLoadable)) {
|
||||
BaseMediaChunk mediaChunk = (BaseMediaChunk) currentLoadable;
|
||||
mediaChunk.init(sampleQueue);
|
||||
|
|
@ -514,6 +504,7 @@ public class ChunkSampleSource implements SampleSource, TrackStream, Loader.Call
|
|||
while (mediaChunks.size() > queueLength) {
|
||||
removed = mediaChunks.removeLast();
|
||||
startTimeUs = removed.startTimeUs;
|
||||
loadingFinished = false;
|
||||
}
|
||||
sampleQueue.discardUpstreamSamples(removed.getFirstSampleIndex());
|
||||
eventDispatcher.upstreamDiscarded(startTimeUs, endTimeUs);
|
||||
|
|
|
|||
|
|
@ -90,25 +90,32 @@ public interface ChunkSource {
|
|||
void continueBuffering(long playbackPositionUs);
|
||||
|
||||
/**
|
||||
* Updates the provided {@link ChunkOperationHolder} to contain the next operation that should
|
||||
* be performed by the calling {@link ChunkSampleSource}.
|
||||
* Evaluates whether {@link MediaChunk}s should be removed from the back of the queue.
|
||||
* <p>
|
||||
* This method should only be called when the source is enabled.
|
||||
* Removing {@link MediaChunk}s 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 queue A representation of the currently buffered {@link MediaChunk}s.
|
||||
* @param playbackPositionUs The current playback position. If the queue is empty then this
|
||||
* @param playbackPositionUs The current playback position.
|
||||
* @param queue The queue of buffered {@link MediaChunk}s.
|
||||
* @return The preferred queue size.
|
||||
*/
|
||||
int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue);
|
||||
|
||||
/**
|
||||
* Gets the next chunk to load.
|
||||
* <p>
|
||||
* If a chunk is available then {@link ChunkHolder#chunk} is set. If the end of the stream has
|
||||
* been reached then {@link ChunkHolder#endOfStream} is set. If a chunk is not available but the
|
||||
* end of the stream has not been reached, the {@link ChunkHolder} is not modified.
|
||||
*
|
||||
* @param previous The most recently loaded media chunk.
|
||||
* @param playbackPositionUs The current playback position. If {@code previous} is null then this
|
||||
* parameter is the position from which playback is expected to start (or restart) and hence
|
||||
* should be interpreted as a seek position.
|
||||
* @param out A holder for the next operation, whose {@link ChunkOperationHolder#endOfStream} is
|
||||
* initially set to false, whose {@link ChunkOperationHolder#queueSize} is initially equal to
|
||||
* the length of the queue, and whose {@link ChunkOperationHolder#chunk} is initially equal to
|
||||
* null or a {@link Chunk} previously supplied by the {@link ChunkSource} that the caller has
|
||||
* not yet finished loading. In the latter case the chunk can either be replaced or left
|
||||
* unchanged. Note that leaving the chunk unchanged is both preferred and more efficient than
|
||||
* replacing it with a new but identical chunk.
|
||||
* @param out A holder to populate.
|
||||
*/
|
||||
void getChunkOperation(List<? extends MediaChunk> queue, long playbackPositionUs,
|
||||
ChunkOperationHolder out);
|
||||
void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out);
|
||||
|
||||
/**
|
||||
* Invoked when the {@link ChunkSampleSource} has finished loading a chunk obtained from this
|
||||
|
|
|
|||
|
|
@ -42,26 +42,30 @@ public interface FormatEvaluator {
|
|||
* Update the supplied evaluation.
|
||||
* <p>
|
||||
* When invoked, {@code evaluation} must contain the currently selected format (null for an
|
||||
* initial evaluation), the most recent trigger (@link Chunk#TRIGGER_INITIAL} for an initial
|
||||
* evaluation) and the size of {@code queue}. The invocation will update the format and trigger,
|
||||
* and may also reduce {@link Evaluation#queueSize} to indicate that chunks should be discarded
|
||||
* from the end of the queue to allow re-buffering in a different format. The evaluation will
|
||||
* always retain the first chunk in the queue, if there is one.
|
||||
* initial evaluation) and the most recent trigger {@link Chunk#TRIGGER_INITIAL} for an initial
|
||||
* evaluation).
|
||||
*
|
||||
* @param queue A read only representation of currently buffered chunks. Must not be empty unless
|
||||
* the evaluation is at the start of playback or immediately follows a seek. All but the first
|
||||
* chunk may be discarded. A caller may pass a singleton list containing only the most
|
||||
* recently buffered chunk in the case that it does not support discarding of chunks.
|
||||
* @param playbackPositionUs The current playback position in microseconds.
|
||||
* @param switchingOverlapUs If switching format requires downloading overlapping media then this
|
||||
* is the duration of the required overlap in microseconds. 0 otherwise.
|
||||
* @param bufferedDurationUs The duration of media currently buffered in microseconds.
|
||||
* @param blacklistFlags An array whose length is equal to the number of available formats. A
|
||||
* {@code true} element indicates that a format is currently blacklisted and should not be
|
||||
* selected by the evaluation. At least one element must be {@code false}.
|
||||
* @param evaluation The evaluation to be updated.
|
||||
*/
|
||||
void evaluate(List<? extends MediaChunk> queue, long playbackPositionUs,
|
||||
long switchingOverlapUs, boolean[] blacklistFlags, Evaluation evaluation);
|
||||
void evaluateFormat(long bufferedDurationUs, boolean[] blacklistFlags,
|
||||
Evaluation evaluation);
|
||||
|
||||
/**
|
||||
* Evaluates whether to discard {@link MediaChunk}s from the queue.
|
||||
*
|
||||
* @param playbackPositionUs The current playback position in microseconds.
|
||||
* @param queue The queue of buffered {@link MediaChunk}s.
|
||||
* @param blacklistFlags An array whose length is equal to the number of available formats. A
|
||||
* {@code true} element indicates that a format is currently blacklisted and should not be
|
||||
* selected by the evaluation. At least one element must be {@code false}.
|
||||
* @return The preferred queue size.
|
||||
*/
|
||||
int evaluateQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue,
|
||||
boolean[] blacklistFlags);
|
||||
|
||||
/**
|
||||
* A format evaluation.
|
||||
|
|
@ -78,11 +82,6 @@ public interface FormatEvaluator {
|
|||
*/
|
||||
public int trigger;
|
||||
|
||||
/**
|
||||
* The desired size of the queue.
|
||||
*/
|
||||
public int queueSize;
|
||||
|
||||
public Evaluation() {
|
||||
trigger = Chunk.TRIGGER_INITIAL;
|
||||
}
|
||||
|
|
@ -128,8 +127,8 @@ public interface FormatEvaluator {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void evaluate(List<? extends MediaChunk> queue, long playbackPositionUs,
|
||||
long switchingOverlapUs, boolean[] blacklistFlags, Evaluation evaluation) {
|
||||
public void evaluateFormat(long bufferedDurationUs, boolean[] blacklistFlags,
|
||||
Evaluation evaluation) {
|
||||
// Count the number of non-blacklisted formats.
|
||||
int nonBlacklistedFormatCount = 0;
|
||||
for (int i = 0; i < blacklistFlags.length; i++) {
|
||||
|
|
@ -156,6 +155,12 @@ public interface FormatEvaluator {
|
|||
evaluation.format = newFormat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluateQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue,
|
||||
boolean[] blacklistFlags) {
|
||||
return queue.size();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -235,43 +240,18 @@ public interface FormatEvaluator {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void evaluate(List<? extends MediaChunk> queue, long playbackPositionUs,
|
||||
long switchingOverlapUs, boolean[] blacklistFlags, Evaluation evaluation) {
|
||||
long bufferedDurationUs = queue.isEmpty() ? 0
|
||||
: queue.get(queue.size() - 1).endTimeUs - playbackPositionUs;
|
||||
if (switchingOverlapUs > 0) {
|
||||
bufferedDurationUs = Math.max(0, bufferedDurationUs - switchingOverlapUs);
|
||||
}
|
||||
public void evaluateFormat(long bufferedDurationUs, boolean[] blacklistFlags,
|
||||
Evaluation evaluation) {
|
||||
Format current = evaluation.format;
|
||||
Format ideal = determineIdealFormat(formats, blacklistFlags,
|
||||
bandwidthMeter.getBitrateEstimate());
|
||||
boolean isHigher = ideal != null && current != null && ideal.bitrate > current.bitrate;
|
||||
boolean isLower = ideal != null && current != null && ideal.bitrate < current.bitrate;
|
||||
if (isHigher) {
|
||||
if (bufferedDurationUs < minDurationForQualityIncreaseUs) {
|
||||
// The ideal format is a higher quality, but we have insufficient buffer to
|
||||
// safely switch up. Defer switching up for now.
|
||||
ideal = current;
|
||||
} else if (bufferedDurationUs >= minDurationToRetainAfterDiscardUs) {
|
||||
// We're switching from an SD stream to a stream of higher resolution. Consider
|
||||
// discarding already buffered media chunks. Specifically, discard media chunks starting
|
||||
// from the first one that is of lower bandwidth, lower resolution and that is not HD.
|
||||
for (int i = 1; i < queue.size(); i++) {
|
||||
MediaChunk thisChunk = queue.get(i);
|
||||
long durationBeforeThisSegmentUs = thisChunk.startTimeUs - playbackPositionUs;
|
||||
if (durationBeforeThisSegmentUs >= minDurationToRetainAfterDiscardUs
|
||||
&& thisChunk.format.bitrate < ideal.bitrate
|
||||
&& thisChunk.format.height < ideal.height
|
||||
&& thisChunk.format.height < 720
|
||||
&& thisChunk.format.width < 1280) {
|
||||
// Discard chunks from this one onwards.
|
||||
evaluation.queueSize = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (isLower && current != null
|
||||
&& bufferedDurationUs >= maxDurationForQualityDecreaseUs) {
|
||||
boolean isHigher = current != null && ideal.bitrate > current.bitrate;
|
||||
boolean isLower = current != null && ideal.bitrate < current.bitrate;
|
||||
if (isHigher && bufferedDurationUs < minDurationForQualityIncreaseUs) {
|
||||
// The ideal format is a higher quality, but we have insufficient buffer to safely switch
|
||||
// up. Defer switching up for now.
|
||||
ideal = current;
|
||||
} else if (isLower && bufferedDurationUs >= maxDurationForQualityDecreaseUs) {
|
||||
// The ideal format is a lower quality, but we have sufficient buffer to defer switching
|
||||
// down for now.
|
||||
ideal = current;
|
||||
|
|
@ -282,6 +262,40 @@ public interface FormatEvaluator {
|
|||
evaluation.format = ideal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int evaluateQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue,
|
||||
boolean[] blacklistFlags) {
|
||||
if (queue.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
int queueSize = queue.size();
|
||||
long bufferedDurationUs = queue.get(queueSize - 1).endTimeUs - playbackPositionUs;
|
||||
if (bufferedDurationUs < minDurationToRetainAfterDiscardUs) {
|
||||
return queueSize;
|
||||
}
|
||||
Format current = queue.get(queueSize - 1).format;
|
||||
Format ideal = determineIdealFormat(formats, blacklistFlags,
|
||||
bandwidthMeter.getBitrateEstimate());
|
||||
if (ideal.bitrate <= current.bitrate) {
|
||||
return queueSize;
|
||||
}
|
||||
// Discard from the first SD chunk beyond minDurationToRetainAfterDiscardUs whose resolution
|
||||
// and bitrate are both lower than the ideal format.
|
||||
for (int i = 0; i < queueSize; i++) {
|
||||
MediaChunk thisChunk = queue.get(i);
|
||||
long durationBeforeThisSegmentUs = thisChunk.startTimeUs - playbackPositionUs;
|
||||
if (durationBeforeThisSegmentUs >= minDurationToRetainAfterDiscardUs
|
||||
&& thisChunk.format.bitrate < ideal.bitrate
|
||||
&& thisChunk.format.height < ideal.height
|
||||
&& thisChunk.format.height < 720
|
||||
&& thisChunk.format.width < 1280) {
|
||||
// Discard chunks from this one onwards.
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return queueSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the ideal format ignoring buffer health.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import com.google.android.exoplayer.TimeRange.StaticTimeRange;
|
|||
import com.google.android.exoplayer.TrackGroup;
|
||||
import com.google.android.exoplayer.chunk.Chunk;
|
||||
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper;
|
||||
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
|
||||
import com.google.android.exoplayer.chunk.ChunkHolder;
|
||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
||||
import com.google.android.exoplayer.chunk.ContainerMediaChunk;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||
|
|
@ -297,17 +297,24 @@ public class DashChunkSource implements ChunkSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final void getChunkOperation(List<? extends MediaChunk> queue, long playbackPositionUs,
|
||||
ChunkOperationHolder out) {
|
||||
public int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
|
||||
if (fatalError != null || enabledFormats.length < 2) {
|
||||
return queue.size();
|
||||
}
|
||||
return adaptiveFormatEvaluator.evaluateQueueSize(playbackPositionUs, queue,
|
||||
adaptiveFormatBlacklistFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) {
|
||||
if (fatalError != null) {
|
||||
out.chunk = null;
|
||||
return;
|
||||
}
|
||||
|
||||
evaluation.queueSize = queue.size();
|
||||
if (evaluation.format == null || !lastChunkWasInitialization) {
|
||||
if (enabledFormats.length > 1) {
|
||||
adaptiveFormatEvaluator.evaluate(queue, playbackPositionUs, 0, adaptiveFormatBlacklistFlags,
|
||||
long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
|
||||
adaptiveFormatEvaluator.evaluateFormat(bufferedDurationUs, adaptiveFormatBlacklistFlags,
|
||||
evaluation);
|
||||
} else {
|
||||
evaluation.format = enabledFormats[0];
|
||||
|
|
@ -316,26 +323,15 @@ public class DashChunkSource implements ChunkSource {
|
|||
}
|
||||
|
||||
Format selectedFormat = evaluation.format;
|
||||
out.queueSize = evaluation.queueSize;
|
||||
|
||||
if (selectedFormat == null) {
|
||||
out.chunk = null;
|
||||
return;
|
||||
} else if (out.queueSize == queue.size() && out.chunk != null
|
||||
&& out.chunk.format == selectedFormat) {
|
||||
// We already have a chunk, and the evaluation hasn't changed either the format or the size
|
||||
// of the queue. Leave unchanged.
|
||||
return;
|
||||
}
|
||||
|
||||
// In all cases where we return before instantiating a new chunk, we want out.chunk to be null.
|
||||
out.chunk = null;
|
||||
|
||||
boolean startingNewPeriod;
|
||||
PeriodHolder periodHolder;
|
||||
|
||||
availableRange.getCurrentBoundsUs(availableRangeValues);
|
||||
if (queue.isEmpty()) {
|
||||
if (previous == null) {
|
||||
if (live) {
|
||||
if (startAtLiveEdge) {
|
||||
// We want live streams to start at the live edge instead of the beginning of the
|
||||
|
|
@ -358,7 +354,6 @@ public class DashChunkSource implements ChunkSource {
|
|||
startAtLiveEdge = false;
|
||||
}
|
||||
|
||||
MediaChunk previous = queue.get(out.queueSize - 1);
|
||||
long nextSegmentStartTimeUs = previous.endTimeUs;
|
||||
if (live && nextSegmentStartTimeUs < availableRangeValues[0]) {
|
||||
// This is before the first chunk in the current manifest.
|
||||
|
|
@ -433,9 +428,9 @@ public class DashChunkSource implements ChunkSource {
|
|||
return;
|
||||
}
|
||||
|
||||
int segmentNum = queue.isEmpty() ? representationHolder.getSegmentNum(playbackPositionUs)
|
||||
int segmentNum = previous == null ? representationHolder.getSegmentNum(playbackPositionUs)
|
||||
: startingNewPeriod ? representationHolder.getFirstAvailableSegmentNum()
|
||||
: queue.get(out.queueSize - 1).getNextChunkIndex();
|
||||
: previous.getNextChunkIndex();
|
||||
Chunk nextMediaChunk = newMediaChunk(periodHolder, representationHolder, dataSource,
|
||||
selectedFormat, sampleFormat, segmentNum, evaluation.trigger);
|
||||
lastChunkWasInitialization = false;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import com.google.android.exoplayer.BehindLiveWindowException;
|
|||
import com.google.android.exoplayer.C;
|
||||
import com.google.android.exoplayer.Format;
|
||||
import com.google.android.exoplayer.chunk.Chunk;
|
||||
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
|
||||
import com.google.android.exoplayer.chunk.ChunkHolder;
|
||||
import com.google.android.exoplayer.chunk.DataChunk;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator.Evaluation;
|
||||
|
|
@ -317,21 +317,22 @@ public class HlsChunkSource {
|
|||
}
|
||||
|
||||
/**
|
||||
* Updates the provided {@link ChunkOperationHolder} to contain the next operation that should
|
||||
* be performed by the calling {@link HlsSampleSource}.
|
||||
* Gets the next chunk to load.
|
||||
* <p>
|
||||
* If a chunk is available then {@link ChunkHolder#chunk} is set. If the end of the stream has
|
||||
* been reached then {@link ChunkHolder#endOfStream} is set. If a chunk is not available but the
|
||||
* end of the stream has not been reached, the {@link ChunkHolder} is not modified.
|
||||
*
|
||||
* @param previousTsChunk The previously loaded chunk that the next chunk should follow.
|
||||
* @param playbackPositionUs The current playback position. If previousTsChunk is null then this
|
||||
* @param previous The most recently loaded media chunk.
|
||||
* @param playbackPositionUs The current playback position. If {@code previous} is null then this
|
||||
* parameter is the position from which playback is expected to start (or restart) and hence
|
||||
* should be interpreted as a seek position.
|
||||
* @param out The holder to populate with the result. {@link ChunkOperationHolder#queueSize} is
|
||||
* unused.
|
||||
* @param out A holder to populate.
|
||||
*/
|
||||
public void getChunkOperation(TsChunk previousTsChunk, long playbackPositionUs,
|
||||
ChunkOperationHolder out) {
|
||||
int variantIndex = getNextVariantIndex(previousTsChunk, playbackPositionUs);
|
||||
boolean switchingVariant = previousTsChunk != null
|
||||
&& variants[variantIndex].format != previousTsChunk.format;
|
||||
public void getNextChunk(TsChunk previous, long playbackPositionUs, ChunkHolder out) {
|
||||
int variantIndex = getNextVariantIndex(previous, playbackPositionUs);
|
||||
boolean switchingVariant = previous != null
|
||||
&& variants[variantIndex].format != previous.format;
|
||||
|
||||
HlsMediaPlaylist mediaPlaylist = variantPlaylists[variantIndex];
|
||||
if (mediaPlaylist == null) {
|
||||
|
|
@ -342,11 +343,10 @@ public class HlsChunkSource {
|
|||
|
||||
int chunkMediaSequence = 0;
|
||||
if (live) {
|
||||
if (previousTsChunk == null) {
|
||||
if (previous == null) {
|
||||
chunkMediaSequence = getLiveStartChunkMediaSequence(variantIndex);
|
||||
} else {
|
||||
chunkMediaSequence = switchingVariant ? previousTsChunk.chunkIndex
|
||||
: previousTsChunk.chunkIndex + 1;
|
||||
chunkMediaSequence = switchingVariant ? previous.chunkIndex : previous.chunkIndex + 1;
|
||||
if (chunkMediaSequence < mediaPlaylist.mediaSequence) {
|
||||
fatalError = new BehindLiveWindowException();
|
||||
return;
|
||||
|
|
@ -354,12 +354,11 @@ public class HlsChunkSource {
|
|||
}
|
||||
} else {
|
||||
// Not live.
|
||||
if (previousTsChunk == null) {
|
||||
if (previous == null) {
|
||||
chunkMediaSequence = Util.binarySearchFloor(mediaPlaylist.segments, playbackPositionUs,
|
||||
true, true) + mediaPlaylist.mediaSequence;
|
||||
} else {
|
||||
chunkMediaSequence = switchingVariant ? previousTsChunk.chunkIndex
|
||||
: previousTsChunk.chunkIndex + 1;
|
||||
chunkMediaSequence = switchingVariant ? previous.chunkIndex : previous.chunkIndex + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -398,12 +397,12 @@ public class HlsChunkSource {
|
|||
// Compute start and end times, and the sequence number of the next chunk.
|
||||
long startTimeUs;
|
||||
if (live) {
|
||||
if (previousTsChunk == null) {
|
||||
if (previous == null) {
|
||||
startTimeUs = 0;
|
||||
} else if (switchingVariant) {
|
||||
startTimeUs = previousTsChunk.startTimeUs;
|
||||
startTimeUs = previous.startTimeUs;
|
||||
} else {
|
||||
startTimeUs = previousTsChunk.endTimeUs;
|
||||
startTimeUs = previous.endTimeUs;
|
||||
}
|
||||
} else /* Not live */ {
|
||||
startTimeUs = segment.startTimeUs;
|
||||
|
|
@ -439,9 +438,9 @@ public class HlsChunkSource {
|
|||
Extractor extractor = new WebvttExtractor(format.language, timestampAdjuster);
|
||||
extractorWrapper = new HlsExtractorWrapper(trigger, format, startTimeUs, extractor,
|
||||
switchingVariant);
|
||||
} else if (previousTsChunk == null
|
||||
|| previousTsChunk.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|
||||
|| format != previousTsChunk.format) {
|
||||
} else if (previous == null
|
||||
|| previous.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|
||||
|| format != previous.format) {
|
||||
// MPEG-2 TS segments, but we need a new extractor.
|
||||
PtsTimestampAdjuster timestampAdjuster = timestampAdjusterProvider.getAdjuster(true,
|
||||
segment.discontinuitySequenceNumber, startTimeUs);
|
||||
|
|
@ -467,7 +466,7 @@ public class HlsChunkSource {
|
|||
switchingVariant);
|
||||
} else {
|
||||
// MPEG-2 TS segments, and we need to continue using the same extractor.
|
||||
extractorWrapper = previousTsChunk.extractorWrapper;
|
||||
extractorWrapper = previous.extractorWrapper;
|
||||
}
|
||||
|
||||
out.chunk = new TsChunk(dataSource, dataSpec, trigger, format, startTimeUs, endTimeUs,
|
||||
|
|
@ -596,20 +595,19 @@ public class HlsChunkSource {
|
|||
return false;
|
||||
}
|
||||
|
||||
private int getNextVariantIndex(TsChunk previousTsChunk, long playbackPositionUs) {
|
||||
private int getNextVariantIndex(TsChunk previous, long playbackPositionUs) {
|
||||
clearStaleBlacklistedVariants();
|
||||
long switchingOverlapUs;
|
||||
List<TsChunk> queue;
|
||||
if (previousTsChunk != null) {
|
||||
switchingOverlapUs = previousTsChunk.endTimeUs - previousTsChunk.startTimeUs;
|
||||
queue = Collections.singletonList(previousTsChunk);
|
||||
long bufferedDurationUs;
|
||||
if (previous != null) {
|
||||
// Use start time of the previous chunk rather than its end time because switching format will
|
||||
// require downloading overlapping segments.
|
||||
bufferedDurationUs = Math.max(0, previous.startTimeUs - playbackPositionUs);
|
||||
} else {
|
||||
switchingOverlapUs = 0;
|
||||
queue = Collections.<TsChunk>emptyList();
|
||||
bufferedDurationUs = 0;
|
||||
}
|
||||
if (enabledVariants.length > 1) {
|
||||
adaptiveFormatEvaluator.evaluate(queue, playbackPositionUs, switchingOverlapUs,
|
||||
enabledVariantBlacklistFlags, evaluation);
|
||||
adaptiveFormatEvaluator.evaluateFormat(bufferedDurationUs, enabledVariantBlacklistFlags,
|
||||
evaluation);
|
||||
} else {
|
||||
evaluation.format = enabledVariants[0].format;
|
||||
evaluation.trigger = Chunk.TRIGGER_MANUAL;
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import com.google.android.exoplayer.TrackGroupArray;
|
|||
import com.google.android.exoplayer.TrackSelection;
|
||||
import com.google.android.exoplayer.TrackStream;
|
||||
import com.google.android.exoplayer.chunk.Chunk;
|
||||
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
|
||||
import com.google.android.exoplayer.chunk.ChunkHolder;
|
||||
import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener;
|
||||
import com.google.android.exoplayer.chunk.ChunkSampleSourceEventListener.EventDispatcher;
|
||||
import com.google.android.exoplayer.upstream.Loader;
|
||||
|
|
@ -63,7 +63,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||
private final HlsChunkSource chunkSource;
|
||||
private final LinkedList<HlsExtractorWrapper> extractors;
|
||||
private final int bufferSizeContribution;
|
||||
private final ChunkOperationHolder chunkOperationHolder;
|
||||
private final ChunkHolder nextChunkHolder;
|
||||
private final EventDispatcher eventDispatcher;
|
||||
private final LoadControl loadControl;
|
||||
|
||||
|
|
@ -116,7 +116,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||
loader = new Loader("Loader:HLS", minLoadableRetryCount);
|
||||
eventDispatcher = new EventDispatcher(eventHandler, eventListener, eventSourceId);
|
||||
extractors = new LinkedList<>();
|
||||
chunkOperationHolder = new ChunkOperationHolder();
|
||||
nextChunkHolder = new ChunkHolder();
|
||||
}
|
||||
|
||||
// SampleSource implementation.
|
||||
|
|
@ -301,9 +301,7 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||
|
||||
/* package */ void maybeThrowError() throws IOException {
|
||||
loader.maybeThrowError();
|
||||
if (currentLoadable == null) {
|
||||
chunkSource.maybeThrowError();
|
||||
}
|
||||
chunkSource.maybeThrowError();
|
||||
}
|
||||
|
||||
/* package */ long readReset(int group) {
|
||||
|
|
@ -641,12 +639,12 @@ public final class HlsSampleSource implements SampleSource, Loader.Callback {
|
|||
return;
|
||||
}
|
||||
|
||||
chunkSource.getChunkOperation(previousTsLoadable,
|
||||
chunkSource.getNextChunk(previousTsLoadable,
|
||||
pendingResetPositionUs != NO_RESET_PENDING ? pendingResetPositionUs : downstreamPositionUs,
|
||||
chunkOperationHolder);
|
||||
boolean endOfStream = chunkOperationHolder.endOfStream;
|
||||
Chunk nextLoadable = chunkOperationHolder.chunk;
|
||||
chunkOperationHolder.clear();
|
||||
nextChunkHolder);
|
||||
boolean endOfStream = nextChunkHolder.endOfStream;
|
||||
Chunk nextLoadable = nextChunkHolder.chunk;
|
||||
nextChunkHolder.clear();
|
||||
|
||||
if (endOfStream) {
|
||||
loadingFinished = true;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import com.google.android.exoplayer.Format.DecreasingBandwidthComparator;
|
|||
import com.google.android.exoplayer.TrackGroup;
|
||||
import com.google.android.exoplayer.chunk.Chunk;
|
||||
import com.google.android.exoplayer.chunk.ChunkExtractorWrapper;
|
||||
import com.google.android.exoplayer.chunk.ChunkOperationHolder;
|
||||
import com.google.android.exoplayer.chunk.ChunkHolder;
|
||||
import com.google.android.exoplayer.chunk.ChunkSource;
|
||||
import com.google.android.exoplayer.chunk.ContainerMediaChunk;
|
||||
import com.google.android.exoplayer.chunk.FormatEvaluator;
|
||||
|
|
@ -208,16 +208,23 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||
}
|
||||
|
||||
@Override
|
||||
public final void getChunkOperation(List<? extends MediaChunk> queue, long playbackPositionUs,
|
||||
ChunkOperationHolder out) {
|
||||
public int getPreferredQueueSize(long playbackPositionUs, List<? extends MediaChunk> queue) {
|
||||
if (fatalError != null || enabledFormats.length < 2) {
|
||||
return queue.size();
|
||||
}
|
||||
return adaptiveFormatEvaluator.evaluateQueueSize(playbackPositionUs, queue,
|
||||
adaptiveFormatBlacklistFlags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void getNextChunk(MediaChunk previous, long playbackPositionUs, ChunkHolder out) {
|
||||
if (fatalError != null) {
|
||||
out.chunk = null;
|
||||
return;
|
||||
}
|
||||
|
||||
evaluation.queueSize = queue.size();
|
||||
if (enabledFormats.length > 1) {
|
||||
adaptiveFormatEvaluator.evaluate(queue, playbackPositionUs, 0, adaptiveFormatBlacklistFlags,
|
||||
long bufferedDurationUs = previous != null ? (previous.endTimeUs - playbackPositionUs) : 0;
|
||||
adaptiveFormatEvaluator.evaluateFormat(bufferedDurationUs, adaptiveFormatBlacklistFlags,
|
||||
evaluation);
|
||||
} else {
|
||||
evaluation.format = enabledFormats[0];
|
||||
|
|
@ -225,21 +232,10 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||
}
|
||||
|
||||
Format selectedFormat = evaluation.format;
|
||||
out.queueSize = evaluation.queueSize;
|
||||
|
||||
if (selectedFormat == null) {
|
||||
out.chunk = null;
|
||||
return;
|
||||
} else if (out.queueSize == queue.size() && out.chunk != null
|
||||
&& out.chunk.format == selectedFormat) {
|
||||
// We already have a chunk, and the evaluation hasn't changed either the format or the size
|
||||
// of the queue. Leave unchanged.
|
||||
return;
|
||||
}
|
||||
|
||||
// In all cases where we return before instantiating a new chunk, we want out.chunk to be null.
|
||||
out.chunk = null;
|
||||
|
||||
StreamElement streamElement = currentManifest.streamElements[elementIndex];
|
||||
if (streamElement.chunkCount == 0) {
|
||||
if (currentManifest.isLive) {
|
||||
|
|
@ -251,13 +247,12 @@ public class SmoothStreamingChunkSource implements ChunkSource {
|
|||
}
|
||||
|
||||
int chunkIndex;
|
||||
if (queue.isEmpty()) {
|
||||
if (previous == null) {
|
||||
if (live) {
|
||||
playbackPositionUs = getLiveSeekPosition(currentManifest, liveEdgeLatencyUs);
|
||||
}
|
||||
chunkIndex = streamElement.getChunkIndex(playbackPositionUs);
|
||||
} else {
|
||||
MediaChunk previous = queue.get(out.queueSize - 1);
|
||||
chunkIndex = previous.chunkIndex + 1 - currentManifestChunkOffset;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue