MappingTrackSelector.java

/*
 * 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.Timeline;
import androidx.media3.common.TrackGroup;
import androidx.media3.common.Tracks;
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 androidx.media3.exoplayer.source.TrackGroupArray;
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 = group.type == 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);

    Tracks tracks = TrackSelectionUtil.buildTracks(mappedTrackInfo, result.second);

    return new TrackSelectorResult(result.first, result.second, tracks, 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;
  }
}