1515 */
1616package androidx .media3 .exoplayer .trackselection ;
1717
18+ import static com .google .common .base .Preconditions .checkNotNull ;
1819import static java .lang .Math .max ;
1920import static java .lang .Math .min ;
2021
3940import com .google .common .collect .MultimapBuilder ;
4041import java .util .ArrayList ;
4142import java .util .Arrays ;
43+ import java .util .Comparator ;
4244import java .util .List ;
4345
4446/**
4547 * A bandwidth based adaptive {@link ExoTrackSelection}, whose selected track is updated to be the
46- * one of highest quality given the current network conditions and the state of the buffer.
48+ * highest priority track given the current network conditions and the state of the buffer.
4749 */
4850@ UnstableApi
4951public class AdaptiveTrackSelection extends BaseTrackSelection {
@@ -61,6 +63,7 @@ public static class Factory implements ExoTrackSelection.Factory {
6163 private final float bandwidthFraction ;
6264 private final float bufferedFractionToLiveEdgeForQualityIncrease ;
6365 private final Clock clock ;
66+ private Comparator <Format > trackFormatComparator ;
6467
6568 /** Creates an adaptive track selection factory with default parameters. */
6669 public Factory () {
@@ -227,6 +230,19 @@ public Factory(
227230 this .bufferedFractionToLiveEdgeForQualityIncrease =
228231 bufferedFractionToLiveEdgeForQualityIncrease ;
229232 this .clock = clock ;
233+ this .trackFormatComparator = BaseTrackSelection .DEFAULT_FORMAT_COMPARATOR ;
234+ }
235+
236+ /**
237+ * Sets the comparator used to order formats in adaptive track selections.
238+ * The comparator order controls which formats are considered first during adaptation.
239+ *
240+ * @param trackFormatComparator Comparator used to order selected formats.
241+ * @return This factory, for convenience.
242+ */
243+ public Factory setTrackFormatComparator (Comparator <Format > trackFormatComparator ) {
244+ this .trackFormatComparator = checkNotNull (trackFormatComparator );
245+ return this ;
230246 }
231247
232248 @ Override
@@ -289,7 +305,8 @@ protected AdaptiveTrackSelection createAdaptiveTrackSelection(
289305 bandwidthFraction ,
290306 bufferedFractionToLiveEdgeForQualityIncrease ,
291307 adaptationCheckpoints ,
292- clock );
308+ clock ,
309+ trackFormatComparator );
293310 }
294311 }
295312
@@ -389,7 +406,71 @@ protected AdaptiveTrackSelection(
389406 float bufferedFractionToLiveEdgeForQualityIncrease ,
390407 List <AdaptationCheckpoint > adaptationCheckpoints ,
391408 Clock clock ) {
392- super (group , tracks , type );
409+ this (
410+ group ,
411+ tracks ,
412+ type ,
413+ bandwidthMeter ,
414+ minDurationForQualityIncreaseMs ,
415+ maxDurationForQualityDecreaseMs ,
416+ minDurationToRetainAfterDiscardMs ,
417+ maxWidthToDiscard ,
418+ maxHeightToDiscard ,
419+ bandwidthFraction ,
420+ bufferedFractionToLiveEdgeForQualityIncrease ,
421+ adaptationCheckpoints ,
422+ clock ,
423+ BaseTrackSelection .DEFAULT_FORMAT_COMPARATOR );
424+ }
425+
426+ /**
427+ * @param group The {@link TrackGroup}.
428+ * @param tracks The indices of the selected tracks within the {@link TrackGroup}. Must not be
429+ * empty. May be in any order.
430+ * @param type The type that will be returned from {@link TrackSelection#getType()}.
431+ * @param bandwidthMeter Provides an estimate of the currently available bandwidth.
432+ * @param minDurationForQualityIncreaseMs The minimum duration of buffered data required for the
433+ * selected track to switch to one of higher quality.
434+ * @param maxDurationForQualityDecreaseMs The maximum duration of buffered data required for the
435+ * selected track to switch to one of lower quality.
436+ * @param minDurationToRetainAfterDiscardMs When switching to a video track of higher quality, the
437+ * selection may indicate that media already buffered at the lower quality can be discarded to
438+ * speed up the switch. This is the minimum duration of media that must be retained at the
439+ * lower quality. It must be at least {@code minDurationForQualityIncreaseMs}.
440+ * @param maxWidthToDiscard The maximum video width that the selector may discard from the buffer
441+ * to speed up switching to a higher quality.
442+ * @param maxHeightToDiscard The maximum video height that the selector may discard from the
443+ * buffer to speed up switching to a higher quality.
444+ * @param bandwidthFraction The fraction of the available bandwidth that the selection should
445+ * consider available for use. Setting to a value less than 1 is recommended to account for
446+ * inaccuracies in the bandwidth estimator.
447+ * @param bufferedFractionToLiveEdgeForQualityIncrease For live streaming, the fraction of the
448+ * duration from current playback position to the live edge that has to be buffered before the
449+ * selected track can be switched to one of higher quality. This parameter is only applied
450+ * when the playback position is closer to the live edge than {@code
451+ * minDurationForQualityIncreaseMs}, which would otherwise prevent switching to a higher
452+ * quality from happening.
453+ * @param adaptationCheckpoints The {@link AdaptationCheckpoint checkpoints} that can be used to
454+ * calculate available bandwidth for this selection.
455+ * @param clock The {@link Clock}.
456+ * @param trackFormatComparator Comparator used to order selected formats.
457+ */
458+ protected AdaptiveTrackSelection (
459+ TrackGroup group ,
460+ int [] tracks ,
461+ @ Type int type ,
462+ BandwidthMeter bandwidthMeter ,
463+ long minDurationForQualityIncreaseMs ,
464+ long maxDurationForQualityDecreaseMs ,
465+ long minDurationToRetainAfterDiscardMs ,
466+ int maxWidthToDiscard ,
467+ int maxHeightToDiscard ,
468+ float bandwidthFraction ,
469+ float bufferedFractionToLiveEdgeForQualityIncrease ,
470+ List <AdaptationCheckpoint > adaptationCheckpoints ,
471+ Clock clock ,
472+ Comparator <Format > trackFormatComparator ) {
473+ super (group , tracks , type , trackFormatComparator );
393474 if (minDurationToRetainAfterDiscardMs < minDurationForQualityIncreaseMs ) {
394475 Log .w (
395476 TAG ,
@@ -442,11 +523,12 @@ public void updateSelectedTrack(
442523 MediaChunkIterator [] mediaChunkIterators ) {
443524 long nowMs = clock .elapsedRealtime ();
444525 long chunkDurationUs = getNextChunkDurationUs (mediaChunkIterators , queue );
526+ long effectiveBitrate = getAllocatedBandwidth (chunkDurationUs );
445527
446528 // Make initial selection
447529 if (reason == C .SELECTION_REASON_UNKNOWN ) {
448530 reason = C .SELECTION_REASON_INITIAL ;
449- selectedIndex = determineIdealSelectedIndex (nowMs , chunkDurationUs );
531+ selectedIndex = determineIdealSelectedIndexForEffectiveBitrate (nowMs , effectiveBitrate );
450532 return ;
451533 }
452534
@@ -458,23 +540,24 @@ public void updateSelectedTrack(
458540 previousSelectedIndex = formatIndexOfPreviousChunk ;
459541 previousReason = Iterables .getLast (queue ).trackSelectionReason ;
460542 }
461- int newSelectedIndex = determineIdealSelectedIndex (nowMs , chunkDurationUs );
543+ int newSelectedIndex = determineIdealSelectedIndexForEffectiveBitrate (nowMs , effectiveBitrate );
462544 if (newSelectedIndex != previousSelectedIndex
463545 && !isTrackExcluded (previousSelectedIndex , nowMs )) {
464- // Revert back to the previous selection if conditions are not suitable for switching.
465- Format currentFormat = getFormat (previousSelectedIndex );
466- Format selectedFormat = getFormat (newSelectedIndex );
546+ // Revert back to the previous selection if conditions are not suitable for switching. Do not
547+ // defer a switch when the previous format no longer fits into available bandwidth.
467548 long minDurationForQualityIncreaseUs =
468549 minDurationForQualityIncreaseUs (availableDurationUs , chunkDurationUs );
469- if (selectedFormat . bitrate > currentFormat . bitrate
550+ if (newSelectedIndex < previousSelectedIndex
470551 && bufferedDurationUs < minDurationForQualityIncreaseUs ) {
471- // The selected track is a higher quality , but we have insufficient buffer to safely switch
552+ // The selected track is higher priority , but we have insufficient buffer to safely switch
472553 // up. Defer switching up for now.
473554 newSelectedIndex = previousSelectedIndex ;
474- } else if (selectedFormat .bitrate < currentFormat .bitrate
555+ } else if (newSelectedIndex > previousSelectedIndex
556+ && (isUsingDefaultFormatComparator ()
557+ || isTrackSelectable (previousSelectedIndex , effectiveBitrate ))
475558 && bufferedDurationUs >= maxDurationForQualityDecreaseUs ) {
476- // The selected track is a lower quality , but we have sufficient buffer to defer switching
477- // down for now .
559+ // The selected track is lower priority , but we have sufficient buffer to defer switching
560+ // down while preserving existing behavior for default ordering .
478561 newSelectedIndex = previousSelectedIndex ;
479562 }
480563 }
@@ -597,19 +680,33 @@ protected long getMinDurationToRetainAfterDiscardUs() {
597680 * if unknown.
598681 */
599682 private int determineIdealSelectedIndex (long nowMs , long chunkDurationUs ) {
600- long effectiveBitrate = getAllocatedBandwidth (chunkDurationUs );
601- int lowestBitrateAllowedIndex = 0 ;
683+ return determineIdealSelectedIndexForEffectiveBitrate (nowMs , getAllocatedBandwidth (chunkDurationUs ));
684+ }
685+
686+ private int determineIdealSelectedIndexForEffectiveBitrate (long nowMs , long effectiveBitrate ) {
687+ int lowestBitrateAllowedIndex = C .INDEX_UNSET ;
688+ int lowestBitrate = Integer .MAX_VALUE ;
602689 for (int i = 0 ; i < length ; i ++) {
603690 if (nowMs == Long .MIN_VALUE || !isTrackExcluded (i , nowMs )) {
604691 Format format = getFormat (i );
605692 if (canSelectFormat (format , format .bitrate , effectiveBitrate )) {
606693 return i ;
607- } else {
694+ }
695+ int formatBitrate = format .bitrate == Format .NO_VALUE ? 0 : format .bitrate ;
696+ if (formatBitrate <= lowestBitrate ) {
697+ // fallback semantics by selecting the lowest bitrate non-excluded track when no track
698+ // is selectable within available bandwidth.
608699 lowestBitrateAllowedIndex = i ;
700+ lowestBitrate = formatBitrate ;
609701 }
610702 }
611703 }
612- return lowestBitrateAllowedIndex ;
704+ return lowestBitrateAllowedIndex == C .INDEX_UNSET ? 0 : lowestBitrateAllowedIndex ;
705+ }
706+
707+ private boolean isTrackSelectable (int index , long effectiveBitrate ) {
708+ Format format = getFormat (index );
709+ return canSelectFormat (format , format .bitrate , effectiveBitrate );
613710 }
614711
615712 private long minDurationForQualityIncreaseUs (long availableDurationUs , long chunkDurationUs ) {
0 commit comments