public class

TrackSelectionParameters

extends java.lang.Object

 java.lang.Object

↳androidx.media3.common.TrackSelectionParameters

Subclasses:

DefaultTrackSelector.Parameters

Gradle dependencies

compile group: 'androidx.media3', name: 'media3-common', version: '1.5.0-alpha01'

  • groupId: androidx.media3
  • artifactId: media3-common
  • version: 1.5.0-alpha01

Artifact androidx.media3:media3-common:1.5.0-alpha01 it located at Google repository (https://maven.google.com/)

Overview

Parameters for controlling track selection.

Parameters can be queried and set on a Player. For example the following code modifies the parameters to restrict video track selections to SD, and to select a German audio track if there is one:

 // Build on the current parameters.
 TrackSelectionParameters currentParameters = player.getTrackSelectionParameters();
 // Build the resulting parameters.
 TrackSelectionParameters newParameters = currentParameters
     .buildUpon()
     .setMaxVideoSizeSd()
     .setPreferredAudioLanguage("de")
     .build();
 // Set the new parameters.
 player.setTrackSelectionParameters(newParameters);
 

Summary

Fields
public final TrackSelectionParameters.AudioOffloadPreferencesaudioOffloadPreferences

The preferred offload mode settings for audio playback.

public static final TrackSelectionParametersDEFAULT

public static final TrackSelectionParametersDEFAULT_WITHOUT_CONTEXT

An instance with default values, except those obtained from the .

public final <any>disabledTrackTypes

The track types that are disabled.

protected static final intFIELD_CUSTOM_ID_BASE

Defines a minimum field ID value for subclasses to use when implementing TrackSelectionParameters.toBundle() and delegating to TrackSelectionParameters.Builder.Builder(Bundle).

public final booleanforceHighestSupportedBitrate

Whether to force selection of the highest bitrate audio and video tracks that comply with all other constraints.

public final booleanforceLowestBitrate

Whether to force selection of the single lowest bitrate audio and video tracks that comply with all other constraints.

public final intignoredTextSelectionFlags

Bitmask of selection flags that are ignored for text track selections.

public final booleanisPrioritizeImageOverVideoEnabled

Whether an image track will be selected over a video track if both are available.

public final intmaxAudioBitrate

Maximum allowed audio bitrate in bits per second.

public final intmaxAudioChannelCount

Maximum allowed audio channel count.

public final intmaxVideoBitrate

Maximum allowed video bitrate in bits per second.

public final intmaxVideoFrameRate

Maximum allowed video frame rate in hertz.

public final intmaxVideoHeight

Maximum allowed video height in pixels.

public final intmaxVideoWidth

Maximum allowed video width in pixels.

public final intminVideoBitrate

Minimum allowed video bitrate in bits per second.

public final intminVideoFrameRate

Minimum allowed video frame rate in hertz.

public final intminVideoHeight

Minimum allowed video height in pixels.

public final intminVideoWidth

Minimum allowed video width in pixels.

public final <any>overrides

Overrides to force selection of specific tracks.

public final <any>preferredAudioLanguages

The preferred languages for audio and forced text tracks as IETF BCP 47 conformant tags in order of preference.

public final <any>preferredAudioMimeTypes

The preferred sample MIME types for audio tracks in order of preference, or an empty list for no preference.

public final intpreferredAudioRoleFlags

The preferred C.RoleFlags for audio tracks.

public final <any>preferredTextLanguages

The preferred languages for text tracks as IETF BCP 47 conformant tags in order of preference.

public final intpreferredTextRoleFlags

The preferred C.RoleFlags for text tracks.

public final <any>preferredVideoMimeTypes

The preferred sample MIME types for video tracks in order of preference, or an empty list for no preference.

public final intpreferredVideoRoleFlags

The preferred C.RoleFlags for video tracks.

public final booleanselectUndeterminedTextLanguage

Whether a text track with undetermined language should be selected if no track with TrackSelectionParameters.preferredTextLanguages is available, or if TrackSelectionParameters.preferredTextLanguages is unset.

public final intviewportHeight

Viewport height in pixels.

public final booleanviewportOrientationMayChange

Whether the viewport orientation may change during playback.

public final intviewportWidth

Viewport width in pixels.

Constructors
protectedTrackSelectionParameters(TrackSelectionParameters.Builder builder)

Methods
public TrackSelectionParameters.BuilderbuildUpon()

Creates a new TrackSelectionParameters.Builder, copying the initial values from this instance.

public booleanequals(java.lang.Object obj)

public static TrackSelectionParametersfromBundle(Bundle bundle)

Construct an instance from a produced by TrackSelectionParameters.toBundle().

public static TrackSelectionParametersgetDefaults(Context context)

Returns an instance configured with default values.

public inthashCode()

public BundletoBundle()

from java.lang.Objectclone, finalize, getClass, notify, notifyAll, toString, wait, wait, wait

Fields

public static final TrackSelectionParameters DEFAULT_WITHOUT_CONTEXT

An instance with default values, except those obtained from the .

If possible, use TrackSelectionParameters.getDefaults(Context) instead.

This instance will not have the following settings:

public static final TrackSelectionParameters DEFAULT

Deprecated: This instance is not configured using constraints. Use TrackSelectionParameters.getDefaults(Context) instead.

public final int maxVideoWidth

Maximum allowed video width in pixels. The default value is MAX_VALUE (i.e. no constraint).

To constrain adaptive video track selections to be suitable for a given viewport (the region of the display within which video will be played), use (TrackSelectionParameters.viewportWidth, TrackSelectionParameters.viewportHeight and TrackSelectionParameters.viewportOrientationMayChange) instead.

public final int maxVideoHeight

Maximum allowed video height in pixels. The default value is MAX_VALUE (i.e. no constraint).

To constrain adaptive video track selections to be suitable for a given viewport (the region of the display within which video will be played), use (TrackSelectionParameters.viewportWidth, TrackSelectionParameters.viewportHeight and TrackSelectionParameters.viewportOrientationMayChange) instead.

public final int maxVideoFrameRate

Maximum allowed video frame rate in hertz. The default value is MAX_VALUE (i.e. no constraint).

public final int maxVideoBitrate

Maximum allowed video bitrate in bits per second. The default value is MAX_VALUE (i.e. no constraint).

public final int minVideoWidth

Minimum allowed video width in pixels. The default value is 0 (i.e. no constraint).

public final int minVideoHeight

Minimum allowed video height in pixels. The default value is 0 (i.e. no constraint).

public final int minVideoFrameRate

Minimum allowed video frame rate in hertz. The default value is 0 (i.e. no constraint).

public final int minVideoBitrate

Minimum allowed video bitrate in bits per second. The default value is 0 (i.e. no constraint).

public final int viewportWidth

Viewport width in pixels. Constrains video track selections for adaptive content so that only tracks suitable for the viewport are selected. The default value is the physical width of the primary display, in pixels.

public final int viewportHeight

Viewport height in pixels. Constrains video track selections for adaptive content so that only tracks suitable for the viewport are selected. The default value is the physical height of the primary display, in pixels.

public final boolean viewportOrientationMayChange

Whether the viewport orientation may change during playback. Constrains video track selections for adaptive content so that only tracks suitable for the viewport are selected. The default value is true.

public final <any> preferredVideoMimeTypes

The preferred sample MIME types for video tracks in order of preference, or an empty list for no preference. The default is an empty list.

public final int preferredVideoRoleFlags

The preferred C.RoleFlags for video tracks. 0 selects the default track if there is one, or the first track if there's no default. The default value is 0.

public final <any> preferredAudioLanguages

The preferred languages for audio and forced text tracks as IETF BCP 47 conformant tags in order of preference. An empty list selects the default track, or the first track if there's no default. The default value is an empty list.

public final int preferredAudioRoleFlags

The preferred C.RoleFlags for audio tracks. 0 selects the default track if there is one, or the first track if there's no default. The default value is 0.

public final int maxAudioChannelCount

Maximum allowed audio channel count. The default value is MAX_VALUE (i.e. no constraint).

public final int maxAudioBitrate

Maximum allowed audio bitrate in bits per second. The default value is MAX_VALUE (i.e. no constraint).

public final <any> preferredAudioMimeTypes

The preferred sample MIME types for audio tracks in order of preference, or an empty list for no preference. The default is an empty list.

public final TrackSelectionParameters.AudioOffloadPreferences audioOffloadPreferences

The preferred offload mode settings for audio playback. The default is TrackSelectionParameters.AudioOffloadPreferences.DEFAULT.

public final <any> preferredTextLanguages

The preferred languages for text tracks as IETF BCP 47 conformant tags in order of preference. An empty list selects the default track if there is one, or no track otherwise. The default value is an empty list, or the language of the accessibility CaptioningManager if enabled.

public final int preferredTextRoleFlags

The preferred C.RoleFlags for text tracks. 0 selects the default track if there is one, or no track otherwise. The default value is 0, or C.ROLE_FLAG_SUBTITLE | C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND if the accessibility CaptioningManager is enabled.

public final int ignoredTextSelectionFlags

Bitmask of selection flags that are ignored for text track selections. See C.SelectionFlags. The default value is 0 (i.e., no flags are ignored).

public final boolean selectUndeterminedTextLanguage

Whether a text track with undetermined language should be selected if no track with TrackSelectionParameters.preferredTextLanguages is available, or if TrackSelectionParameters.preferredTextLanguages is unset. The default value is false.

public final boolean isPrioritizeImageOverVideoEnabled

Whether an image track will be selected over a video track if both are available. The default value is false.

public final boolean forceLowestBitrate

Whether to force selection of the single lowest bitrate audio and video tracks that comply with all other constraints. The default value is false.

public final boolean forceHighestSupportedBitrate

Whether to force selection of the highest bitrate audio and video tracks that comply with all other constraints. The default value is false.

public final <any> overrides

Overrides to force selection of specific tracks.

public final <any> disabledTrackTypes

The track types that are disabled. No track of a disabled type will be selected, thus no track type contained in the set will be played. The default value is that no track type is disabled (empty set).

protected static final int FIELD_CUSTOM_ID_BASE

Defines a minimum field ID value for subclasses to use when implementing TrackSelectionParameters.toBundle() and delegating to TrackSelectionParameters.Builder.Builder(Bundle).

Subclasses should obtain keys for their representation by applying a non-negative offset on this constant and passing the result to Util.intToStringMaxRadix(int).

Constructors

protected TrackSelectionParameters(TrackSelectionParameters.Builder builder)

Methods

public static TrackSelectionParameters getDefaults(Context context)

Returns an instance configured with default values.

Creates a new TrackSelectionParameters.Builder, copying the initial values from this instance.

public boolean equals(java.lang.Object obj)

public int hashCode()

public Bundle toBundle()

public static TrackSelectionParameters fromBundle(Bundle bundle)

Construct an instance from a produced by TrackSelectionParameters.toBundle().

Source

/*
 * Copyright (C) 2019 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.common;

import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.BundleCollectionUtil.toBundleArrayList;
import static com.google.common.base.MoreObjects.firstNonNull;
import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.SOURCE;

import android.content.Context;
import android.graphics.Point;
import android.os.Bundle;
import android.os.Looper;
import android.view.accessibility.CaptioningManager;
import androidx.annotation.CallSuper;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.media3.common.util.BundleCollectionUtil;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;

// LINT.IfChange(javadoc)
/**
 * Parameters for controlling track selection.
 *
 * <p>Parameters can be queried and set on a {@link Player}. For example the following code modifies
 * the parameters to restrict video track selections to SD, and to select a German audio track if
 * there is one:
 *
 * <pre>{@code
 * // Build on the current parameters.
 * TrackSelectionParameters currentParameters = player.getTrackSelectionParameters();
 * // Build the resulting parameters.
 * TrackSelectionParameters newParameters = currentParameters
 *     .buildUpon()
 *     .setMaxVideoSizeSd()
 *     .setPreferredAudioLanguage("de")
 *     .build();
 * // Set the new parameters.
 * player.setTrackSelectionParameters(newParameters);
 * }</pre>
 */
public class TrackSelectionParameters {

  /**
   * A builder for {@link TrackSelectionParameters}. See the {@link TrackSelectionParameters}
   * documentation for explanations of the parameters that can be configured using this builder.
   */
  public static class Builder {
    // Video
    private int maxVideoWidth;
    private int maxVideoHeight;
    private int maxVideoFrameRate;
    private int maxVideoBitrate;
    private int minVideoWidth;
    private int minVideoHeight;
    private int minVideoFrameRate;
    private int minVideoBitrate;
    private int viewportWidth;
    private int viewportHeight;
    private boolean viewportOrientationMayChange;
    private ImmutableList<String> preferredVideoMimeTypes;
    private @C.RoleFlags int preferredVideoRoleFlags;
    // Audio
    private ImmutableList<String> preferredAudioLanguages;
    private @C.RoleFlags int preferredAudioRoleFlags;
    private int maxAudioChannelCount;
    private int maxAudioBitrate;
    private ImmutableList<String> preferredAudioMimeTypes;
    private AudioOffloadPreferences audioOffloadPreferences;
    // Text
    private ImmutableList<String> preferredTextLanguages;
    private @C.RoleFlags int preferredTextRoleFlags;
    private @C.SelectionFlags int ignoredTextSelectionFlags;
    private boolean selectUndeterminedTextLanguage;
    // Image
    private boolean isPrioritizeImageOverVideoEnabled;
    // General
    private boolean forceLowestBitrate;
    private boolean forceHighestSupportedBitrate;
    private HashMap<TrackGroup, TrackSelectionOverride> overrides;
    private HashSet<@C.TrackType Integer> disabledTrackTypes;

    /**
     * @deprecated {@link Context} constraints will not be set using this constructor. Use {@link
     *     #Builder(Context)} instead.
     */
    @UnstableApi
    @Deprecated
    public Builder() {
      // Video
      maxVideoWidth = Integer.MAX_VALUE;
      maxVideoHeight = Integer.MAX_VALUE;
      maxVideoFrameRate = Integer.MAX_VALUE;
      maxVideoBitrate = Integer.MAX_VALUE;
      viewportWidth = Integer.MAX_VALUE;
      viewportHeight = Integer.MAX_VALUE;
      viewportOrientationMayChange = true;
      preferredVideoMimeTypes = ImmutableList.of();
      preferredVideoRoleFlags = 0;
      // Audio
      preferredAudioLanguages = ImmutableList.of();
      preferredAudioRoleFlags = 0;
      maxAudioChannelCount = Integer.MAX_VALUE;
      maxAudioBitrate = Integer.MAX_VALUE;
      preferredAudioMimeTypes = ImmutableList.of();
      audioOffloadPreferences = AudioOffloadPreferences.DEFAULT;
      // Text
      preferredTextLanguages = ImmutableList.of();
      preferredTextRoleFlags = 0;
      ignoredTextSelectionFlags = 0;
      selectUndeterminedTextLanguage = false;
      // Image
      isPrioritizeImageOverVideoEnabled = false;
      // General
      forceLowestBitrate = false;
      forceHighestSupportedBitrate = false;
      overrides = new HashMap<>();
      disabledTrackTypes = new HashSet<>();
    }

    /**
     * Creates a builder with default initial values.
     *
     * @param context Any context.
     */
    @SuppressWarnings({"deprecation", "method.invocation"}) // Methods invoked are setter only.
    public Builder(Context context) {
      this();
      setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings(context);
      setViewportSizeToPhysicalDisplaySize(context, /* viewportOrientationMayChange= */ true);
    }

    /** Creates a builder with the initial values specified in {@code initialValues}. */
    @UnstableApi
    protected Builder(TrackSelectionParameters initialValues) {
      init(initialValues);
    }

    /** Creates a builder with the initial values specified in {@code bundle}. */
    @UnstableApi
    protected Builder(Bundle bundle) {
      // Video
      maxVideoWidth = bundle.getInt(FIELD_MAX_VIDEO_WIDTH, DEFAULT_WITHOUT_CONTEXT.maxVideoWidth);
      maxVideoHeight =
          bundle.getInt(FIELD_MAX_VIDEO_HEIGHT, DEFAULT_WITHOUT_CONTEXT.maxVideoHeight);
      maxVideoFrameRate =
          bundle.getInt(FIELD_MAX_VIDEO_FRAMERATE, DEFAULT_WITHOUT_CONTEXT.maxVideoFrameRate);
      maxVideoBitrate =
          bundle.getInt(FIELD_MAX_VIDEO_BITRATE, DEFAULT_WITHOUT_CONTEXT.maxVideoBitrate);
      minVideoWidth = bundle.getInt(FIELD_MIN_VIDEO_WIDTH, DEFAULT_WITHOUT_CONTEXT.minVideoWidth);
      minVideoHeight =
          bundle.getInt(FIELD_MIN_VIDEO_HEIGHT, DEFAULT_WITHOUT_CONTEXT.minVideoHeight);
      minVideoFrameRate =
          bundle.getInt(FIELD_MIN_VIDEO_FRAMERATE, DEFAULT_WITHOUT_CONTEXT.minVideoFrameRate);
      minVideoBitrate =
          bundle.getInt(FIELD_MIN_VIDEO_BITRATE, DEFAULT_WITHOUT_CONTEXT.minVideoBitrate);
      viewportWidth = bundle.getInt(FIELD_VIEWPORT_WIDTH, DEFAULT_WITHOUT_CONTEXT.viewportWidth);
      viewportHeight = bundle.getInt(FIELD_VIEWPORT_HEIGHT, DEFAULT_WITHOUT_CONTEXT.viewportHeight);
      viewportOrientationMayChange =
          bundle.getBoolean(
              FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE,
              DEFAULT_WITHOUT_CONTEXT.viewportOrientationMayChange);
      preferredVideoMimeTypes =
          ImmutableList.copyOf(
              firstNonNull(bundle.getStringArray(FIELD_PREFERRED_VIDEO_MIMETYPES), new String[0]));
      preferredVideoRoleFlags =
          bundle.getInt(
              FIELD_PREFERRED_VIDEO_ROLE_FLAGS, DEFAULT_WITHOUT_CONTEXT.preferredVideoRoleFlags);
      // Audio
      String[] preferredAudioLanguages1 =
          firstNonNull(bundle.getStringArray(FIELD_PREFERRED_AUDIO_LANGUAGES), new String[0]);
      preferredAudioLanguages = normalizeLanguageCodes(preferredAudioLanguages1);
      preferredAudioRoleFlags =
          bundle.getInt(
              FIELD_PREFERRED_AUDIO_ROLE_FLAGS, DEFAULT_WITHOUT_CONTEXT.preferredAudioRoleFlags);
      maxAudioChannelCount =
          bundle.getInt(
              FIELD_MAX_AUDIO_CHANNEL_COUNT, DEFAULT_WITHOUT_CONTEXT.maxAudioChannelCount);
      maxAudioBitrate =
          bundle.getInt(FIELD_MAX_AUDIO_BITRATE, DEFAULT_WITHOUT_CONTEXT.maxAudioBitrate);
      preferredAudioMimeTypes =
          ImmutableList.copyOf(
              firstNonNull(bundle.getStringArray(FIELD_PREFERRED_AUDIO_MIME_TYPES), new String[0]));
      audioOffloadPreferences = getAudioOffloadPreferencesFromBundle(bundle);
      // Text
      preferredTextLanguages =
          normalizeLanguageCodes(
              firstNonNull(bundle.getStringArray(FIELD_PREFERRED_TEXT_LANGUAGES), new String[0]));
      preferredTextRoleFlags =
          bundle.getInt(
              FIELD_PREFERRED_TEXT_ROLE_FLAGS, DEFAULT_WITHOUT_CONTEXT.preferredTextRoleFlags);
      ignoredTextSelectionFlags =
          bundle.getInt(
              FIELD_IGNORED_TEXT_SELECTION_FLAGS,
              DEFAULT_WITHOUT_CONTEXT.ignoredTextSelectionFlags);
      selectUndeterminedTextLanguage =
          bundle.getBoolean(
              FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE,
              DEFAULT_WITHOUT_CONTEXT.selectUndeterminedTextLanguage);
      // Image
      isPrioritizeImageOverVideoEnabled =
          bundle.getBoolean(
              FIELD_IS_PREFER_IMAGE_OVER_VIDEO_ENABLED,
              DEFAULT_WITHOUT_CONTEXT.isPrioritizeImageOverVideoEnabled);

      // General
      forceLowestBitrate =
          bundle.getBoolean(FIELD_FORCE_LOWEST_BITRATE, DEFAULT_WITHOUT_CONTEXT.forceLowestBitrate);
      forceHighestSupportedBitrate =
          bundle.getBoolean(
              FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE,
              DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate);
      @Nullable
      List<Bundle> overrideBundleList = bundle.getParcelableArrayList(FIELD_SELECTION_OVERRIDES);
      List<TrackSelectionOverride> overrideList =
          overrideBundleList == null
              ? ImmutableList.of()
              : BundleCollectionUtil.fromBundleList(
                  TrackSelectionOverride::fromBundle, overrideBundleList);
      overrides = new HashMap<>();
      for (int i = 0; i < overrideList.size(); i++) {
        TrackSelectionOverride override = overrideList.get(i);
        overrides.put(override.mediaTrackGroup, override);
      }
      int[] disabledTrackTypeArray =
          firstNonNull(bundle.getIntArray(FIELD_DISABLED_TRACK_TYPE), new int[0]);
      disabledTrackTypes = new HashSet<>();
      for (@C.TrackType int disabledTrackType : disabledTrackTypeArray) {
        disabledTrackTypes.add(disabledTrackType);
      }
    }

    private static AudioOffloadPreferences getAudioOffloadPreferencesFromBundle(Bundle bundle) {
      Bundle audioOffloadPreferencesBundle = bundle.getBundle(FIELD_AUDIO_OFFLOAD_PREFERENCES);
      return (audioOffloadPreferencesBundle != null)
          ? AudioOffloadPreferences.fromBundle(audioOffloadPreferencesBundle)
          : new AudioOffloadPreferences.Builder()
              .setAudioOffloadMode(
                  bundle.getInt(
                      FIELD_AUDIO_OFFLOAD_MODE_PREFERENCE,
                      AudioOffloadPreferences.DEFAULT.audioOffloadMode))
              .setIsGaplessSupportRequired(
                  bundle.getBoolean(
                      FIELD_IS_GAPLESS_SUPPORT_REQUIRED,
                      AudioOffloadPreferences.DEFAULT.isGaplessSupportRequired))
              .setIsSpeedChangeSupportRequired(
                  bundle.getBoolean(
                      FIELD_IS_SPEED_CHANGE_SUPPORT_REQUIRED,
                      AudioOffloadPreferences.DEFAULT.isSpeedChangeSupportRequired))
              .build();
    }

    /** Overrides the value of the builder with the value of {@link TrackSelectionParameters}. */
    @EnsuresNonNull({
      "preferredVideoMimeTypes",
      "preferredAudioLanguages",
      "preferredAudioMimeTypes",
      "audioOffloadPreferences",
      "preferredTextLanguages",
      "overrides",
      "disabledTrackTypes",
    })
    private void init(@UnknownInitialization Builder this, TrackSelectionParameters parameters) {
      // Video
      maxVideoWidth = parameters.maxVideoWidth;
      maxVideoHeight = parameters.maxVideoHeight;
      maxVideoFrameRate = parameters.maxVideoFrameRate;
      maxVideoBitrate = parameters.maxVideoBitrate;
      minVideoWidth = parameters.minVideoWidth;
      minVideoHeight = parameters.minVideoHeight;
      minVideoFrameRate = parameters.minVideoFrameRate;
      minVideoBitrate = parameters.minVideoBitrate;
      viewportWidth = parameters.viewportWidth;
      viewportHeight = parameters.viewportHeight;
      viewportOrientationMayChange = parameters.viewportOrientationMayChange;
      preferredVideoMimeTypes = parameters.preferredVideoMimeTypes;
      preferredVideoRoleFlags = parameters.preferredVideoRoleFlags;
      // Audio
      preferredAudioLanguages = parameters.preferredAudioLanguages;
      preferredAudioRoleFlags = parameters.preferredAudioRoleFlags;
      maxAudioChannelCount = parameters.maxAudioChannelCount;
      maxAudioBitrate = parameters.maxAudioBitrate;
      preferredAudioMimeTypes = parameters.preferredAudioMimeTypes;
      audioOffloadPreferences = parameters.audioOffloadPreferences;
      // Text
      preferredTextLanguages = parameters.preferredTextLanguages;
      preferredTextRoleFlags = parameters.preferredTextRoleFlags;
      ignoredTextSelectionFlags = parameters.ignoredTextSelectionFlags;
      selectUndeterminedTextLanguage = parameters.selectUndeterminedTextLanguage;
      // Image
      isPrioritizeImageOverVideoEnabled = parameters.isPrioritizeImageOverVideoEnabled;
      // General
      forceLowestBitrate = parameters.forceLowestBitrate;
      forceHighestSupportedBitrate = parameters.forceHighestSupportedBitrate;
      disabledTrackTypes = new HashSet<>(parameters.disabledTrackTypes);
      overrides = new HashMap<>(parameters.overrides);
    }

    /** Overrides the value of the builder with the value of {@link TrackSelectionParameters}. */
    @CanIgnoreReturnValue
    @UnstableApi
    protected Builder set(TrackSelectionParameters parameters) {
      init(parameters);
      return this;
    }

    // Video

    /**
     * Equivalent to {@link #setMaxVideoSize setMaxVideoSize(1279, 719)}.
     *
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setMaxVideoSizeSd() {
      return setMaxVideoSize(1279, 719);
    }

    /**
     * Equivalent to {@link #setMaxVideoSize setMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}.
     *
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder clearVideoSizeConstraints() {
      return setMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE);
    }

    /**
     * Sets the maximum allowed video width and height.
     *
     * @param maxVideoWidth Maximum allowed video width in pixels.
     * @param maxVideoHeight Maximum allowed video height in pixels.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setMaxVideoSize(int maxVideoWidth, int maxVideoHeight) {
      this.maxVideoWidth = maxVideoWidth;
      this.maxVideoHeight = maxVideoHeight;
      return this;
    }

    /**
     * Sets the maximum allowed video frame rate.
     *
     * @param maxVideoFrameRate Maximum allowed video frame rate in hertz.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setMaxVideoFrameRate(int maxVideoFrameRate) {
      this.maxVideoFrameRate = maxVideoFrameRate;
      return this;
    }

    /**
     * Sets the maximum allowed video bitrate.
     *
     * @param maxVideoBitrate Maximum allowed video bitrate in bits per second.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setMaxVideoBitrate(int maxVideoBitrate) {
      this.maxVideoBitrate = maxVideoBitrate;
      return this;
    }

    /**
     * Sets the minimum allowed video width and height.
     *
     * @param minVideoWidth Minimum allowed video width in pixels.
     * @param minVideoHeight Minimum allowed video height in pixels.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setMinVideoSize(int minVideoWidth, int minVideoHeight) {
      this.minVideoWidth = minVideoWidth;
      this.minVideoHeight = minVideoHeight;
      return this;
    }

    /**
     * Sets the minimum allowed video frame rate.
     *
     * @param minVideoFrameRate Minimum allowed video frame rate in hertz.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setMinVideoFrameRate(int minVideoFrameRate) {
      this.minVideoFrameRate = minVideoFrameRate;
      return this;
    }

    /**
     * Sets the minimum allowed video bitrate.
     *
     * @param minVideoBitrate Minimum allowed video bitrate in bits per second.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setMinVideoBitrate(int minVideoBitrate) {
      this.minVideoBitrate = minVideoBitrate;
      return this;
    }

    /**
     * Equivalent to calling {@link #setViewportSize(int, int, boolean)} with the viewport size
     * obtained from {@link Util#getCurrentDisplayModeSize(Context)}.
     *
     * @param context Any context.
     * @param viewportOrientationMayChange Whether the viewport orientation may change during
     *     playback.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setViewportSizeToPhysicalDisplaySize(
        Context context, boolean viewportOrientationMayChange) {
      // Assume the viewport is fullscreen.
      Point viewportSize = Util.getCurrentDisplayModeSize(context);
      return setViewportSize(viewportSize.x, viewportSize.y, viewportOrientationMayChange);
    }

    /**
     * Equivalent to {@link #setViewportSize setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE,
     * true)}.
     *
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder clearViewportSizeConstraints() {
      return setViewportSize(Integer.MAX_VALUE, Integer.MAX_VALUE, true);
    }

    /**
     * Sets the viewport size to constrain adaptive video selections so that only tracks suitable
     * for the viewport are selected.
     *
     * @param viewportWidth Viewport width in pixels.
     * @param viewportHeight Viewport height in pixels.
     * @param viewportOrientationMayChange Whether the viewport orientation may change during
     *     playback.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setViewportSize(
        int viewportWidth, int viewportHeight, boolean viewportOrientationMayChange) {
      this.viewportWidth = viewportWidth;
      this.viewportHeight = viewportHeight;
      this.viewportOrientationMayChange = viewportOrientationMayChange;
      return this;
    }

    /**
     * Sets the preferred sample MIME type for video tracks.
     *
     * @param mimeType The preferred MIME type for video tracks, or {@code null} to clear a
     *     previously set preference.
     * @return This builder.
     */
    public Builder setPreferredVideoMimeType(@Nullable String mimeType) {
      return mimeType == null ? setPreferredVideoMimeTypes() : setPreferredVideoMimeTypes(mimeType);
    }

    /**
     * Sets the preferred sample MIME types for video tracks.
     *
     * @param mimeTypes The preferred MIME types for video tracks in order of preference, or an
     *     empty list for no preference.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setPreferredVideoMimeTypes(String... mimeTypes) {
      preferredVideoMimeTypes = ImmutableList.copyOf(mimeTypes);
      return this;
    }

    /**
     * Sets the preferred {@link C.RoleFlags} for video tracks.
     *
     * @param preferredVideoRoleFlags Preferred video role flags.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setPreferredVideoRoleFlags(@C.RoleFlags int preferredVideoRoleFlags) {
      this.preferredVideoRoleFlags = preferredVideoRoleFlags;
      return this;
    }

    // Audio

    /**
     * Sets the preferred language for audio and forced text tracks.
     *
     * @param preferredAudioLanguage Preferred audio language as an IETF BCP 47 conformant tag, or
     *     {@code null} to select the default track, or the first track if there's no default.
     * @return This builder.
     */
    public Builder setPreferredAudioLanguage(@Nullable String preferredAudioLanguage) {
      return preferredAudioLanguage == null
          ? setPreferredAudioLanguages()
          : setPreferredAudioLanguages(preferredAudioLanguage);
    }

    /**
     * Sets the preferred languages for audio and forced text tracks.
     *
     * @param preferredAudioLanguages Preferred audio languages as IETF BCP 47 conformant tags in
     *     order of preference, or an empty array to select the default track, or the first track if
     *     there's no default.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setPreferredAudioLanguages(String... preferredAudioLanguages) {
      this.preferredAudioLanguages = normalizeLanguageCodes(preferredAudioLanguages);
      return this;
    }

    /**
     * Sets the preferred {@link C.RoleFlags} for audio tracks.
     *
     * @param preferredAudioRoleFlags Preferred audio role flags.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setPreferredAudioRoleFlags(@C.RoleFlags int preferredAudioRoleFlags) {
      this.preferredAudioRoleFlags = preferredAudioRoleFlags;
      return this;
    }

    /**
     * Sets the maximum allowed audio channel count.
     *
     * @param maxAudioChannelCount Maximum allowed audio channel count.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setMaxAudioChannelCount(int maxAudioChannelCount) {
      this.maxAudioChannelCount = maxAudioChannelCount;
      return this;
    }

    /**
     * Sets the maximum allowed audio bitrate.
     *
     * @param maxAudioBitrate Maximum allowed audio bitrate in bits per second.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setMaxAudioBitrate(int maxAudioBitrate) {
      this.maxAudioBitrate = maxAudioBitrate;
      return this;
    }

    /**
     * Sets the preferred sample MIME type for audio tracks.
     *
     * @param mimeType The preferred MIME type for audio tracks, or {@code null} to clear a
     *     previously set preference.
     * @return This builder.
     */
    public Builder setPreferredAudioMimeType(@Nullable String mimeType) {
      return mimeType == null ? setPreferredAudioMimeTypes() : setPreferredAudioMimeTypes(mimeType);
    }

    /**
     * Sets the preferred sample MIME types for audio tracks.
     *
     * @param mimeTypes The preferred MIME types for audio tracks in order of preference, or an
     *     empty list for no preference.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setPreferredAudioMimeTypes(String... mimeTypes) {
      preferredAudioMimeTypes = ImmutableList.copyOf(mimeTypes);
      return this;
    }

    /**
     * Sets the audio offload mode preferences. This includes whether to enable/disable offload as
     * well as to set requirements like if the device must support gapless transitions or speed
     * change during offload.
     */
    @UnstableApi
    @CanIgnoreReturnValue
    public Builder setAudioOffloadPreferences(AudioOffloadPreferences audioOffloadPreferences) {
      this.audioOffloadPreferences = audioOffloadPreferences;
      return this;
    }

    // Text

    /**
     * Sets the preferred language and role flags for text tracks based on the accessibility
     * settings of {@link CaptioningManager}.
     *
     * <p>Does nothing when the {@link CaptioningManager} is disabled.
     *
     * @param context A {@link Context}.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings(
        Context context) {
      if (Util.SDK_INT < 23 && Looper.myLooper() == null) {
        // Android platform bug (pre-Marshmallow) that causes RuntimeExceptions when
        // CaptioningService is instantiated from a non-Looper thread. See [internal: b/143779904].
        return this;
      }
      CaptioningManager captioningManager =
          (CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
      if (captioningManager == null || !captioningManager.isEnabled()) {
        return this;
      }
      preferredTextRoleFlags = C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND;
      Locale preferredLocale = captioningManager.getLocale();
      if (preferredLocale != null) {
        preferredTextLanguages = ImmutableList.of(Util.getLocaleLanguageTag(preferredLocale));
      }
      return this;
    }

    /**
     * Sets the preferred language for text tracks.
     *
     * @param preferredTextLanguage Preferred text language as an IETF BCP 47 conformant tag, or
     *     {@code null} to select the default track if there is one, or no track otherwise.
     * @return This builder.
     */
    public Builder setPreferredTextLanguage(@Nullable String preferredTextLanguage) {
      return preferredTextLanguage == null
          ? setPreferredTextLanguages()
          : setPreferredTextLanguages(preferredTextLanguage);
    }

    /**
     * Sets the preferred languages for text tracks.
     *
     * @param preferredTextLanguages Preferred text languages as IETF BCP 47 conformant tags in
     *     order of preference, or an empty array to select the default track if there is one, or no
     *     track otherwise.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setPreferredTextLanguages(String... preferredTextLanguages) {
      this.preferredTextLanguages = normalizeLanguageCodes(preferredTextLanguages);
      return this;
    }

    /**
     * Sets the preferred {@link C.RoleFlags} for text tracks.
     *
     * @param preferredTextRoleFlags Preferred text role flags.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setPreferredTextRoleFlags(@C.RoleFlags int preferredTextRoleFlags) {
      this.preferredTextRoleFlags = preferredTextRoleFlags;
      return this;
    }

    /**
     * Sets a bitmask of selection flags that are ignored for text track selections.
     *
     * @param ignoredTextSelectionFlags A bitmask of {@link C.SelectionFlags} that are ignored for
     *     text track selections.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setIgnoredTextSelectionFlags(@C.SelectionFlags int ignoredTextSelectionFlags) {
      this.ignoredTextSelectionFlags = ignoredTextSelectionFlags;
      return this;
    }

    /**
     * Sets whether a text track with undetermined language should be selected if no track with
     * {@link #setPreferredTextLanguages(String...) a preferred language} is available, or if the
     * preferred language is unset.
     *
     * @param selectUndeterminedTextLanguage Whether a text track with undetermined language should
     *     be selected if no preferred language track is available.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setSelectUndeterminedTextLanguage(boolean selectUndeterminedTextLanguage) {
      this.selectUndeterminedTextLanguage = selectUndeterminedTextLanguage;
      return this;
    }

    // Image

    /**
     * Sets whether an image track would be selected over a video track if both are available.
     *
     * @param isPrioritizeImageOverVideoEnabled Whether an image track would be selected over a
     *     video track if both are available.
     * @return This builder.
     */
    @UnstableApi
    @CanIgnoreReturnValue
    public Builder setPrioritizeImageOverVideoEnabled(boolean isPrioritizeImageOverVideoEnabled) {
      this.isPrioritizeImageOverVideoEnabled = isPrioritizeImageOverVideoEnabled;
      return this;
    }

    // General

    /**
     * Sets whether to force selection of the single lowest bitrate audio and video tracks that
     * comply with all other constraints.
     *
     * @param forceLowestBitrate Whether to force selection of the single lowest bitrate audio and
     *     video tracks.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setForceLowestBitrate(boolean forceLowestBitrate) {
      this.forceLowestBitrate = forceLowestBitrate;
      return this;
    }

    /**
     * Sets whether to force selection of the highest bitrate audio and video tracks that comply
     * with all other constraints.
     *
     * @param forceHighestSupportedBitrate Whether to force selection of the highest bitrate audio
     *     and video tracks.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setForceHighestSupportedBitrate(boolean forceHighestSupportedBitrate) {
      this.forceHighestSupportedBitrate = forceHighestSupportedBitrate;
      return this;
    }

    /** Adds an override, replacing any override for the same {@link TrackGroup}. */
    @CanIgnoreReturnValue
    public Builder addOverride(TrackSelectionOverride override) {
      overrides.put(override.mediaTrackGroup, override);
      return this;
    }

    /** Sets an override, replacing all existing overrides with the same track type. */
    @CanIgnoreReturnValue
    public Builder setOverrideForType(TrackSelectionOverride override) {
      clearOverridesOfType(override.getType());
      overrides.put(override.mediaTrackGroup, override);
      return this;
    }

    /** Removes the override for the provided media {@link TrackGroup}, if there is one. */
    @CanIgnoreReturnValue
    public Builder clearOverride(TrackGroup mediaTrackGroup) {
      overrides.remove(mediaTrackGroup);
      return this;
    }

    /** Removes all overrides of the provided track type. */
    @CanIgnoreReturnValue
    public Builder clearOverridesOfType(@C.TrackType int trackType) {
      Iterator<TrackSelectionOverride> it = overrides.values().iterator();
      while (it.hasNext()) {
        TrackSelectionOverride override = it.next();
        if (override.getType() == trackType) {
          it.remove();
        }
      }
      return this;
    }

    /** Removes all overrides. */
    @CanIgnoreReturnValue
    public Builder clearOverrides() {
      overrides.clear();
      return this;
    }

    /**
     * Sets the disabled track types, preventing all tracks of those types from being selected for
     * playback. Any previously disabled track types are cleared.
     *
     * @param disabledTrackTypes The track types to disable.
     * @return This builder.
     * @deprecated Use {@link #setTrackTypeDisabled(int, boolean)}.
     */
    @CanIgnoreReturnValue
    @Deprecated
    @UnstableApi
    public Builder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) {
      this.disabledTrackTypes.clear();
      this.disabledTrackTypes.addAll(disabledTrackTypes);
      return this;
    }

    /**
     * Sets whether a track type is disabled. If disabled, no tracks of the specified type will be
     * selected for playback.
     *
     * @param trackType The track type.
     * @param disabled Whether the track type should be disabled.
     * @return This builder.
     */
    @CanIgnoreReturnValue
    public Builder setTrackTypeDisabled(@C.TrackType int trackType, boolean disabled) {
      if (disabled) {
        disabledTrackTypes.add(trackType);
      } else {
        disabledTrackTypes.remove(trackType);
      }
      return this;
    }

    /** Builds a {@link TrackSelectionParameters} instance with the selected values. */
    public TrackSelectionParameters build() {
      return new TrackSelectionParameters(this);
    }

    private static ImmutableList<String> normalizeLanguageCodes(String[] preferredTextLanguages) {
      ImmutableList.Builder<String> listBuilder = ImmutableList.builder();
      for (String language : checkNotNull(preferredTextLanguages)) {
        listBuilder.add(Util.normalizeLanguageCode(checkNotNull(language)));
      }
      return listBuilder.build();
    }
  }

  /** Preferences and constraints for enabling audio offload. */
  @UnstableApi
  public static final class AudioOffloadPreferences {

    /**
     * The preference level for enabling audio offload on the audio sink. One of {@link
     * #AUDIO_OFFLOAD_MODE_REQUIRED}, {@link #AUDIO_OFFLOAD_MODE_ENABLED}, or {@link
     * #AUDIO_OFFLOAD_MODE_DISABLED}.
     */
    @Documented
    @Retention(SOURCE)
    @Target(TYPE_USE)
    @IntDef({
      AUDIO_OFFLOAD_MODE_REQUIRED,
      AUDIO_OFFLOAD_MODE_ENABLED,
      AUDIO_OFFLOAD_MODE_DISABLED,
    })
    public @interface AudioOffloadMode {}

    /**
     * The track selector will only select tracks that with the renderer capabilities provide an
     * audio offload compatible playback scenario. If it is impossible to create an
     * offload-compatible track selection, then no tracks will be selected.
     */
    public static final int AUDIO_OFFLOAD_MODE_REQUIRED = 2;

    /**
     * The track selector will enable audio offload if the selected tracks and renderer capabilities
     * are compatible.
     */
    public static final int AUDIO_OFFLOAD_MODE_ENABLED = 1;

    /**
     * The track selector will disable audio offload on the audio sink. Track selection will not
     * take into consideration whether or not a track is offload compatible.
     */
    public static final int AUDIO_OFFLOAD_MODE_DISABLED = 0;

    /**
     * A builder for {@link AudioOffloadPreferences}. See the {@link AudioOffloadPreferences}
     * documentation for explanations of the parameters that can be configured using this builder.
     */
    public static final class Builder {
      private @AudioOffloadMode int audioOffloadMode;
      private boolean isGaplessSupportRequired;
      private boolean isSpeedChangeSupportRequired;

      public Builder() {
        this.audioOffloadMode = AUDIO_OFFLOAD_MODE_DISABLED;
        this.isGaplessSupportRequired = false;
        this.isSpeedChangeSupportRequired = false;
      }

      /**
       * Sets the audio offload mode preferences. For instance if the preferred mode is
       * enabled/disabled or if offload is required for playback. Default value is {@link
       * #AUDIO_OFFLOAD_MODE_DISABLED}.
       *
       * @param audioOffloadMode for enabling/disabling offload. One of {@link
       *     #AUDIO_OFFLOAD_MODE_REQUIRED}, {@link #AUDIO_OFFLOAD_MODE_ENABLED}, or {@link
       *     #AUDIO_OFFLOAD_MODE_DISABLED}.
       * @return This builder.
       */
      @CanIgnoreReturnValue
      public Builder setAudioOffloadMode(@AudioOffloadMode int audioOffloadMode) {
        this.audioOffloadMode = audioOffloadMode;
        return this;
      }

      /**
       * Sets a constraint on audio offload enablement. If {@code true} then audio offload will be
       * enabled only if the device supports gapless transitions during offload or the selected
       * audio is not gapless. Default value is {@code false}.
       *
       * @param isGaplessSupportRequired for playing gapless audio offloaded.
       * @return This builder.
       */
      @CanIgnoreReturnValue
      public Builder setIsGaplessSupportRequired(boolean isGaplessSupportRequired) {
        this.isGaplessSupportRequired = isGaplessSupportRequired;
        return this;
      }

      /**
       * Sets a constraint on audio offload enablement. If {@code true}, then audio offload will be
       * enabled only if the device supports changing playback speed during offload. Default value
       * is {@code false}.
       *
       * @param isSpeedChangeSupportRequired for playing audio offloaded.
       * @return This builder.
       */
      @CanIgnoreReturnValue
      public Builder setIsSpeedChangeSupportRequired(boolean isSpeedChangeSupportRequired) {
        this.isSpeedChangeSupportRequired = isSpeedChangeSupportRequired;
        return this;
      }

      /** Builds a {@link TrackSelectionParameters} instance with the selected values. */
      public AudioOffloadPreferences build() {
        return new AudioOffloadPreferences(this);
      }
    }

    /** Returns an instance configured with default values. */
    public static final AudioOffloadPreferences DEFAULT =
        new AudioOffloadPreferences.Builder().build();

    /** The preferred offload mode setting for audio playback. */
    public final @AudioOffloadMode int audioOffloadMode;

    /**
     * A constraint on enabling offload. If {@code true}, then audio offload will be enabled only if
     * the device supports gapless transitions during offload or the selected audio is not gapless.
     */
    public final boolean isGaplessSupportRequired;

    /**
     * A constraint on enabling offload. If {@code true}, then audio offload will be enabled only if
     * the device supports changing playback speed during offload.
     */
    public final boolean isSpeedChangeSupportRequired;

    private AudioOffloadPreferences(Builder builder) {
      this.audioOffloadMode = builder.audioOffloadMode;
      this.isGaplessSupportRequired = builder.isGaplessSupportRequired;
      this.isSpeedChangeSupportRequired = builder.isSpeedChangeSupportRequired;
    }

    /**
     * Creates a new {@link AudioOffloadPreferences.Builder}, copying the initial values from this
     * instance.
     */
    public AudioOffloadPreferences.Builder buildUpon() {
      return new AudioOffloadPreferences.Builder()
          .setAudioOffloadMode(audioOffloadMode)
          .setIsGaplessSupportRequired(isGaplessSupportRequired)
          .setIsSpeedChangeSupportRequired(isSpeedChangeSupportRequired);
    }

    @Override
    public boolean equals(@Nullable Object obj) {
      if (this == obj) {
        return true;
      }
      if (obj == null || getClass() != obj.getClass()) {
        return false;
      }
      AudioOffloadPreferences other = (AudioOffloadPreferences) obj;
      return audioOffloadMode == other.audioOffloadMode
          && isGaplessSupportRequired == other.isGaplessSupportRequired
          && isSpeedChangeSupportRequired == other.isSpeedChangeSupportRequired;
    }

    @Override
    public int hashCode() {
      int result = 1;
      result = 31 * result + audioOffloadMode;
      result = 31 * result + (isGaplessSupportRequired ? 1 : 0);
      result = 31 * result + (isSpeedChangeSupportRequired ? 1 : 0);
      return result;
    }

    private static final String FIELD_AUDIO_OFFLOAD_MODE_PREFERENCE = Util.intToStringMaxRadix(1);
    private static final String FIELD_IS_GAPLESS_SUPPORT_REQUIRED = Util.intToStringMaxRadix(2);
    private static final String FIELD_IS_SPEED_CHANGE_SUPPORT_REQUIRED =
        Util.intToStringMaxRadix(3);

    public Bundle toBundle() {
      Bundle bundle = new Bundle();
      bundle.putInt(FIELD_AUDIO_OFFLOAD_MODE_PREFERENCE, audioOffloadMode);
      bundle.putBoolean(FIELD_IS_GAPLESS_SUPPORT_REQUIRED, isGaplessSupportRequired);
      bundle.putBoolean(FIELD_IS_SPEED_CHANGE_SUPPORT_REQUIRED, isSpeedChangeSupportRequired);
      return bundle;
    }

    /** Construct an instance from a {@link Bundle} produced by {@link #toBundle()}. */
    public static AudioOffloadPreferences fromBundle(Bundle bundle) {
      return new AudioOffloadPreferences.Builder()
          .setAudioOffloadMode(
              bundle.getInt(FIELD_AUDIO_OFFLOAD_MODE_PREFERENCE, DEFAULT.audioOffloadMode))
          .setIsGaplessSupportRequired(
              bundle.getBoolean(
                  FIELD_IS_GAPLESS_SUPPORT_REQUIRED, DEFAULT.isGaplessSupportRequired))
          .setIsSpeedChangeSupportRequired(
              bundle.getBoolean(
                  FIELD_IS_SPEED_CHANGE_SUPPORT_REQUIRED, DEFAULT.isSpeedChangeSupportRequired))
          .build();
    }
  }

  /**
   * An instance with default values, except those obtained from the {@link Context}.
   *
   * <p>If possible, use {@link #getDefaults(Context)} instead.
   *
   * <p>This instance will not have the following settings:
   *
   * <ul>
   *   <li>{@link Builder#setViewportSizeToPhysicalDisplaySize(Context, boolean) Viewport
   *       constraints} configured for the primary display.
   *   <li>{@link Builder#setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings(Context)
   *       Preferred text language and role flags} configured to the accessibility settings of
   *       {@link CaptioningManager}.
   * </ul>
   */
  @UnstableApi
  @SuppressWarnings("deprecation")
  public static final TrackSelectionParameters DEFAULT_WITHOUT_CONTEXT = new Builder().build();

  /**
   * @deprecated This instance is not configured using {@link Context} constraints. Use {@link
   *     #getDefaults(Context)} instead.
   */
  @UnstableApi @Deprecated
  public static final TrackSelectionParameters DEFAULT = DEFAULT_WITHOUT_CONTEXT;

  /** Returns an instance configured with default values. */
  public static TrackSelectionParameters getDefaults(Context context) {
    return new Builder(context).build();
  }

  // Video
  /**
   * Maximum allowed video width in pixels. The default value is {@link Integer#MAX_VALUE} (i.e. no
   * constraint).
   *
   * <p>To constrain adaptive video track selections to be suitable for a given viewport (the region
   * of the display within which video will be played), use ({@link #viewportWidth}, {@link
   * #viewportHeight} and {@link #viewportOrientationMayChange}) instead.
   */
  public final int maxVideoWidth;

  /**
   * Maximum allowed video height in pixels. The default value is {@link Integer#MAX_VALUE} (i.e. no
   * constraint).
   *
   * <p>To constrain adaptive video track selections to be suitable for a given viewport (the region
   * of the display within which video will be played), use ({@link #viewportWidth}, {@link
   * #viewportHeight} and {@link #viewportOrientationMayChange}) instead.
   */
  public final int maxVideoHeight;

  /**
   * Maximum allowed video frame rate in hertz. The default value is {@link Integer#MAX_VALUE} (i.e.
   * no constraint).
   */
  public final int maxVideoFrameRate;

  /**
   * Maximum allowed video bitrate in bits per second. The default value is {@link
   * Integer#MAX_VALUE} (i.e. no constraint).
   */
  public final int maxVideoBitrate;

  /** Minimum allowed video width in pixels. The default value is 0 (i.e. no constraint). */
  public final int minVideoWidth;

  /** Minimum allowed video height in pixels. The default value is 0 (i.e. no constraint). */
  public final int minVideoHeight;

  /** Minimum allowed video frame rate in hertz. The default value is 0 (i.e. no constraint). */
  public final int minVideoFrameRate;

  /**
   * Minimum allowed video bitrate in bits per second. The default value is 0 (i.e. no constraint).
   */
  public final int minVideoBitrate;

  /**
   * Viewport width in pixels. Constrains video track selections for adaptive content so that only
   * tracks suitable for the viewport are selected. The default value is the physical width of the
   * primary display, in pixels.
   */
  public final int viewportWidth;

  /**
   * Viewport height in pixels. Constrains video track selections for adaptive content so that only
   * tracks suitable for the viewport are selected. The default value is the physical height of the
   * primary display, in pixels.
   */
  public final int viewportHeight;

  /**
   * Whether the viewport orientation may change during playback. Constrains video track selections
   * for adaptive content so that only tracks suitable for the viewport are selected. The default
   * value is {@code true}.
   */
  public final boolean viewportOrientationMayChange;

  /**
   * The preferred sample MIME types for video tracks in order of preference, or an empty list for
   * no preference. The default is an empty list.
   */
  public final ImmutableList<String> preferredVideoMimeTypes;

  /**
   * The preferred {@link C.RoleFlags} for video tracks. {@code 0} selects the default track if
   * there is one, or the first track if there's no default. The default value is {@code 0}.
   */
  public final @C.RoleFlags int preferredVideoRoleFlags;

  // Audio
  /**
   * The preferred languages for audio and forced text tracks as IETF BCP 47 conformant tags in
   * order of preference. An empty list selects the default track, or the first track if there's no
   * default. The default value is an empty list.
   */
  public final ImmutableList<String> preferredAudioLanguages;

  /**
   * The preferred {@link C.RoleFlags} for audio tracks. {@code 0} selects the default track if
   * there is one, or the first track if there's no default. The default value is {@code 0}.
   */
  public final @C.RoleFlags int preferredAudioRoleFlags;

  /**
   * Maximum allowed audio channel count. The default value is {@link Integer#MAX_VALUE} (i.e. no
   * constraint).
   */
  public final int maxAudioChannelCount;

  /**
   * Maximum allowed audio bitrate in bits per second. The default value is {@link
   * Integer#MAX_VALUE} (i.e. no constraint).
   */
  public final int maxAudioBitrate;

  /**
   * The preferred sample MIME types for audio tracks in order of preference, or an empty list for
   * no preference. The default is an empty list.
   */
  public final ImmutableList<String> preferredAudioMimeTypes;

  /**
   * The preferred offload mode settings for audio playback. The default is {@link
   * AudioOffloadPreferences#DEFAULT}.
   */
  @UnstableApi public final AudioOffloadPreferences audioOffloadPreferences;

  // Text
  /**
   * The preferred languages for text tracks as IETF BCP 47 conformant tags in order of preference.
   * An empty list selects the default track if there is one, or no track otherwise. The default
   * value is an empty list, or the language of the accessibility {@link CaptioningManager} if
   * enabled.
   */
  public final ImmutableList<String> preferredTextLanguages;

  /**
   * The preferred {@link C.RoleFlags} for text tracks. {@code 0} selects the default track if there
   * is one, or no track otherwise. The default value is {@code 0}, or {@link C#ROLE_FLAG_SUBTITLE}
   * | {@link C#ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND} if the accessibility {@link CaptioningManager}
   * is enabled.
   */
  public final @C.RoleFlags int preferredTextRoleFlags;

  /**
   * Bitmask of selection flags that are ignored for text track selections. See {@link
   * C.SelectionFlags}. The default value is {@code 0} (i.e., no flags are ignored).
   */
  public final @C.SelectionFlags int ignoredTextSelectionFlags;

  /**
   * Whether a text track with undetermined language should be selected if no track with {@link
   * #preferredTextLanguages} is available, or if {@link #preferredTextLanguages} is unset. The
   * default value is {@code false}.
   */
  public final boolean selectUndeterminedTextLanguage;

  // Image
  /**
   * Whether an image track will be selected over a video track if both are available. The default
   * value is {@code false}.
   */
  @UnstableApi public final boolean isPrioritizeImageOverVideoEnabled;

  // General
  /**
   * Whether to force selection of the single lowest bitrate audio and video tracks that comply with
   * all other constraints. The default value is {@code false}.
   */
  public final boolean forceLowestBitrate;

  /**
   * Whether to force selection of the highest bitrate audio and video tracks that comply with all
   * other constraints. The default value is {@code false}.
   */
  public final boolean forceHighestSupportedBitrate;

  /** Overrides to force selection of specific tracks. */
  public final ImmutableMap<TrackGroup, TrackSelectionOverride> overrides;

  /**
   * The track types that are disabled. No track of a disabled type will be selected, thus no track
   * type contained in the set will be played. The default value is that no track type is disabled
   * (empty set).
   */
  public final ImmutableSet<@C.TrackType Integer> disabledTrackTypes;

  @UnstableApi
  protected TrackSelectionParameters(Builder builder) {
    // Video
    this.maxVideoWidth = builder.maxVideoWidth;
    this.maxVideoHeight = builder.maxVideoHeight;
    this.maxVideoFrameRate = builder.maxVideoFrameRate;
    this.maxVideoBitrate = builder.maxVideoBitrate;
    this.minVideoWidth = builder.minVideoWidth;
    this.minVideoHeight = builder.minVideoHeight;
    this.minVideoFrameRate = builder.minVideoFrameRate;
    this.minVideoBitrate = builder.minVideoBitrate;
    this.viewportWidth = builder.viewportWidth;
    this.viewportHeight = builder.viewportHeight;
    this.viewportOrientationMayChange = builder.viewportOrientationMayChange;
    this.preferredVideoMimeTypes = builder.preferredVideoMimeTypes;
    this.preferredVideoRoleFlags = builder.preferredVideoRoleFlags;
    // Audio
    this.preferredAudioLanguages = builder.preferredAudioLanguages;
    this.preferredAudioRoleFlags = builder.preferredAudioRoleFlags;
    this.maxAudioChannelCount = builder.maxAudioChannelCount;
    this.maxAudioBitrate = builder.maxAudioBitrate;
    this.preferredAudioMimeTypes = builder.preferredAudioMimeTypes;
    this.audioOffloadPreferences = builder.audioOffloadPreferences;
    // Text
    this.preferredTextLanguages = builder.preferredTextLanguages;
    this.preferredTextRoleFlags = builder.preferredTextRoleFlags;
    this.ignoredTextSelectionFlags = builder.ignoredTextSelectionFlags;
    this.selectUndeterminedTextLanguage = builder.selectUndeterminedTextLanguage;
    // Image
    this.isPrioritizeImageOverVideoEnabled = builder.isPrioritizeImageOverVideoEnabled;
    // General
    this.forceLowestBitrate = builder.forceLowestBitrate;
    this.forceHighestSupportedBitrate = builder.forceHighestSupportedBitrate;
    this.overrides = ImmutableMap.copyOf(builder.overrides);
    this.disabledTrackTypes = ImmutableSet.copyOf(builder.disabledTrackTypes);
  }

  /** Creates a new {@link Builder}, copying the initial values from this instance. */
  public Builder buildUpon() {
    return new Builder(this);
  }

  @Override
  @SuppressWarnings("EqualsGetClass")
  public boolean equals(@Nullable Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null || getClass() != obj.getClass()) {
      return false;
    }
    TrackSelectionParameters other = (TrackSelectionParameters) obj;
    // Video
    return maxVideoWidth == other.maxVideoWidth
        && maxVideoHeight == other.maxVideoHeight
        && maxVideoFrameRate == other.maxVideoFrameRate
        && maxVideoBitrate == other.maxVideoBitrate
        && minVideoWidth == other.minVideoWidth
        && minVideoHeight == other.minVideoHeight
        && minVideoFrameRate == other.minVideoFrameRate
        && minVideoBitrate == other.minVideoBitrate
        && viewportOrientationMayChange == other.viewportOrientationMayChange
        && viewportWidth == other.viewportWidth
        && viewportHeight == other.viewportHeight
        && preferredVideoMimeTypes.equals(other.preferredVideoMimeTypes)
        && preferredVideoRoleFlags == other.preferredVideoRoleFlags
        // Audio
        && preferredAudioLanguages.equals(other.preferredAudioLanguages)
        && preferredAudioRoleFlags == other.preferredAudioRoleFlags
        && maxAudioChannelCount == other.maxAudioChannelCount
        && maxAudioBitrate == other.maxAudioBitrate
        && preferredAudioMimeTypes.equals(other.preferredAudioMimeTypes)
        && audioOffloadPreferences.equals(other.audioOffloadPreferences)
        // Text
        && preferredTextLanguages.equals(other.preferredTextLanguages)
        && preferredTextRoleFlags == other.preferredTextRoleFlags
        && ignoredTextSelectionFlags == other.ignoredTextSelectionFlags
        && selectUndeterminedTextLanguage == other.selectUndeterminedTextLanguage
        // Image
        && isPrioritizeImageOverVideoEnabled == other.isPrioritizeImageOverVideoEnabled
        // General
        && forceLowestBitrate == other.forceLowestBitrate
        && forceHighestSupportedBitrate == other.forceHighestSupportedBitrate
        && overrides.equals(other.overrides)
        && disabledTrackTypes.equals(other.disabledTrackTypes);
  }

  @Override
  public int hashCode() {
    int result = 1;
    // Video
    result = 31 * result + maxVideoWidth;
    result = 31 * result + maxVideoHeight;
    result = 31 * result + maxVideoFrameRate;
    result = 31 * result + maxVideoBitrate;
    result = 31 * result + minVideoWidth;
    result = 31 * result + minVideoHeight;
    result = 31 * result + minVideoFrameRate;
    result = 31 * result + minVideoBitrate;
    result = 31 * result + (viewportOrientationMayChange ? 1 : 0);
    result = 31 * result + viewportWidth;
    result = 31 * result + viewportHeight;
    result = 31 * result + preferredVideoMimeTypes.hashCode();
    result = 31 * result + preferredVideoRoleFlags;
    // Audio
    result = 31 * result + preferredAudioLanguages.hashCode();
    result = 31 * result + preferredAudioRoleFlags;
    result = 31 * result + maxAudioChannelCount;
    result = 31 * result + maxAudioBitrate;
    result = 31 * result + preferredAudioMimeTypes.hashCode();
    result = 31 * result + audioOffloadPreferences.hashCode();
    // Text
    result = 31 * result + preferredTextLanguages.hashCode();
    result = 31 * result + preferredTextRoleFlags;
    result = 31 * result + ignoredTextSelectionFlags;
    result = 31 * result + (selectUndeterminedTextLanguage ? 1 : 0);
    // Image
    result = 31 * result + (isPrioritizeImageOverVideoEnabled ? 1 : 0);
    // General
    result = 31 * result + (forceLowestBitrate ? 1 : 0);
    result = 31 * result + (forceHighestSupportedBitrate ? 1 : 0);
    result = 31 * result + overrides.hashCode();
    result = 31 * result + disabledTrackTypes.hashCode();
    return result;
  }

  private static final String FIELD_PREFERRED_AUDIO_LANGUAGES = Util.intToStringMaxRadix(1);
  private static final String FIELD_PREFERRED_AUDIO_ROLE_FLAGS = Util.intToStringMaxRadix(2);
  private static final String FIELD_PREFERRED_TEXT_LANGUAGES = Util.intToStringMaxRadix(3);
  private static final String FIELD_PREFERRED_TEXT_ROLE_FLAGS = Util.intToStringMaxRadix(4);
  private static final String FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE = Util.intToStringMaxRadix(5);
  private static final String FIELD_MAX_VIDEO_WIDTH = Util.intToStringMaxRadix(6);
  private static final String FIELD_MAX_VIDEO_HEIGHT = Util.intToStringMaxRadix(7);
  private static final String FIELD_MAX_VIDEO_FRAMERATE = Util.intToStringMaxRadix(8);
  private static final String FIELD_MAX_VIDEO_BITRATE = Util.intToStringMaxRadix(9);
  private static final String FIELD_MIN_VIDEO_WIDTH = Util.intToStringMaxRadix(10);
  private static final String FIELD_MIN_VIDEO_HEIGHT = Util.intToStringMaxRadix(11);
  private static final String FIELD_MIN_VIDEO_FRAMERATE = Util.intToStringMaxRadix(12);
  private static final String FIELD_MIN_VIDEO_BITRATE = Util.intToStringMaxRadix(13);
  private static final String FIELD_VIEWPORT_WIDTH = Util.intToStringMaxRadix(14);
  private static final String FIELD_VIEWPORT_HEIGHT = Util.intToStringMaxRadix(15);
  private static final String FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE = Util.intToStringMaxRadix(16);
  private static final String FIELD_PREFERRED_VIDEO_MIMETYPES = Util.intToStringMaxRadix(17);
  private static final String FIELD_MAX_AUDIO_CHANNEL_COUNT = Util.intToStringMaxRadix(18);
  private static final String FIELD_MAX_AUDIO_BITRATE = Util.intToStringMaxRadix(19);
  private static final String FIELD_PREFERRED_AUDIO_MIME_TYPES = Util.intToStringMaxRadix(20);
  private static final String FIELD_FORCE_LOWEST_BITRATE = Util.intToStringMaxRadix(21);
  private static final String FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE = Util.intToStringMaxRadix(22);
  private static final String FIELD_SELECTION_OVERRIDES = Util.intToStringMaxRadix(23);
  private static final String FIELD_DISABLED_TRACK_TYPE = Util.intToStringMaxRadix(24);
  private static final String FIELD_PREFERRED_VIDEO_ROLE_FLAGS = Util.intToStringMaxRadix(25);
  private static final String FIELD_IGNORED_TEXT_SELECTION_FLAGS = Util.intToStringMaxRadix(26);
  private static final String FIELD_AUDIO_OFFLOAD_MODE_PREFERENCE = Util.intToStringMaxRadix(27);
  private static final String FIELD_IS_GAPLESS_SUPPORT_REQUIRED = Util.intToStringMaxRadix(28);
  private static final String FIELD_IS_SPEED_CHANGE_SUPPORT_REQUIRED = Util.intToStringMaxRadix(29);
  private static final String FIELD_AUDIO_OFFLOAD_PREFERENCES = Util.intToStringMaxRadix(30);
  private static final String FIELD_IS_PREFER_IMAGE_OVER_VIDEO_ENABLED =
      Util.intToStringMaxRadix(31);

  /**
   * Defines a minimum field ID value for subclasses to use when implementing {@link #toBundle()}
   * and delegating to {@link Builder#Builder(Bundle)}.
   *
   * <p>Subclasses should obtain keys for their {@link Bundle} representation by applying a
   * non-negative offset on this constant and passing the result to {@link
   * Util#intToStringMaxRadix(int)}.
   */
  @UnstableApi protected static final int FIELD_CUSTOM_ID_BASE = 1000;

  @CallSuper
  public Bundle toBundle() {
    Bundle bundle = new Bundle();

    // Video
    bundle.putInt(FIELD_MAX_VIDEO_WIDTH, maxVideoWidth);
    bundle.putInt(FIELD_MAX_VIDEO_HEIGHT, maxVideoHeight);
    bundle.putInt(FIELD_MAX_VIDEO_FRAMERATE, maxVideoFrameRate);
    bundle.putInt(FIELD_MAX_VIDEO_BITRATE, maxVideoBitrate);
    bundle.putInt(FIELD_MIN_VIDEO_WIDTH, minVideoWidth);
    bundle.putInt(FIELD_MIN_VIDEO_HEIGHT, minVideoHeight);
    bundle.putInt(FIELD_MIN_VIDEO_FRAMERATE, minVideoFrameRate);
    bundle.putInt(FIELD_MIN_VIDEO_BITRATE, minVideoBitrate);
    bundle.putInt(FIELD_VIEWPORT_WIDTH, viewportWidth);
    bundle.putInt(FIELD_VIEWPORT_HEIGHT, viewportHeight);
    bundle.putBoolean(FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE, viewportOrientationMayChange);
    bundle.putStringArray(
        FIELD_PREFERRED_VIDEO_MIMETYPES, preferredVideoMimeTypes.toArray(new String[0]));
    bundle.putInt(FIELD_PREFERRED_VIDEO_ROLE_FLAGS, preferredVideoRoleFlags);
    // Audio
    bundle.putStringArray(
        FIELD_PREFERRED_AUDIO_LANGUAGES, preferredAudioLanguages.toArray(new String[0]));
    bundle.putInt(FIELD_PREFERRED_AUDIO_ROLE_FLAGS, preferredAudioRoleFlags);
    bundle.putInt(FIELD_MAX_AUDIO_CHANNEL_COUNT, maxAudioChannelCount);
    bundle.putInt(FIELD_MAX_AUDIO_BITRATE, maxAudioBitrate);
    bundle.putStringArray(
        FIELD_PREFERRED_AUDIO_MIME_TYPES, preferredAudioMimeTypes.toArray(new String[0]));
    // Text
    bundle.putStringArray(
        FIELD_PREFERRED_TEXT_LANGUAGES, preferredTextLanguages.toArray(new String[0]));
    bundle.putInt(FIELD_PREFERRED_TEXT_ROLE_FLAGS, preferredTextRoleFlags);
    bundle.putInt(FIELD_IGNORED_TEXT_SELECTION_FLAGS, ignoredTextSelectionFlags);
    bundle.putBoolean(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE, selectUndeterminedTextLanguage);
    bundle.putInt(FIELD_AUDIO_OFFLOAD_MODE_PREFERENCE, audioOffloadPreferences.audioOffloadMode);
    bundle.putBoolean(
        FIELD_IS_GAPLESS_SUPPORT_REQUIRED, audioOffloadPreferences.isGaplessSupportRequired);
    bundle.putBoolean(
        FIELD_IS_SPEED_CHANGE_SUPPORT_REQUIRED,
        audioOffloadPreferences.isSpeedChangeSupportRequired);
    bundle.putBundle(FIELD_AUDIO_OFFLOAD_PREFERENCES, audioOffloadPreferences.toBundle());
    // Image
    bundle.putBoolean(FIELD_IS_PREFER_IMAGE_OVER_VIDEO_ENABLED, isPrioritizeImageOverVideoEnabled);
    // General
    bundle.putBoolean(FIELD_FORCE_LOWEST_BITRATE, forceLowestBitrate);
    bundle.putBoolean(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE, forceHighestSupportedBitrate);
    bundle.putParcelableArrayList(
        FIELD_SELECTION_OVERRIDES,
        toBundleArrayList(overrides.values(), TrackSelectionOverride::toBundle));
    bundle.putIntArray(FIELD_DISABLED_TRACK_TYPE, Ints.toArray(disabledTrackTypes));

    return bundle;
  }

  /** Construct an instance from a {@link Bundle} produced by {@link #toBundle()}. */
  public static TrackSelectionParameters fromBundle(Bundle bundle) {
    return new Builder(bundle).build();
  }
}