public abstract class

MappingTrackSelector

extends TrackSelector

 java.lang.Object

androidx.media3.exoplayer.trackselection.TrackSelector

↳androidx.media3.exoplayer.trackselection.MappingTrackSelector

Subclasses:

DefaultTrackSelector, FakeTrackSelector

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-exoplayer', version: '1.0.0-alpha03'

  • groupId: androidx.media3
  • artifactId: media3-exoplayer
  • version: 1.0.0-alpha03

Artifact androidx.media3:media3-exoplayer:1.0.0-alpha03 it located at Google repository (https://maven.google.com/)

Overview

Base class for TrackSelectors that first establish a mapping between TrackGroups and Renderers, and then from that mapping create a ExoTrackSelection for each renderer.

Summary

Constructors
publicMappingTrackSelector()

Methods
public final MappingTrackSelector.MappedTrackInfogetCurrentMappedTrackInfo()

Returns the mapping information for the currently active track selection, or null if no selection is currently active.

public abstract voidonSelectionActivated(java.lang.Object info)

Called by the player when a TrackSelectorResult previously generated by TrackSelector is activated.

protected abstract <any>selectTracks(MappingTrackSelector.MappedTrackInfo mappedTrackInfo, int[][][] rendererFormatSupports[][][], int[] rendererMixedMimeTypeAdaptationSupport[], MediaSource.MediaPeriodId mediaPeriodId, Timeline timeline)

Given mapped track information, returns a track selection and configuration for each renderer.

public abstract TrackSelectorResultselectTracks(RendererCapabilities rendererCapabilities[], TrackGroupArray trackGroups, MediaSource.MediaPeriodId periodId, Timeline timeline)

Called by the player to perform a track selection.

from TrackSelectorgetBandwidthMeter, getParameters, init, invalidate, isSetParametersSupported, setParameters
from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Constructors

public MappingTrackSelector()

Methods

public final MappingTrackSelector.MappedTrackInfo getCurrentMappedTrackInfo()

Returns the mapping information for the currently active track selection, or null if no selection is currently active.

public abstract void onSelectionActivated(java.lang.Object info)

Called by the player when a TrackSelectorResult previously generated by TrackSelector is activated.

Parameters:

info: The value of TrackSelectorResult.info in the activated selection.

public abstract TrackSelectorResult selectTracks(RendererCapabilities rendererCapabilities[], TrackGroupArray trackGroups, MediaSource.MediaPeriodId periodId, Timeline timeline)

Called by the player to perform a track selection.

Parameters:

rendererCapabilities: The RendererCapabilities of the renderers for which tracks are to be selected.
trackGroups: The available track groups.
periodId: The MediaSource.MediaPeriodId of the period for which tracks are to be selected.
timeline: The Timeline holding the period for which tracks are to be selected.

Returns:

A TrackSelectorResult describing the track selections.

protected abstract <any> selectTracks(MappingTrackSelector.MappedTrackInfo mappedTrackInfo, int[][][] rendererFormatSupports[][][], int[] rendererMixedMimeTypeAdaptationSupport[], MediaSource.MediaPeriodId mediaPeriodId, Timeline timeline)

Given mapped track information, returns a track selection and configuration for each renderer.

Parameters:

mappedTrackInfo: Mapped track information.
rendererFormatSupports: The RendererCapabilities.Capabilities for ach mapped track, indexed by renderer, track group and track (in that order).
rendererMixedMimeTypeAdaptationSupport: The RendererCapabilities.AdaptiveSupport for mixed MIME type adaptation for the renderer.
mediaPeriodId: The MediaSource.MediaPeriodId of the period for which tracks are to be selected.
timeline: The Timeline holding the period for which tracks are to be selected.

Returns:

A pair consisting of the track selections and configurations for each renderer. A null configuration indicates the renderer should be disabled, in which case the track selection will also be null. A track selection may also be null for a non-disabled renderer if RendererCapabilities.getTrackType() is C.TRACK_TYPE_NONE.

Source

/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package androidx.media3.exoplayer.trackselection;

import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.ElementType.TYPE_USE;

import android.util.Pair;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C;
import androidx.media3.common.C.FormatSupport;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.Timeline;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.TrackGroupArray;
import androidx.media3.common.TrackSelection;
import androidx.media3.common.TracksInfo;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.ExoPlaybackException;
import androidx.media3.exoplayer.Renderer;
import androidx.media3.exoplayer.RendererCapabilities;
import androidx.media3.exoplayer.RendererCapabilities.AdaptiveSupport;
import androidx.media3.exoplayer.RendererCapabilities.Capabilities;
import androidx.media3.exoplayer.RendererConfiguration;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import com.google.common.collect.ImmutableList;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Arrays;
import org.checkerframework.checker.nullness.compatqual.NullableType;

/**
 * Base class for {@link TrackSelector}s that first establish a mapping between {@link TrackGroup}s
 * and {@link Renderer}s, and then from that mapping create a {@link ExoTrackSelection} for each
 * renderer.
 */
@UnstableApi
public abstract class MappingTrackSelector extends TrackSelector {

  /** Provides mapped track information for each renderer. */
  public static final class MappedTrackInfo {

    /**
     * Levels of renderer support. Higher numerical values indicate higher levels of support. One of
     * {@link #RENDERER_SUPPORT_NO_TRACKS}, {@link #RENDERER_SUPPORT_UNSUPPORTED_TRACKS}, {@link
     * #RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS} or {@link #RENDERER_SUPPORT_PLAYABLE_TRACKS}.
     */
    // @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
    // with Kotlin usages from before TYPE_USE was added.
    @Documented
    @Retention(RetentionPolicy.SOURCE)
    @Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
    @IntDef({
      RENDERER_SUPPORT_NO_TRACKS,
      RENDERER_SUPPORT_UNSUPPORTED_TRACKS,
      RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS,
      RENDERER_SUPPORT_PLAYABLE_TRACKS
    })
    public @interface RendererSupport {}
    /** The renderer does not have any associated tracks. */
    public static final int RENDERER_SUPPORT_NO_TRACKS = 0;
    /**
     * The renderer has tracks mapped to it, but all are unsupported. In other words, {@link
     * #getTrackSupport(int, int, int)} returns {@link C#FORMAT_UNSUPPORTED_DRM}, {@link
     * C#FORMAT_UNSUPPORTED_SUBTYPE} or {@link C#FORMAT_UNSUPPORTED_TYPE} for all tracks mapped to
     * the renderer.
     */
    public static final int RENDERER_SUPPORT_UNSUPPORTED_TRACKS = 1;
    /**
     * The renderer has tracks mapped to it and at least one is of a supported type, but all such
     * tracks exceed the renderer's capabilities. In other words, {@link #getTrackSupport(int, int,
     * int)} returns {@link C#FORMAT_EXCEEDS_CAPABILITIES} for at least one track mapped to the
     * renderer, but does not return {@link C#FORMAT_HANDLED} for any tracks mapped to the renderer.
     */
    public static final int RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS = 2;
    /**
     * The renderer has tracks mapped to it, and at least one such track is playable. In other
     * words, {@link #getTrackSupport(int, int, int)} returns {@link C#FORMAT_HANDLED} for at least
     * one track mapped to the renderer.
     */
    public static final int RENDERER_SUPPORT_PLAYABLE_TRACKS = 3;

    private final int rendererCount;
    private final String[] rendererNames;
    private final @C.TrackType int[] rendererTrackTypes;
    private final TrackGroupArray[] rendererTrackGroups;
    private final @AdaptiveSupport int[] rendererMixedMimeTypeAdaptiveSupports;
    private final @Capabilities int[][][] rendererFormatSupports;
    private final TrackGroupArray unmappedTrackGroups;

    /**
     * @param rendererNames The name of each renderer.
     * @param rendererTrackTypes The {@link C.TrackType track type} handled by each renderer.
     * @param rendererTrackGroups The {@link TrackGroup}s mapped to each renderer.
     * @param rendererMixedMimeTypeAdaptiveSupports The {@link AdaptiveSupport} for mixed MIME type
     *     adaptation for the renderer.
     * @param rendererFormatSupports The {@link Capabilities} for each mapped track, indexed by
     *     renderer, track group and track (in that order).
     * @param unmappedTrackGroups {@link TrackGroup}s not mapped to any renderer.
     */
    @VisibleForTesting
    /* package */ MappedTrackInfo(
        String[] rendererNames,
        @C.TrackType int[] rendererTrackTypes,
        TrackGroupArray[] rendererTrackGroups,
        @AdaptiveSupport int[] rendererMixedMimeTypeAdaptiveSupports,
        @Capabilities int[][][] rendererFormatSupports,
        TrackGroupArray unmappedTrackGroups) {
      this.rendererNames = rendererNames;
      this.rendererTrackTypes = rendererTrackTypes;
      this.rendererTrackGroups = rendererTrackGroups;
      this.rendererFormatSupports = rendererFormatSupports;
      this.rendererMixedMimeTypeAdaptiveSupports = rendererMixedMimeTypeAdaptiveSupports;
      this.unmappedTrackGroups = unmappedTrackGroups;
      this.rendererCount = rendererTrackTypes.length;
    }

    /** Returns the number of renderers. */
    public int getRendererCount() {
      return rendererCount;
    }

    /**
     * Returns the name of the renderer at a given index.
     *
     * @see Renderer#getName()
     * @param rendererIndex The renderer index.
     * @return The name of the renderer.
     */
    public String getRendererName(int rendererIndex) {
      return rendererNames[rendererIndex];
    }

    /**
     * Returns the track type that the renderer at a given index handles.
     *
     * @see Renderer#getTrackType()
     * @param rendererIndex The renderer index.
     * @return The {@link C.TrackType} of the renderer.
     */
    public @C.TrackType int getRendererType(int rendererIndex) {
      return rendererTrackTypes[rendererIndex];
    }

    /**
     * Returns the {@link TrackGroup}s mapped to the renderer at the specified index.
     *
     * @param rendererIndex The renderer index.
     * @return The corresponding {@link TrackGroup}s.
     */
    public TrackGroupArray getTrackGroups(int rendererIndex) {
      return rendererTrackGroups[rendererIndex];
    }

    /**
     * Returns the extent to which a renderer can play the tracks that are mapped to it.
     *
     * @param rendererIndex The renderer index.
     * @return The {@link RendererSupport}.
     */
    public @RendererSupport int getRendererSupport(int rendererIndex) {
      @RendererSupport int bestRendererSupport = RENDERER_SUPPORT_NO_TRACKS;
      @Capabilities int[][] rendererFormatSupport = rendererFormatSupports[rendererIndex];
      for (@Capabilities int[] trackGroupFormatSupport : rendererFormatSupport) {
        for (@Capabilities int trackFormatSupport : trackGroupFormatSupport) {
          int trackRendererSupport;
          switch (RendererCapabilities.getFormatSupport(trackFormatSupport)) {
            case C.FORMAT_HANDLED:
              return RENDERER_SUPPORT_PLAYABLE_TRACKS;
            case C.FORMAT_EXCEEDS_CAPABILITIES:
              trackRendererSupport = RENDERER_SUPPORT_EXCEEDS_CAPABILITIES_TRACKS;
              break;
            case C.FORMAT_UNSUPPORTED_TYPE:
            case C.FORMAT_UNSUPPORTED_SUBTYPE:
            case C.FORMAT_UNSUPPORTED_DRM:
              trackRendererSupport = RENDERER_SUPPORT_UNSUPPORTED_TRACKS;
              break;
            default:
              throw new IllegalStateException();
          }
          bestRendererSupport = max(bestRendererSupport, trackRendererSupport);
        }
      }
      return bestRendererSupport;
    }

    /**
     * Returns the extent to which tracks of a specified type are supported. This is the best level
     * of support obtained from {@link #getRendererSupport(int)} for all renderers that handle the
     * specified type. If no such renderers exist then {@link #RENDERER_SUPPORT_NO_TRACKS} is
     * returned.
     *
     * @param trackType The {@link C.TrackType track type}.
     * @return The {@link RendererSupport}.
     */
    public @RendererSupport int getTypeSupport(@C.TrackType int trackType) {
      @RendererSupport int bestRendererSupport = RENDERER_SUPPORT_NO_TRACKS;
      for (int i = 0; i < rendererCount; i++) {
        if (rendererTrackTypes[i] == trackType) {
          bestRendererSupport = max(bestRendererSupport, getRendererSupport(i));
        }
      }
      return bestRendererSupport;
    }

    /**
     * Returns the {@link Capabilities} of the renderer for an individual track.
     *
     * @param rendererIndex The renderer index.
     * @param groupIndex The index of the track group to which the track belongs.
     * @param trackIndex The index of the track within the track group.
     * @return The {@link Capabilities}.
     */
    public @Capabilities int getCapabilities(int rendererIndex, int groupIndex, int trackIndex) {
      return rendererFormatSupports[rendererIndex][groupIndex][trackIndex];
    }

    /**
     * Returns the extent to which an individual track is supported by the renderer.
     *
     * @param rendererIndex The renderer index.
     * @param groupIndex The index of the track group to which the track belongs.
     * @param trackIndex The index of the track within the track group.
     * @return The {@link FormatSupport}.
     */
    public @FormatSupport int getTrackSupport(int rendererIndex, int groupIndex, int trackIndex) {
      return RendererCapabilities.getFormatSupport(
          getCapabilities(rendererIndex, groupIndex, trackIndex));
    }

    /**
     * Returns the extent to which a renderer supports adaptation between supported tracks in a
     * specified {@link TrackGroup}.
     *
     * <p>Tracks for which {@link #getTrackSupport(int, int, int)} returns {@link C#FORMAT_HANDLED}
     * are always considered. Tracks for which {@link #getTrackSupport(int, int, int)} returns
     * {@link C#FORMAT_EXCEEDS_CAPABILITIES} are also considered if {@code
     * includeCapabilitiesExceededTracks} is set to {@code true}. Tracks for which {@link
     * #getTrackSupport(int, int, int)} returns {@link C#FORMAT_UNSUPPORTED_DRM}, {@link
     * C#FORMAT_UNSUPPORTED_TYPE} or {@link C#FORMAT_UNSUPPORTED_SUBTYPE} are never considered.
     *
     * @param rendererIndex The renderer index.
     * @param groupIndex The index of the track group.
     * @param includeCapabilitiesExceededTracks Whether tracks that exceed the capabilities of the
     *     renderer are included when determining support.
     * @return The {@link AdaptiveSupport}.
     */
    public @AdaptiveSupport int getAdaptiveSupport(
        int rendererIndex, int groupIndex, boolean includeCapabilitiesExceededTracks) {
      int trackCount = rendererTrackGroups[rendererIndex].get(groupIndex).length;
      // Iterate over the tracks in the group, recording the indices of those to consider.
      int[] trackIndices = new int[trackCount];
      int trackIndexCount = 0;
      for (int i = 0; i < trackCount; i++) {
        @FormatSupport int fixedSupport = getTrackSupport(rendererIndex, groupIndex, i);
        if (fixedSupport == C.FORMAT_HANDLED
            || (includeCapabilitiesExceededTracks
                && fixedSupport == C.FORMAT_EXCEEDS_CAPABILITIES)) {
          trackIndices[trackIndexCount++] = i;
        }
      }
      trackIndices = Arrays.copyOf(trackIndices, trackIndexCount);
      return getAdaptiveSupport(rendererIndex, groupIndex, trackIndices);
    }

    /**
     * Returns the extent to which a renderer supports adaptation between specified tracks within a
     * {@link TrackGroup}.
     *
     * @param rendererIndex The renderer index.
     * @param groupIndex The index of the track group.
     * @return The {@link AdaptiveSupport}.
     */
    public @AdaptiveSupport int getAdaptiveSupport(
        int rendererIndex, int groupIndex, int[] trackIndices) {
      int handledTrackCount = 0;
      @AdaptiveSupport int adaptiveSupport = RendererCapabilities.ADAPTIVE_SEAMLESS;
      boolean multipleMimeTypes = false;
      String firstSampleMimeType = null;
      for (int i = 0; i < trackIndices.length; i++) {
        int trackIndex = trackIndices[i];
        @Nullable
        String sampleMimeType =
            rendererTrackGroups[rendererIndex].get(groupIndex).getFormat(trackIndex).sampleMimeType;
        if (handledTrackCount++ == 0) {
          firstSampleMimeType = sampleMimeType;
        } else {
          multipleMimeTypes |= !Util.areEqual(firstSampleMimeType, sampleMimeType);
        }
        adaptiveSupport =
            min(
                adaptiveSupport,
                RendererCapabilities.getAdaptiveSupport(
                    rendererFormatSupports[rendererIndex][groupIndex][i]));
      }
      return multipleMimeTypes
          ? min(adaptiveSupport, rendererMixedMimeTypeAdaptiveSupports[rendererIndex])
          : adaptiveSupport;
    }

    /** Returns {@link TrackGroup}s not mapped to any renderer. */
    public TrackGroupArray getUnmappedTrackGroups() {
      return unmappedTrackGroups;
    }
  }

  @Nullable private MappedTrackInfo currentMappedTrackInfo;

  /**
   * Returns the mapping information for the currently active track selection, or null if no
   * selection is currently active.
   */
  @Nullable
  public final MappedTrackInfo getCurrentMappedTrackInfo() {
    return currentMappedTrackInfo;
  }

  // TrackSelector implementation.

  @Override
  public final void onSelectionActivated(@Nullable Object info) {
    currentMappedTrackInfo = (MappedTrackInfo) info;
  }

  @Override
  public final TrackSelectorResult selectTracks(
      RendererCapabilities[] rendererCapabilities,
      TrackGroupArray trackGroups,
      MediaPeriodId periodId,
      Timeline timeline)
      throws ExoPlaybackException {
    // Structures into which data will be written during the selection. The extra item at the end
    // of each array is to store data associated with track groups that cannot be associated with
    // any renderer.
    int[] rendererTrackGroupCounts = new int[rendererCapabilities.length + 1];
    TrackGroup[][] rendererTrackGroups = new TrackGroup[rendererCapabilities.length + 1][];
    @Capabilities int[][][] rendererFormatSupports = new int[rendererCapabilities.length + 1][][];
    for (int i = 0; i < rendererTrackGroups.length; i++) {
      rendererTrackGroups[i] = new TrackGroup[trackGroups.length];
      rendererFormatSupports[i] = new int[trackGroups.length][];
    }

    // Determine the extent to which each renderer supports mixed mimeType adaptation.
    @AdaptiveSupport
    int[] rendererMixedMimeTypeAdaptationSupports =
        getMixedMimeTypeAdaptationSupports(rendererCapabilities);

    // Associate each track group to a preferred renderer, and evaluate the support that the
    // renderer provides for each track in the group.
    for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
      TrackGroup group = trackGroups.get(groupIndex);
      // Associate the group to a preferred renderer.
      boolean preferUnassociatedRenderer =
          MimeTypes.getTrackType(group.getFormat(0).sampleMimeType) == C.TRACK_TYPE_METADATA;
      int rendererIndex =
          findRenderer(
              rendererCapabilities, group, rendererTrackGroupCounts, preferUnassociatedRenderer);
      // Evaluate the support that the renderer provides for each track in the group.
      @Capabilities
      int[] rendererFormatSupport =
          rendererIndex == rendererCapabilities.length
              ? new int[group.length]
              : getFormatSupport(rendererCapabilities[rendererIndex], group);
      // Stash the results.
      int rendererTrackGroupCount = rendererTrackGroupCounts[rendererIndex];
      rendererTrackGroups[rendererIndex][rendererTrackGroupCount] = group;
      rendererFormatSupports[rendererIndex][rendererTrackGroupCount] = rendererFormatSupport;
      rendererTrackGroupCounts[rendererIndex]++;
    }

    // Create a track group array for each renderer, and trim each rendererFormatSupports entry.
    TrackGroupArray[] rendererTrackGroupArrays = new TrackGroupArray[rendererCapabilities.length];
    String[] rendererNames = new String[rendererCapabilities.length];
    int[] rendererTrackTypes = new int[rendererCapabilities.length];
    for (int i = 0; i < rendererCapabilities.length; i++) {
      int rendererTrackGroupCount = rendererTrackGroupCounts[i];
      rendererTrackGroupArrays[i] =
          new TrackGroupArray(
              Util.nullSafeArrayCopy(rendererTrackGroups[i], rendererTrackGroupCount));
      rendererFormatSupports[i] =
          Util.nullSafeArrayCopy(rendererFormatSupports[i], rendererTrackGroupCount);
      rendererNames[i] = rendererCapabilities[i].getName();
      rendererTrackTypes[i] = rendererCapabilities[i].getTrackType();
    }

    // Create a track group array for track groups not mapped to a renderer.
    int unmappedTrackGroupCount = rendererTrackGroupCounts[rendererCapabilities.length];
    TrackGroupArray unmappedTrackGroupArray =
        new TrackGroupArray(
            Util.nullSafeArrayCopy(
                rendererTrackGroups[rendererCapabilities.length], unmappedTrackGroupCount));

    // Package up the track information and selections.
    MappedTrackInfo mappedTrackInfo =
        new MappedTrackInfo(
            rendererNames,
            rendererTrackTypes,
            rendererTrackGroupArrays,
            rendererMixedMimeTypeAdaptationSupports,
            rendererFormatSupports,
            unmappedTrackGroupArray);

    Pair<@NullableType RendererConfiguration[], @NullableType ExoTrackSelection[]> result =
        selectTracks(
            mappedTrackInfo,
            rendererFormatSupports,
            rendererMixedMimeTypeAdaptationSupports,
            periodId,
            timeline);

    TracksInfo tracksInfo = buildTracksInfo(result.second, mappedTrackInfo);

    return new TrackSelectorResult(result.first, result.second, tracksInfo, mappedTrackInfo);
  }

  /**
   * Given mapped track information, returns a track selection and configuration for each renderer.
   *
   * @param mappedTrackInfo Mapped track information.
   * @param rendererFormatSupports The {@link Capabilities} for ach mapped track, indexed by
   *     renderer, track group and track (in that order).
   * @param rendererMixedMimeTypeAdaptationSupport The {@link AdaptiveSupport} for mixed MIME type
   *     adaptation for the renderer.
   * @param mediaPeriodId The {@link MediaPeriodId} of the period for which tracks are to be
   *     selected.
   * @param timeline The {@link Timeline} holding the period for which tracks are to be selected.
   * @return A pair consisting of the track selections and configurations for each renderer. A null
   *     configuration indicates the renderer should be disabled, in which case the track selection
   *     will also be null. A track selection may also be null for a non-disabled renderer if {@link
   *     RendererCapabilities#getTrackType()} is {@link C#TRACK_TYPE_NONE}.
   * @throws ExoPlaybackException If an error occurs while selecting the tracks.
   */
  protected abstract Pair<@NullableType RendererConfiguration[], @NullableType ExoTrackSelection[]>
      selectTracks(
          MappedTrackInfo mappedTrackInfo,
          @Capabilities int[][][] rendererFormatSupports,
          @AdaptiveSupport int[] rendererMixedMimeTypeAdaptationSupport,
          MediaPeriodId mediaPeriodId,
          Timeline timeline)
          throws ExoPlaybackException;

  /**
   * Finds the renderer to which the provided {@link TrackGroup} should be mapped.
   *
   * <p>A {@link TrackGroup} is mapped to the renderer that reports the highest of (listed in
   * decreasing order of support) {@link C#FORMAT_HANDLED}, {@link C#FORMAT_EXCEEDS_CAPABILITIES},
   * {@link C#FORMAT_UNSUPPORTED_DRM} and {@link C#FORMAT_UNSUPPORTED_SUBTYPE}.
   *
   * <p>In the case that two or more renderers report the same level of support, the assignment
   * depends on {@code preferUnassociatedRenderer}.
   *
   * <ul>
   *   <li>If {@code preferUnassociatedRenderer} is false, the renderer with the lowest index is
   *       chosen regardless of how many other track groups are already mapped to this renderer.
   *   <li>If {@code preferUnassociatedRenderer} is true, the renderer with the lowest index and no
   *       other mapped track group is chosen, or the renderer with the lowest index if all
   *       available renderers have already mapped track groups.
   * </ul>
   *
   * <p>If all renderers report {@link C#FORMAT_UNSUPPORTED_TYPE} for all of the tracks in the
   * group, then {@code renderers.length} is returned to indicate that the group was not mapped to
   * any renderer.
   *
   * @param rendererCapabilities The {@link RendererCapabilities} of the renderers.
   * @param group The track group to map to a renderer.
   * @param rendererTrackGroupCounts The number of already mapped track groups for each renderer.
   * @param preferUnassociatedRenderer Whether renderers unassociated to any track group should be
   *     preferred.
   * @return The index of the renderer to which the track group was mapped, or {@code
   *     renderers.length} if it was not mapped to any renderer.
   * @throws ExoPlaybackException If an error occurs finding a renderer.
   */
  private static int findRenderer(
      RendererCapabilities[] rendererCapabilities,
      TrackGroup group,
      int[] rendererTrackGroupCounts,
      boolean preferUnassociatedRenderer)
      throws ExoPlaybackException {
    int bestRendererIndex = rendererCapabilities.length;
    @FormatSupport int bestFormatSupportLevel = C.FORMAT_UNSUPPORTED_TYPE;
    boolean bestRendererIsUnassociated = true;
    for (int rendererIndex = 0; rendererIndex < rendererCapabilities.length; rendererIndex++) {
      RendererCapabilities rendererCapability = rendererCapabilities[rendererIndex];
      @FormatSupport int formatSupportLevel = C.FORMAT_UNSUPPORTED_TYPE;
      for (int trackIndex = 0; trackIndex < group.length; trackIndex++) {
        @FormatSupport
        int trackFormatSupportLevel =
            RendererCapabilities.getFormatSupport(
                rendererCapability.supportsFormat(group.getFormat(trackIndex)));
        formatSupportLevel = max(formatSupportLevel, trackFormatSupportLevel);
      }
      boolean rendererIsUnassociated = rendererTrackGroupCounts[rendererIndex] == 0;
      if (formatSupportLevel > bestFormatSupportLevel
          || (formatSupportLevel == bestFormatSupportLevel
              && preferUnassociatedRenderer
              && !bestRendererIsUnassociated
              && rendererIsUnassociated)) {
        bestRendererIndex = rendererIndex;
        bestFormatSupportLevel = formatSupportLevel;
        bestRendererIsUnassociated = rendererIsUnassociated;
      }
    }
    return bestRendererIndex;
  }

  /**
   * Calls {@link RendererCapabilities#supportsFormat} for each track in the specified {@link
   * TrackGroup}, returning the results in an array.
   *
   * @param rendererCapabilities The {@link RendererCapabilities} of the renderer.
   * @param group The track group to evaluate.
   * @return An array containing {@link Capabilities} for each track in the group.
   * @throws ExoPlaybackException If an error occurs determining the format support.
   */
  private static @Capabilities int[] getFormatSupport(
      RendererCapabilities rendererCapabilities, TrackGroup group) throws ExoPlaybackException {
    @Capabilities int[] formatSupport = new int[group.length];
    for (int i = 0; i < group.length; i++) {
      formatSupport[i] = rendererCapabilities.supportsFormat(group.getFormat(i));
    }
    return formatSupport;
  }

  /**
   * Calls {@link RendererCapabilities#supportsMixedMimeTypeAdaptation()} for each renderer,
   * returning the results in an array.
   *
   * @param rendererCapabilities The {@link RendererCapabilities} of the renderers.
   * @return An array containing the {@link AdaptiveSupport} for mixed MIME type adaptation for the
   *     renderer.
   * @throws ExoPlaybackException If an error occurs determining the adaptation support.
   */
  private static @AdaptiveSupport int[] getMixedMimeTypeAdaptationSupports(
      RendererCapabilities[] rendererCapabilities) throws ExoPlaybackException {
    @AdaptiveSupport int[] mixedMimeTypeAdaptationSupport = new int[rendererCapabilities.length];
    for (int i = 0; i < mixedMimeTypeAdaptationSupport.length; i++) {
      mixedMimeTypeAdaptationSupport[i] = rendererCapabilities[i].supportsMixedMimeTypeAdaptation();
    }
    return mixedMimeTypeAdaptationSupport;
  }

  @VisibleForTesting
  /* package */ static TracksInfo buildTracksInfo(
      @NullableType TrackSelection[] selections, MappedTrackInfo mappedTrackInfo) {
    ImmutableList.Builder<TracksInfo.TrackGroupInfo> builder = new ImmutableList.Builder<>();
    for (int rendererIndex = 0;
        rendererIndex < mappedTrackInfo.getRendererCount();
        rendererIndex++) {
      TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(rendererIndex);
      @Nullable TrackSelection trackSelection = selections[rendererIndex];
      for (int groupIndex = 0; groupIndex < trackGroupArray.length; groupIndex++) {
        TrackGroup trackGroup = trackGroupArray.get(groupIndex);
        @C.FormatSupport int[] trackSupport = new int[trackGroup.length];
        boolean[] selected = new boolean[trackGroup.length];
        for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
          trackSupport[trackIndex] =
              mappedTrackInfo.getTrackSupport(rendererIndex, groupIndex, trackIndex);
          boolean isTrackSelected =
              trackSelection != null
                  && trackSelection.getTrackGroup().equals(trackGroup)
                  && trackSelection.indexOf(trackIndex) != C.INDEX_UNSET;
          selected[trackIndex] = isTrackSelected;
        }
        @C.TrackType int trackGroupType = mappedTrackInfo.getRendererType(rendererIndex);
        builder.add(
            new TracksInfo.TrackGroupInfo(trackGroup, trackSupport, trackGroupType, selected));
      }
    }
    TrackGroupArray unmappedTrackGroups = mappedTrackInfo.getUnmappedTrackGroups();
    for (int groupIndex = 0; groupIndex < unmappedTrackGroups.length; groupIndex++) {
      TrackGroup trackGroup = unmappedTrackGroups.get(groupIndex);
      @C.FormatSupport int[] trackSupport = new int[trackGroup.length];
      Arrays.fill(trackSupport, C.FORMAT_UNSUPPORTED_TYPE);
      // A track group only contains tracks of the same type, thus only consider the first track.
      @C.TrackType
      int trackGroupType = MimeTypes.getTrackType(trackGroup.getFormat(0).sampleMimeType);
      boolean[] selected = new boolean[trackGroup.length]; // Initialized to false.
      builder.add(
          new TracksInfo.TrackGroupInfo(trackGroup, trackSupport, trackGroupType, selected));
    }
    return new TracksInfo(builder.build());
  }
}