diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java index 2c74d2412d..e1ccf5a0f7 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/AdaptiveTrackSelection.java @@ -255,7 +255,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { private final Clock clock; private final Format[] formats; private final int[] formatBitrates; - private final int[] estimatedBitrates; + private final int[] trackBitrates; private TrackBitrateEstimator trackBitrateEstimator; private float playbackSpeed; @@ -338,7 +338,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { trackBitrateEstimator = TrackBitrateEstimator.DEFAULT; formats = new Format[length]; formatBitrates = new int[length]; - estimatedBitrates = new int[length]; + trackBitrates = new int[length]; for (int i = 0; i < length; i++) { @SuppressWarnings("nullness:method.invocation.invalid") Format format = getFormat(i); @@ -380,11 +380,12 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { MediaChunkIterator[] mediaChunkIterators) { long nowMs = clock.elapsedRealtime(); - trackBitrateEstimator.getBitrates(formats, queue, mediaChunkIterators, estimatedBitrates); + // Update the estimated track bitrates. + trackBitrateEstimator.getBitrates(formats, queue, mediaChunkIterators, trackBitrates); // Stash the current selection, then make a new one. int currentSelectedIndex = selectedIndex; - selectedIndex = determineIdealSelectedIndex(nowMs, estimatedBitrates); + selectedIndex = determineIdealSelectedIndex(nowMs, trackBitrates); if (selectedIndex == currentSelectedIndex) { return; } @@ -429,10 +430,10 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { @Override public int evaluateQueueSize(long playbackPositionUs, List queue) { long nowMs = clock.elapsedRealtime(); - if (lastBufferEvaluationMs != C.TIME_UNSET - && nowMs - lastBufferEvaluationMs < minTimeBetweenBufferReevaluationMs) { + if (!shouldEvaluateQueueSize(nowMs)) { return queue.size(); } + lastBufferEvaluationMs = nowMs; if (queue.isEmpty()) { return 0; @@ -443,6 +444,7 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { long playoutBufferedDurationBeforeLastChunkUs = Util.getPlayoutDurationForMediaDuration( lastChunk.startTimeUs - playbackPositionUs, playbackSpeed); + long minDurationToRetainAfterDiscardUs = getMinDurationToRetainAfterDiscardUs(); if (playoutBufferedDurationBeforeLastChunkUs < minDurationToRetainAfterDiscardUs) { return queueSize; } @@ -468,19 +470,59 @@ public class AdaptiveTrackSelection extends BaseTrackSelection { return queueSize; } + /** + * Called when updating the selected track to determine whether a candidate track can be selected. + * + * @param format The {@link Format} of the candidate track. + * @param trackBitrate The estimated bitrate of the track. May differ from {@link Format#bitrate} + * if a more accurate estimate of the current track bitrate is available. + * @param playbackSpeed The current playback speed. + * @param effectiveBitrate The bitrate available to this selection. + * @return Whether this {@link Format} can be selected. + */ + @SuppressWarnings("unused") + protected boolean canSelectFormat( + Format format, int trackBitrate, float playbackSpeed, long effectiveBitrate) { + return Math.round(trackBitrate * playbackSpeed) <= effectiveBitrate; + } + + /** + * Called from {@link #evaluateQueueSize(long, List)} to determine whether an evaluation should be + * performed. + * + * @param nowMs The current value of {@link Clock#elapsedRealtime()}. + * @return Whether an evaluation should be performed. + */ + protected boolean shouldEvaluateQueueSize(long nowMs) { + return lastBufferEvaluationMs == C.TIME_UNSET + || nowMs - lastBufferEvaluationMs >= minTimeBetweenBufferReevaluationMs; + } + + /** + * Called from {@link #evaluateQueueSize(long, List)} to determine the minimum duration of buffer + * to retain after discarding chunks. + * + * @return The minimum duration of buffer to retain after discarding chunks, in microseconds. + */ + protected long getMinDurationToRetainAfterDiscardUs() { + return minDurationToRetainAfterDiscardUs; + } + /** * Computes the ideal selected index ignoring buffer health. * * @param nowMs The current time in the timebase of {@link Clock#elapsedRealtime()}, or {@link * Long#MIN_VALUE} to ignore blacklisting. - * @param bitrates Track bitrates. + * @param trackBitrates The estimated track bitrates. May differ from format bitrates if more + * accurate estimates of the current track bitrates are available. */ - private int determineIdealSelectedIndex(long nowMs, int[] bitrates) { + private int determineIdealSelectedIndex(long nowMs, int[] trackBitrates) { long effectiveBitrate = (long) (bandwidthMeter.getBitrateEstimate() * bandwidthFraction); int lowestBitrateNonBlacklistedIndex = 0; for (int i = 0; i < length; i++) { if (nowMs == Long.MIN_VALUE || !isBlacklisted(i, nowMs)) { - if (Math.round(bitrates[i] * playbackSpeed) <= effectiveBitrate) { + Format format = getFormat(i); + if (canSelectFormat(format, trackBitrates[i], playbackSpeed, effectiveBitrate)) { return i; } else { lowestBitrateNonBlacklistedIndex = i;