| /* |
| * 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 com.google.android.exoplayer2.trackselection; |
| |
| import android.content.Context; |
| import android.graphics.Point; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.text.TextUtils; |
| import android.util.Pair; |
| import android.util.SparseArray; |
| import android.util.SparseBooleanArray; |
| import androidx.annotation.Nullable; |
| import com.google.android.exoplayer2.C; |
| import com.google.android.exoplayer2.ExoPlaybackException; |
| import com.google.android.exoplayer2.Format; |
| import com.google.android.exoplayer2.Player; |
| import com.google.android.exoplayer2.Renderer; |
| import com.google.android.exoplayer2.RendererCapabilities; |
| import com.google.android.exoplayer2.RendererCapabilities.AdaptiveSupport; |
| import com.google.android.exoplayer2.RendererCapabilities.Capabilities; |
| import com.google.android.exoplayer2.RendererCapabilities.FormatSupport; |
| import com.google.android.exoplayer2.RendererConfiguration; |
| import com.google.android.exoplayer2.source.TrackGroup; |
| import com.google.android.exoplayer2.source.TrackGroupArray; |
| import com.google.android.exoplayer2.upstream.BandwidthMeter; |
| import com.google.android.exoplayer2.util.Assertions; |
| import com.google.android.exoplayer2.util.Util; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.atomic.AtomicReference; |
| import org.checkerframework.checker.initialization.qual.UnderInitialization; |
| import org.checkerframework.checker.nullness.compatqual.NullableType; |
| |
| /** |
| * A default {@link TrackSelector} suitable for most use cases. Track selections are made according |
| * to configurable {@link Parameters}, which can be set by calling {@link |
| * #setParameters(Parameters)}. |
| * |
| * <h3>Modifying parameters</h3> |
| * |
| * To modify only some aspects of the parameters currently used by a selector, it's possible to |
| * obtain a {@link ParametersBuilder} initialized with the current {@link Parameters}. The desired |
| * modifications can be made on the builder, and the resulting {@link Parameters} can then be built |
| * and set on the selector. 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. |
| * Parameters currentParameters = trackSelector.getParameters(); |
| * // Build the resulting parameters. |
| * Parameters newParameters = currentParameters |
| * .buildUpon() |
| * .setMaxVideoSizeSd() |
| * .setPreferredAudioLanguage("deu") |
| * .build(); |
| * // Set the new parameters. |
| * trackSelector.setParameters(newParameters); |
| * }</pre> |
| * |
| * Convenience methods and chaining allow this to be written more concisely as: |
| * |
| * <pre>{@code |
| * trackSelector.setParameters( |
| * trackSelector |
| * .buildUponParameters() |
| * .setMaxVideoSizeSd() |
| * .setPreferredAudioLanguage("deu")); |
| * }</pre> |
| * |
| * Selection {@link Parameters} support many different options, some of which are described below. |
| * |
| * <h3>Selecting specific tracks</h3> |
| * |
| * Track selection overrides can be used to select specific tracks. To specify an override for a |
| * renderer, it's first necessary to obtain the tracks that have been mapped to it: |
| * |
| * <pre>{@code |
| * MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); |
| * TrackGroupArray rendererTrackGroups = mappedTrackInfo == null ? null |
| * : mappedTrackInfo.getTrackGroups(rendererIndex); |
| * }</pre> |
| * |
| * If {@code rendererTrackGroups} is null then there aren't any currently mapped tracks, and so |
| * setting an override isn't possible. Note that a {@link Player.EventListener} registered on the |
| * player can be used to determine when the current tracks (and therefore the mapping) changes. If |
| * {@code rendererTrackGroups} is non-null then an override can be set. The next step is to query |
| * the properties of the available tracks to determine the {@code groupIndex} and the {@code |
| * trackIndices} within the group it that should be selected. The override can then be specified |
| * using {@link ParametersBuilder#setSelectionOverride}: |
| * |
| * <pre>{@code |
| * SelectionOverride selectionOverride = new SelectionOverride(groupIndex, trackIndices); |
| * trackSelector.setParameters( |
| * trackSelector |
| * .buildUponParameters() |
| * .setSelectionOverride(rendererIndex, rendererTrackGroups, selectionOverride)); |
| * }</pre> |
| * |
| * <h3>Constraint based track selection</h3> |
| * |
| * Whilst track selection overrides make it possible to select specific tracks, the recommended way |
| * of controlling which tracks are selected is by specifying constraints. For example consider the |
| * case of wanting to restrict video track selections to SD, and preferring German audio tracks. |
| * Track selection overrides could be used to select specific tracks meeting these criteria, however |
| * a simpler and more flexible approach is to specify these constraints directly: |
| * |
| * <pre>{@code |
| * trackSelector.setParameters( |
| * trackSelector |
| * .buildUponParameters() |
| * .setMaxVideoSizeSd() |
| * .setPreferredAudioLanguage("deu")); |
| * }</pre> |
| * |
| * There are several benefits to using constraint based track selection instead of specific track |
| * overrides: |
| * |
| * <ul> |
| * <li>You can specify constraints before knowing what tracks the media provides. This can |
| * simplify track selection code (e.g. you don't have to listen for changes in the available |
| * tracks before configuring the selector). |
| * <li>Constraints can be applied consistently across all periods in a complex piece of media, |
| * even if those periods contain different tracks. In contrast, a specific track override is |
| * only applied to periods whose tracks match those for which the override was set. |
| * </ul> |
| * |
| * <h3>Disabling renderers</h3> |
| * |
| * Renderers can be disabled using {@link ParametersBuilder#setRendererDisabled}. Disabling a |
| * renderer differs from setting a {@code null} override because the renderer is disabled |
| * unconditionally, whereas a {@code null} override is applied only when the track groups available |
| * to the renderer match the {@link TrackGroupArray} for which it was specified. |
| * |
| * <h3>Tunneling</h3> |
| * |
| * Tunneled playback can be enabled in cases where the combination of renderers and selected tracks |
| * support it. Tunneled playback is enabled by passing an audio session ID to {@link |
| * ParametersBuilder#setTunnelingAudioSessionId(int)}. |
| */ |
| public class DefaultTrackSelector extends MappingTrackSelector { |
| |
| /** |
| * A builder for {@link Parameters}. See the {@link Parameters} documentation for explanations of |
| * the parameters that can be configured using this builder. |
| */ |
| public static final class ParametersBuilder extends TrackSelectionParameters.Builder { |
| |
| // Video |
| private int maxVideoWidth; |
| private int maxVideoHeight; |
| private int maxVideoFrameRate; |
| private int maxVideoBitrate; |
| private boolean exceedVideoConstraintsIfNecessary; |
| private boolean allowVideoMixedMimeTypeAdaptiveness; |
| private boolean allowVideoNonSeamlessAdaptiveness; |
| private int viewportWidth; |
| private int viewportHeight; |
| private boolean viewportOrientationMayChange; |
| // Audio |
| private int maxAudioChannelCount; |
| private int maxAudioBitrate; |
| private boolean exceedAudioConstraintsIfNecessary; |
| private boolean allowAudioMixedMimeTypeAdaptiveness; |
| private boolean allowAudioMixedSampleRateAdaptiveness; |
| private boolean allowAudioMixedChannelCountAdaptiveness; |
| // General |
| private boolean forceLowestBitrate; |
| private boolean forceHighestSupportedBitrate; |
| private boolean exceedRendererCapabilitiesIfNecessary; |
| private int tunnelingAudioSessionId; |
| |
| private final SparseArray<Map<TrackGroupArray, @NullableType SelectionOverride>> |
| selectionOverrides; |
| private final SparseBooleanArray rendererDisabledFlags; |
| |
| /** |
| * @deprecated {@link Context} constraints will not be set using this constructor. Use {@link |
| * #ParametersBuilder(Context)} instead. |
| */ |
| @Deprecated |
| @SuppressWarnings({"deprecation"}) |
| public ParametersBuilder() { |
| super(); |
| setInitialValuesWithoutContext(); |
| selectionOverrides = new SparseArray<>(); |
| rendererDisabledFlags = new SparseBooleanArray(); |
| } |
| |
| /** |
| * Creates a builder with default initial values. |
| * |
| * @param context Any context. |
| */ |
| |
| public ParametersBuilder(Context context) { |
| super(context); |
| setInitialValuesWithoutContext(); |
| selectionOverrides = new SparseArray<>(); |
| rendererDisabledFlags = new SparseBooleanArray(); |
| setViewportSizeToPhysicalDisplaySize(context, /* viewportOrientationMayChange= */ true); |
| } |
| |
| /** |
| * @param initialValues The {@link Parameters} from which the initial values of the builder are |
| * obtained. |
| */ |
| private ParametersBuilder(Parameters initialValues) { |
| super(initialValues); |
| // Video |
| maxVideoWidth = initialValues.maxVideoWidth; |
| maxVideoHeight = initialValues.maxVideoHeight; |
| maxVideoFrameRate = initialValues.maxVideoFrameRate; |
| maxVideoBitrate = initialValues.maxVideoBitrate; |
| exceedVideoConstraintsIfNecessary = initialValues.exceedVideoConstraintsIfNecessary; |
| allowVideoMixedMimeTypeAdaptiveness = initialValues.allowVideoMixedMimeTypeAdaptiveness; |
| allowVideoNonSeamlessAdaptiveness = initialValues.allowVideoNonSeamlessAdaptiveness; |
| viewportWidth = initialValues.viewportWidth; |
| viewportHeight = initialValues.viewportHeight; |
| viewportOrientationMayChange = initialValues.viewportOrientationMayChange; |
| // Audio |
| maxAudioChannelCount = initialValues.maxAudioChannelCount; |
| maxAudioBitrate = initialValues.maxAudioBitrate; |
| exceedAudioConstraintsIfNecessary = initialValues.exceedAudioConstraintsIfNecessary; |
| allowAudioMixedMimeTypeAdaptiveness = initialValues.allowAudioMixedMimeTypeAdaptiveness; |
| allowAudioMixedSampleRateAdaptiveness = initialValues.allowAudioMixedSampleRateAdaptiveness; |
| allowAudioMixedChannelCountAdaptiveness = |
| initialValues.allowAudioMixedChannelCountAdaptiveness; |
| // General |
| forceLowestBitrate = initialValues.forceLowestBitrate; |
| forceHighestSupportedBitrate = initialValues.forceHighestSupportedBitrate; |
| exceedRendererCapabilitiesIfNecessary = initialValues.exceedRendererCapabilitiesIfNecessary; |
| tunnelingAudioSessionId = initialValues.tunnelingAudioSessionId; |
| // Overrides |
| selectionOverrides = cloneSelectionOverrides(initialValues.selectionOverrides); |
| rendererDisabledFlags = initialValues.rendererDisabledFlags.clone(); |
| } |
| |
| // Video |
| |
| /** |
| * Equivalent to {@link #setMaxVideoSize setMaxVideoSize(1279, 719)}. |
| * |
| * @return This builder. |
| */ |
| public ParametersBuilder setMaxVideoSizeSd() { |
| return setMaxVideoSize(1279, 719); |
| } |
| |
| /** |
| * Equivalent to {@link #setMaxVideoSize setMaxVideoSize(Integer.MAX_VALUE, Integer.MAX_VALUE)}. |
| * |
| * @return This builder. |
| */ |
| public ParametersBuilder 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. |
| */ |
| public ParametersBuilder 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. |
| */ |
| public ParametersBuilder 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. |
| */ |
| public ParametersBuilder setMaxVideoBitrate(int maxVideoBitrate) { |
| this.maxVideoBitrate = maxVideoBitrate; |
| return this; |
| } |
| |
| /** |
| * Sets whether to exceed the {@link #setMaxVideoSize(int, int)} and {@link |
| * #setMaxAudioBitrate(int)} constraints when no selection can be made otherwise. |
| * |
| * @param exceedVideoConstraintsIfNecessary Whether to exceed video constraints when no |
| * selection can be made otherwise. |
| * @return This builder. |
| */ |
| public ParametersBuilder setExceedVideoConstraintsIfNecessary( |
| boolean exceedVideoConstraintsIfNecessary) { |
| this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary; |
| return this; |
| } |
| |
| /** |
| * Sets whether to allow adaptive video selections containing mixed MIME types. |
| * |
| * <p>Adaptations between different MIME types may not be completely seamless, in which case |
| * {@link #setAllowVideoNonSeamlessAdaptiveness(boolean)} also needs to be {@code true} for |
| * mixed MIME type selections to be made. |
| * |
| * @param allowVideoMixedMimeTypeAdaptiveness Whether to allow adaptive video selections |
| * containing mixed MIME types. |
| * @return This builder. |
| */ |
| public ParametersBuilder setAllowVideoMixedMimeTypeAdaptiveness( |
| boolean allowVideoMixedMimeTypeAdaptiveness) { |
| this.allowVideoMixedMimeTypeAdaptiveness = allowVideoMixedMimeTypeAdaptiveness; |
| return this; |
| } |
| |
| /** |
| * Sets whether to allow adaptive video selections where adaptation may not be completely |
| * seamless. |
| * |
| * @param allowVideoNonSeamlessAdaptiveness Whether to allow adaptive video selections where |
| * adaptation may not be completely seamless. |
| * @return This builder. |
| */ |
| public ParametersBuilder setAllowVideoNonSeamlessAdaptiveness( |
| boolean allowVideoNonSeamlessAdaptiveness) { |
| this.allowVideoNonSeamlessAdaptiveness = allowVideoNonSeamlessAdaptiveness; |
| 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. |
| */ |
| public ParametersBuilder 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. |
| */ |
| public ParametersBuilder 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. |
| */ |
| public ParametersBuilder setViewportSize( |
| int viewportWidth, int viewportHeight, boolean viewportOrientationMayChange) { |
| this.viewportWidth = viewportWidth; |
| this.viewportHeight = viewportHeight; |
| this.viewportOrientationMayChange = viewportOrientationMayChange; |
| return this; |
| } |
| |
| // Audio |
| |
| @Override |
| public ParametersBuilder setPreferredAudioLanguage(@Nullable String preferredAudioLanguage) { |
| super.setPreferredAudioLanguage(preferredAudioLanguage); |
| return this; |
| } |
| |
| /** |
| * Sets the maximum allowed audio channel count. |
| * |
| * @param maxAudioChannelCount Maximum allowed audio channel count. |
| * @return This builder. |
| */ |
| public ParametersBuilder 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. |
| */ |
| public ParametersBuilder setMaxAudioBitrate(int maxAudioBitrate) { |
| this.maxAudioBitrate = maxAudioBitrate; |
| return this; |
| } |
| |
| /** |
| * Sets whether to exceed the {@link #setMaxAudioChannelCount(int)} and {@link |
| * #setMaxAudioBitrate(int)} constraints when no selection can be made otherwise. |
| * |
| * @param exceedAudioConstraintsIfNecessary Whether to exceed audio constraints when no |
| * selection can be made otherwise. |
| * @return This builder. |
| */ |
| public ParametersBuilder setExceedAudioConstraintsIfNecessary( |
| boolean exceedAudioConstraintsIfNecessary) { |
| this.exceedAudioConstraintsIfNecessary = exceedAudioConstraintsIfNecessary; |
| return this; |
| } |
| |
| /** |
| * Sets whether to allow adaptive audio selections containing mixed MIME types. |
| * |
| * <p>Adaptations between different MIME types may not be completely seamless. |
| * |
| * @param allowAudioMixedMimeTypeAdaptiveness Whether to allow adaptive audio selections |
| * containing mixed MIME types. |
| * @return This builder. |
| */ |
| public ParametersBuilder setAllowAudioMixedMimeTypeAdaptiveness( |
| boolean allowAudioMixedMimeTypeAdaptiveness) { |
| this.allowAudioMixedMimeTypeAdaptiveness = allowAudioMixedMimeTypeAdaptiveness; |
| return this; |
| } |
| |
| /** |
| * Sets whether to allow adaptive audio selections containing mixed sample rates. |
| * |
| * <p>Adaptations between different sample rates may not be completely seamless. |
| * |
| * @param allowAudioMixedSampleRateAdaptiveness Whether to allow adaptive audio selections |
| * containing mixed sample rates. |
| * @return This builder. |
| */ |
| public ParametersBuilder setAllowAudioMixedSampleRateAdaptiveness( |
| boolean allowAudioMixedSampleRateAdaptiveness) { |
| this.allowAudioMixedSampleRateAdaptiveness = allowAudioMixedSampleRateAdaptiveness; |
| return this; |
| } |
| |
| /** |
| * Sets whether to allow adaptive audio selections containing mixed channel counts. |
| * |
| * <p>Adaptations between different channel counts may not be completely seamless. |
| * |
| * @param allowAudioMixedChannelCountAdaptiveness Whether to allow adaptive audio selections |
| * containing mixed channel counts. |
| * @return This builder. |
| */ |
| public ParametersBuilder setAllowAudioMixedChannelCountAdaptiveness( |
| boolean allowAudioMixedChannelCountAdaptiveness) { |
| this.allowAudioMixedChannelCountAdaptiveness = allowAudioMixedChannelCountAdaptiveness; |
| return this; |
| } |
| |
| // Text |
| |
| @Override |
| public ParametersBuilder setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings( |
| Context context) { |
| super.setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings(context); |
| return this; |
| } |
| |
| @Override |
| public ParametersBuilder setPreferredTextLanguage(@Nullable String preferredTextLanguage) { |
| super.setPreferredTextLanguage(preferredTextLanguage); |
| return this; |
| } |
| |
| @Override |
| public ParametersBuilder setPreferredTextRoleFlags(@C.RoleFlags int preferredTextRoleFlags) { |
| super.setPreferredTextRoleFlags(preferredTextRoleFlags); |
| return this; |
| } |
| |
| @Override |
| public ParametersBuilder setSelectUndeterminedTextLanguage( |
| boolean selectUndeterminedTextLanguage) { |
| super.setSelectUndeterminedTextLanguage(selectUndeterminedTextLanguage); |
| return this; |
| } |
| |
| @Override |
| public ParametersBuilder setDisabledTextTrackSelectionFlags( |
| @C.SelectionFlags int disabledTextTrackSelectionFlags) { |
| super.setDisabledTextTrackSelectionFlags(disabledTextTrackSelectionFlags); |
| 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. |
| */ |
| public ParametersBuilder 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. |
| */ |
| public ParametersBuilder setForceHighestSupportedBitrate(boolean forceHighestSupportedBitrate) { |
| this.forceHighestSupportedBitrate = forceHighestSupportedBitrate; |
| return this; |
| } |
| |
| /** |
| * Sets whether to exceed renderer capabilities when no selection can be made otherwise. |
| * |
| * <p>This parameter applies when all of the tracks available for a renderer exceed the |
| * renderer's reported capabilities. If the parameter is {@code true} then the lowest quality |
| * track will still be selected. Playback may succeed if the renderer has under-reported its |
| * true capabilities. If {@code false} then no track will be selected. |
| * |
| * @param exceedRendererCapabilitiesIfNecessary Whether to exceed renderer capabilities when no |
| * selection can be made otherwise. |
| * @return This builder. |
| */ |
| public ParametersBuilder setExceedRendererCapabilitiesIfNecessary( |
| boolean exceedRendererCapabilitiesIfNecessary) { |
| this.exceedRendererCapabilitiesIfNecessary = exceedRendererCapabilitiesIfNecessary; |
| return this; |
| } |
| |
| /** |
| * Sets the audio session id to use when tunneling. |
| * |
| * <p>Enables or disables tunneling. To enable tunneling, pass an audio session id to use when |
| * in tunneling mode. Session ids can be generated using {@link |
| * C#generateAudioSessionIdV21(Context)}. To disable tunneling pass {@link |
| * C#AUDIO_SESSION_ID_UNSET}. Tunneling will only be activated if it's both enabled and |
| * supported by the audio and video renderers for the selected tracks. |
| * |
| * @param tunnelingAudioSessionId The audio session id to use when tunneling, or {@link |
| * C#AUDIO_SESSION_ID_UNSET} to disable tunneling. |
| * @return This builder. |
| */ |
| public ParametersBuilder setTunnelingAudioSessionId(int tunnelingAudioSessionId) { |
| this.tunnelingAudioSessionId = tunnelingAudioSessionId; |
| return this; |
| } |
| |
| // Overrides |
| |
| /** |
| * Sets whether the renderer at the specified index is disabled. Disabling a renderer prevents |
| * the selector from selecting any tracks for it. |
| * |
| * @param rendererIndex The renderer index. |
| * @param disabled Whether the renderer is disabled. |
| * @return This builder. |
| */ |
| public final ParametersBuilder setRendererDisabled(int rendererIndex, boolean disabled) { |
| if (rendererDisabledFlags.get(rendererIndex) == disabled) { |
| // The disabled flag is unchanged. |
| return this; |
| } |
| // Only true values are placed in the array to make it easier to check for equality. |
| if (disabled) { |
| rendererDisabledFlags.put(rendererIndex, true); |
| } else { |
| rendererDisabledFlags.delete(rendererIndex); |
| } |
| return this; |
| } |
| |
| /** |
| * Overrides the track selection for the renderer at the specified index. |
| * |
| * <p>When the {@link TrackGroupArray} mapped to the renderer matches the one provided, the |
| * override is applied. When the {@link TrackGroupArray} does not match, the override has no |
| * effect. The override replaces any previous override for the specified {@link TrackGroupArray} |
| * for the specified {@link Renderer}. |
| * |
| * <p>Passing a {@code null} override will cause the renderer to be disabled when the {@link |
| * TrackGroupArray} mapped to it matches the one provided. When the {@link TrackGroupArray} does |
| * not match a {@code null} override has no effect. Hence a {@code null} override differs from |
| * disabling the renderer using {@link #setRendererDisabled(int, boolean)} because the renderer |
| * is disabled conditionally on the {@link TrackGroupArray} mapped to it, where-as {@link |
| * #setRendererDisabled(int, boolean)} disables the renderer unconditionally. |
| * |
| * <p>To remove overrides use {@link #clearSelectionOverride(int, TrackGroupArray)}, {@link |
| * #clearSelectionOverrides(int)} or {@link #clearSelectionOverrides()}. |
| * |
| * @param rendererIndex The renderer index. |
| * @param groups The {@link TrackGroupArray} for which the override should be applied. |
| * @param override The override. |
| * @return This builder. |
| */ |
| public final ParametersBuilder setSelectionOverride( |
| int rendererIndex, TrackGroupArray groups, @Nullable SelectionOverride override) { |
| Map<TrackGroupArray, @NullableType SelectionOverride> overrides = |
| selectionOverrides.get(rendererIndex); |
| if (overrides == null) { |
| overrides = new HashMap<>(); |
| selectionOverrides.put(rendererIndex, overrides); |
| } |
| if (overrides.containsKey(groups) && Util.areEqual(overrides.get(groups), override)) { |
| // The override is unchanged. |
| return this; |
| } |
| overrides.put(groups, override); |
| return this; |
| } |
| |
| /** |
| * Clears a track selection override for the specified renderer and {@link TrackGroupArray}. |
| * |
| * @param rendererIndex The renderer index. |
| * @param groups The {@link TrackGroupArray} for which the override should be cleared. |
| * @return This builder. |
| */ |
| public final ParametersBuilder clearSelectionOverride( |
| int rendererIndex, TrackGroupArray groups) { |
| Map<TrackGroupArray, @NullableType SelectionOverride> overrides = |
| selectionOverrides.get(rendererIndex); |
| if (overrides == null || !overrides.containsKey(groups)) { |
| // Nothing to clear. |
| return this; |
| } |
| overrides.remove(groups); |
| if (overrides.isEmpty()) { |
| selectionOverrides.remove(rendererIndex); |
| } |
| return this; |
| } |
| |
| /** |
| * Clears all track selection overrides for the specified renderer. |
| * |
| * @param rendererIndex The renderer index. |
| * @return This builder. |
| */ |
| public final ParametersBuilder clearSelectionOverrides(int rendererIndex) { |
| Map<TrackGroupArray, @NullableType SelectionOverride> overrides = |
| selectionOverrides.get(rendererIndex); |
| if (overrides == null || overrides.isEmpty()) { |
| // Nothing to clear. |
| return this; |
| } |
| selectionOverrides.remove(rendererIndex); |
| return this; |
| } |
| |
| /** |
| * Clears all track selection overrides for all renderers. |
| * |
| * @return This builder. |
| */ |
| public final ParametersBuilder clearSelectionOverrides() { |
| if (selectionOverrides.size() == 0) { |
| // Nothing to clear. |
| return this; |
| } |
| selectionOverrides.clear(); |
| return this; |
| } |
| |
| /** |
| * Builds a {@link Parameters} instance with the selected values. |
| */ |
| public Parameters build() { |
| return new Parameters( |
| // Video |
| maxVideoWidth, |
| maxVideoHeight, |
| maxVideoFrameRate, |
| maxVideoBitrate, |
| exceedVideoConstraintsIfNecessary, |
| allowVideoMixedMimeTypeAdaptiveness, |
| allowVideoNonSeamlessAdaptiveness, |
| viewportWidth, |
| viewportHeight, |
| viewportOrientationMayChange, |
| // Audio |
| preferredAudioLanguage, |
| maxAudioChannelCount, |
| maxAudioBitrate, |
| exceedAudioConstraintsIfNecessary, |
| allowAudioMixedMimeTypeAdaptiveness, |
| allowAudioMixedSampleRateAdaptiveness, |
| allowAudioMixedChannelCountAdaptiveness, |
| // Text |
| preferredTextLanguage, |
| preferredTextRoleFlags, |
| selectUndeterminedTextLanguage, |
| disabledTextTrackSelectionFlags, |
| // General |
| forceLowestBitrate, |
| forceHighestSupportedBitrate, |
| exceedRendererCapabilitiesIfNecessary, |
| tunnelingAudioSessionId, |
| selectionOverrides, |
| rendererDisabledFlags); |
| } |
| |
| private void setInitialValuesWithoutContext(@UnderInitialization ParametersBuilder this) { |
| // Video |
| maxVideoWidth = Integer.MAX_VALUE; |
| maxVideoHeight = Integer.MAX_VALUE; |
| maxVideoFrameRate = Integer.MAX_VALUE; |
| maxVideoBitrate = Integer.MAX_VALUE; |
| exceedVideoConstraintsIfNecessary = true; |
| allowVideoMixedMimeTypeAdaptiveness = false; |
| allowVideoNonSeamlessAdaptiveness = true; |
| viewportWidth = Integer.MAX_VALUE; |
| viewportHeight = Integer.MAX_VALUE; |
| viewportOrientationMayChange = true; |
| // Audio |
| maxAudioChannelCount = Integer.MAX_VALUE; |
| maxAudioBitrate = Integer.MAX_VALUE; |
| exceedAudioConstraintsIfNecessary = true; |
| allowAudioMixedMimeTypeAdaptiveness = false; |
| allowAudioMixedSampleRateAdaptiveness = false; |
| allowAudioMixedChannelCountAdaptiveness = false; |
| // General |
| forceLowestBitrate = false; |
| forceHighestSupportedBitrate = false; |
| exceedRendererCapabilitiesIfNecessary = true; |
| tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET; |
| } |
| |
| private static SparseArray<Map<TrackGroupArray, @NullableType SelectionOverride>> |
| cloneSelectionOverrides( |
| SparseArray<Map<TrackGroupArray, @NullableType SelectionOverride>> selectionOverrides) { |
| SparseArray<Map<TrackGroupArray, @NullableType SelectionOverride>> clone = |
| new SparseArray<>(); |
| for (int i = 0; i < selectionOverrides.size(); i++) { |
| clone.put(selectionOverrides.keyAt(i), new HashMap<>(selectionOverrides.valueAt(i))); |
| } |
| return clone; |
| } |
| } |
| |
| /** |
| * Extends {@link TrackSelectionParameters} by adding fields that are specific to {@link |
| * DefaultTrackSelector}. |
| */ |
| public static final class Parameters extends TrackSelectionParameters { |
| |
| /** |
| * 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 ParametersBuilder#setViewportSizeToPhysicalDisplaySize(Context, boolean) |
| * Viewport constraints} configured for the primary display. |
| * <li>{@link |
| * ParametersBuilder#setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings(Context) |
| * Preferred text language and role flags} configured to the accessibility settings of |
| * {@link android.view.accessibility.CaptioningManager}. |
| * </ul> |
| */ |
| @SuppressWarnings("deprecation") |
| public static final Parameters DEFAULT_WITHOUT_CONTEXT = new ParametersBuilder().build(); |
| |
| /** Returns an instance configured with default values. */ |
| public static Parameters getDefaults(Context context) { |
| return new ParametersBuilder(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; |
| /** |
| * Whether to exceed the {@link #maxVideoWidth}, {@link #maxVideoHeight} and {@link |
| * #maxVideoBitrate} constraints when no selection can be made otherwise. The default value is |
| * {@code true}. |
| */ |
| public final boolean exceedVideoConstraintsIfNecessary; |
| /** |
| * Whether to allow adaptive video selections containing mixed MIME types. Adaptations between |
| * different MIME types may not be completely seamless, in which case {@link |
| * #allowVideoNonSeamlessAdaptiveness} also needs to be {@code true} for mixed MIME type |
| * selections to be made. The default value is {@code false}. |
| */ |
| public final boolean allowVideoMixedMimeTypeAdaptiveness; |
| /** |
| * Whether to allow adaptive video selections where adaptation may not be completely seamless. |
| * The default value is {@code true}. |
| */ |
| public final boolean allowVideoNonSeamlessAdaptiveness; |
| /** |
| * 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; |
| // Audio |
| /** |
| * 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; |
| /** |
| * Whether to exceed the {@link #maxAudioChannelCount} and {@link #maxAudioBitrate} constraints |
| * when no selection can be made otherwise. The default value is {@code true}. |
| */ |
| public final boolean exceedAudioConstraintsIfNecessary; |
| /** |
| * Whether to allow adaptive audio selections containing mixed MIME types. Adaptations between |
| * different MIME types may not be completely seamless. The default value is {@code false}. |
| */ |
| public final boolean allowAudioMixedMimeTypeAdaptiveness; |
| /** |
| * Whether to allow adaptive audio selections containing mixed sample rates. Adaptations between |
| * different sample rates may not be completely seamless. The default value is {@code false}. |
| */ |
| public final boolean allowAudioMixedSampleRateAdaptiveness; |
| /** |
| * Whether to allow adaptive audio selections containing mixed channel counts. Adaptations |
| * between different channel counts may not be completely seamless. The default value is {@code |
| * false}. |
| */ |
| public final boolean allowAudioMixedChannelCountAdaptiveness; |
| |
| // 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; |
| /** |
| * Whether to exceed renderer capabilities when no selection can be made otherwise. |
| * |
| * <p>This parameter applies when all of the tracks available for a renderer exceed the |
| * renderer's reported capabilities. If the parameter is {@code true} then the lowest quality |
| * track will still be selected. Playback may succeed if the renderer has under-reported its |
| * true capabilities. If {@code false} then no track will be selected. The default value is |
| * {@code true}. |
| */ |
| public final boolean exceedRendererCapabilitiesIfNecessary; |
| /** |
| * The audio session id to use when tunneling, or {@link C#AUDIO_SESSION_ID_UNSET} if tunneling |
| * is disabled. The default value is {@link C#AUDIO_SESSION_ID_UNSET} (i.e. tunneling is |
| * disabled). |
| */ |
| public final int tunnelingAudioSessionId; |
| |
| // Overrides |
| private final SparseArray<Map<TrackGroupArray, @NullableType SelectionOverride>> |
| selectionOverrides; |
| private final SparseBooleanArray rendererDisabledFlags; |
| |
| /* package */ Parameters( |
| // Video |
| int maxVideoWidth, |
| int maxVideoHeight, |
| int maxVideoFrameRate, |
| int maxVideoBitrate, |
| boolean exceedVideoConstraintsIfNecessary, |
| boolean allowVideoMixedMimeTypeAdaptiveness, |
| boolean allowVideoNonSeamlessAdaptiveness, |
| int viewportWidth, |
| int viewportHeight, |
| boolean viewportOrientationMayChange, |
| // Audio |
| @Nullable String preferredAudioLanguage, |
| int maxAudioChannelCount, |
| int maxAudioBitrate, |
| boolean exceedAudioConstraintsIfNecessary, |
| boolean allowAudioMixedMimeTypeAdaptiveness, |
| boolean allowAudioMixedSampleRateAdaptiveness, |
| boolean allowAudioMixedChannelCountAdaptiveness, |
| // Text |
| @Nullable String preferredTextLanguage, |
| @C.RoleFlags int preferredTextRoleFlags, |
| boolean selectUndeterminedTextLanguage, |
| @C.SelectionFlags int disabledTextTrackSelectionFlags, |
| // General |
| boolean forceLowestBitrate, |
| boolean forceHighestSupportedBitrate, |
| boolean exceedRendererCapabilitiesIfNecessary, |
| int tunnelingAudioSessionId, |
| // Overrides |
| SparseArray<Map<TrackGroupArray, @NullableType SelectionOverride>> selectionOverrides, |
| SparseBooleanArray rendererDisabledFlags) { |
| super( |
| preferredAudioLanguage, |
| preferredTextLanguage, |
| preferredTextRoleFlags, |
| selectUndeterminedTextLanguage, |
| disabledTextTrackSelectionFlags); |
| // Video |
| this.maxVideoWidth = maxVideoWidth; |
| this.maxVideoHeight = maxVideoHeight; |
| this.maxVideoFrameRate = maxVideoFrameRate; |
| this.maxVideoBitrate = maxVideoBitrate; |
| this.exceedVideoConstraintsIfNecessary = exceedVideoConstraintsIfNecessary; |
| this.allowVideoMixedMimeTypeAdaptiveness = allowVideoMixedMimeTypeAdaptiveness; |
| this.allowVideoNonSeamlessAdaptiveness = allowVideoNonSeamlessAdaptiveness; |
| this.viewportWidth = viewportWidth; |
| this.viewportHeight = viewportHeight; |
| this.viewportOrientationMayChange = viewportOrientationMayChange; |
| // Audio |
| this.maxAudioChannelCount = maxAudioChannelCount; |
| this.maxAudioBitrate = maxAudioBitrate; |
| this.exceedAudioConstraintsIfNecessary = exceedAudioConstraintsIfNecessary; |
| this.allowAudioMixedMimeTypeAdaptiveness = allowAudioMixedMimeTypeAdaptiveness; |
| this.allowAudioMixedSampleRateAdaptiveness = allowAudioMixedSampleRateAdaptiveness; |
| this.allowAudioMixedChannelCountAdaptiveness = allowAudioMixedChannelCountAdaptiveness; |
| // General |
| this.forceLowestBitrate = forceLowestBitrate; |
| this.forceHighestSupportedBitrate = forceHighestSupportedBitrate; |
| this.exceedRendererCapabilitiesIfNecessary = exceedRendererCapabilitiesIfNecessary; |
| this.tunnelingAudioSessionId = tunnelingAudioSessionId; |
| // Overrides |
| this.selectionOverrides = selectionOverrides; |
| this.rendererDisabledFlags = rendererDisabledFlags; |
| } |
| |
| /* package */ |
| Parameters(Parcel in) { |
| super(in); |
| // Video |
| this.maxVideoWidth = in.readInt(); |
| this.maxVideoHeight = in.readInt(); |
| this.maxVideoFrameRate = in.readInt(); |
| this.maxVideoBitrate = in.readInt(); |
| this.exceedVideoConstraintsIfNecessary = Util.readBoolean(in); |
| this.allowVideoMixedMimeTypeAdaptiveness = Util.readBoolean(in); |
| this.allowVideoNonSeamlessAdaptiveness = Util.readBoolean(in); |
| this.viewportWidth = in.readInt(); |
| this.viewportHeight = in.readInt(); |
| this.viewportOrientationMayChange = Util.readBoolean(in); |
| // Audio |
| this.maxAudioChannelCount = in.readInt(); |
| this.maxAudioBitrate = in.readInt(); |
| this.exceedAudioConstraintsIfNecessary = Util.readBoolean(in); |
| this.allowAudioMixedMimeTypeAdaptiveness = Util.readBoolean(in); |
| this.allowAudioMixedSampleRateAdaptiveness = Util.readBoolean(in); |
| this.allowAudioMixedChannelCountAdaptiveness = Util.readBoolean(in); |
| // General |
| this.forceLowestBitrate = Util.readBoolean(in); |
| this.forceHighestSupportedBitrate = Util.readBoolean(in); |
| this.exceedRendererCapabilitiesIfNecessary = Util.readBoolean(in); |
| this.tunnelingAudioSessionId = in.readInt(); |
| // Overrides |
| this.selectionOverrides = readSelectionOverrides(in); |
| this.rendererDisabledFlags = Util.castNonNull(in.readSparseBooleanArray()); |
| } |
| |
| /** |
| * Returns whether the renderer is disabled. |
| * |
| * @param rendererIndex The renderer index. |
| * @return Whether the renderer is disabled. |
| */ |
| public final boolean getRendererDisabled(int rendererIndex) { |
| return rendererDisabledFlags.get(rendererIndex); |
| } |
| |
| /** |
| * Returns whether there is an override for the specified renderer and {@link TrackGroupArray}. |
| * |
| * @param rendererIndex The renderer index. |
| * @param groups The {@link TrackGroupArray}. |
| * @return Whether there is an override. |
| */ |
| public final boolean hasSelectionOverride(int rendererIndex, TrackGroupArray groups) { |
| Map<TrackGroupArray, @NullableType SelectionOverride> overrides = |
| selectionOverrides.get(rendererIndex); |
| return overrides != null && overrides.containsKey(groups); |
| } |
| |
| /** |
| * Returns the override for the specified renderer and {@link TrackGroupArray}. |
| * |
| * @param rendererIndex The renderer index. |
| * @param groups The {@link TrackGroupArray}. |
| * @return The override, or null if no override exists. |
| */ |
| @Nullable |
| public final SelectionOverride getSelectionOverride(int rendererIndex, TrackGroupArray groups) { |
| Map<TrackGroupArray, @NullableType SelectionOverride> overrides = |
| selectionOverrides.get(rendererIndex); |
| return overrides != null ? overrides.get(groups) : null; |
| } |
| |
| /** Creates a new {@link ParametersBuilder}, copying the initial values from this instance. */ |
| @Override |
| public ParametersBuilder buildUpon() { |
| return new ParametersBuilder(this); |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null || getClass() != obj.getClass()) { |
| return false; |
| } |
| Parameters other = (Parameters) obj; |
| return super.equals(obj) |
| // Video |
| && maxVideoWidth == other.maxVideoWidth |
| && maxVideoHeight == other.maxVideoHeight |
| && maxVideoFrameRate == other.maxVideoFrameRate |
| && maxVideoBitrate == other.maxVideoBitrate |
| && exceedVideoConstraintsIfNecessary == other.exceedVideoConstraintsIfNecessary |
| && allowVideoMixedMimeTypeAdaptiveness == other.allowVideoMixedMimeTypeAdaptiveness |
| && allowVideoNonSeamlessAdaptiveness == other.allowVideoNonSeamlessAdaptiveness |
| && viewportOrientationMayChange == other.viewportOrientationMayChange |
| && viewportWidth == other.viewportWidth |
| && viewportHeight == other.viewportHeight |
| // Audio |
| && maxAudioChannelCount == other.maxAudioChannelCount |
| && maxAudioBitrate == other.maxAudioBitrate |
| && exceedAudioConstraintsIfNecessary == other.exceedAudioConstraintsIfNecessary |
| && allowAudioMixedMimeTypeAdaptiveness == other.allowAudioMixedMimeTypeAdaptiveness |
| && allowAudioMixedSampleRateAdaptiveness == other.allowAudioMixedSampleRateAdaptiveness |
| && allowAudioMixedChannelCountAdaptiveness |
| == other.allowAudioMixedChannelCountAdaptiveness |
| // General |
| && forceLowestBitrate == other.forceLowestBitrate |
| && forceHighestSupportedBitrate == other.forceHighestSupportedBitrate |
| && exceedRendererCapabilitiesIfNecessary == other.exceedRendererCapabilitiesIfNecessary |
| && tunnelingAudioSessionId == other.tunnelingAudioSessionId |
| // Overrides |
| && areRendererDisabledFlagsEqual(rendererDisabledFlags, other.rendererDisabledFlags) |
| && areSelectionOverridesEqual(selectionOverrides, other.selectionOverrides); |
| } |
| |
| @Override |
| public int hashCode() { |
| int result = super.hashCode(); |
| // Video |
| result = 31 * result + maxVideoWidth; |
| result = 31 * result + maxVideoHeight; |
| result = 31 * result + maxVideoFrameRate; |
| result = 31 * result + maxVideoBitrate; |
| result = 31 * result + (exceedVideoConstraintsIfNecessary ? 1 : 0); |
| result = 31 * result + (allowVideoMixedMimeTypeAdaptiveness ? 1 : 0); |
| result = 31 * result + (allowVideoNonSeamlessAdaptiveness ? 1 : 0); |
| result = 31 * result + (viewportOrientationMayChange ? 1 : 0); |
| result = 31 * result + viewportWidth; |
| result = 31 * result + viewportHeight; |
| // Audio |
| result = 31 * result + maxAudioChannelCount; |
| result = 31 * result + maxAudioBitrate; |
| result = 31 * result + (exceedAudioConstraintsIfNecessary ? 1 : 0); |
| result = 31 * result + (allowAudioMixedMimeTypeAdaptiveness ? 1 : 0); |
| result = 31 * result + (allowAudioMixedSampleRateAdaptiveness ? 1 : 0); |
| result = 31 * result + (allowAudioMixedChannelCountAdaptiveness ? 1 : 0); |
| // General |
| result = 31 * result + (forceLowestBitrate ? 1 : 0); |
| result = 31 * result + (forceHighestSupportedBitrate ? 1 : 0); |
| result = 31 * result + (exceedRendererCapabilitiesIfNecessary ? 1 : 0); |
| result = 31 * result + tunnelingAudioSessionId; |
| // Overrides (omitted from hashCode). |
| return result; |
| } |
| |
| // Parcelable implementation. |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| super.writeToParcel(dest, flags); |
| // Video |
| dest.writeInt(maxVideoWidth); |
| dest.writeInt(maxVideoHeight); |
| dest.writeInt(maxVideoFrameRate); |
| dest.writeInt(maxVideoBitrate); |
| Util.writeBoolean(dest, exceedVideoConstraintsIfNecessary); |
| Util.writeBoolean(dest, allowVideoMixedMimeTypeAdaptiveness); |
| Util.writeBoolean(dest, allowVideoNonSeamlessAdaptiveness); |
| dest.writeInt(viewportWidth); |
| dest.writeInt(viewportHeight); |
| Util.writeBoolean(dest, viewportOrientationMayChange); |
| // Audio |
| dest.writeInt(maxAudioChannelCount); |
| dest.writeInt(maxAudioBitrate); |
| Util.writeBoolean(dest, exceedAudioConstraintsIfNecessary); |
| Util.writeBoolean(dest, allowAudioMixedMimeTypeAdaptiveness); |
| Util.writeBoolean(dest, allowAudioMixedSampleRateAdaptiveness); |
| Util.writeBoolean(dest, allowAudioMixedChannelCountAdaptiveness); |
| // General |
| Util.writeBoolean(dest, forceLowestBitrate); |
| Util.writeBoolean(dest, forceHighestSupportedBitrate); |
| Util.writeBoolean(dest, exceedRendererCapabilitiesIfNecessary); |
| dest.writeInt(tunnelingAudioSessionId); |
| // Overrides |
| writeSelectionOverridesToParcel(dest, selectionOverrides); |
| dest.writeSparseBooleanArray(rendererDisabledFlags); |
| } |
| |
| public static final Parcelable.Creator<Parameters> CREATOR = |
| new Parcelable.Creator<Parameters>() { |
| |
| @Override |
| public Parameters createFromParcel(Parcel in) { |
| return new Parameters(in); |
| } |
| |
| @Override |
| public Parameters[] newArray(int size) { |
| return new Parameters[size]; |
| } |
| }; |
| |
| // Static utility methods. |
| |
| private static SparseArray<Map<TrackGroupArray, @NullableType SelectionOverride>> |
| readSelectionOverrides(Parcel in) { |
| int renderersWithOverridesCount = in.readInt(); |
| SparseArray<Map<TrackGroupArray, @NullableType SelectionOverride>> selectionOverrides = |
| new SparseArray<>(renderersWithOverridesCount); |
| for (int i = 0; i < renderersWithOverridesCount; i++) { |
| int rendererIndex = in.readInt(); |
| int overrideCount = in.readInt(); |
| Map<TrackGroupArray, @NullableType SelectionOverride> overrides = |
| new HashMap<>(overrideCount); |
| for (int j = 0; j < overrideCount; j++) { |
| TrackGroupArray trackGroups = |
| Assertions.checkNotNull(in.readParcelable(TrackGroupArray.class.getClassLoader())); |
| @Nullable |
| SelectionOverride override = in.readParcelable(SelectionOverride.class.getClassLoader()); |
| overrides.put(trackGroups, override); |
| } |
| selectionOverrides.put(rendererIndex, overrides); |
| } |
| return selectionOverrides; |
| } |
| |
| private static void writeSelectionOverridesToParcel( |
| Parcel dest, |
| SparseArray<Map<TrackGroupArray, @NullableType SelectionOverride>> selectionOverrides) { |
| int renderersWithOverridesCount = selectionOverrides.size(); |
| dest.writeInt(renderersWithOverridesCount); |
| for (int i = 0; i < renderersWithOverridesCount; i++) { |
| int rendererIndex = selectionOverrides.keyAt(i); |
| Map<TrackGroupArray, @NullableType SelectionOverride> overrides = |
| selectionOverrides.valueAt(i); |
| int overrideCount = overrides.size(); |
| dest.writeInt(rendererIndex); |
| dest.writeInt(overrideCount); |
| for (Map.Entry<TrackGroupArray, @NullableType SelectionOverride> override : |
| overrides.entrySet()) { |
| dest.writeParcelable(override.getKey(), /* parcelableFlags= */ 0); |
| dest.writeParcelable(override.getValue(), /* parcelableFlags= */ 0); |
| } |
| } |
| } |
| |
| private static boolean areRendererDisabledFlagsEqual( |
| SparseBooleanArray first, SparseBooleanArray second) { |
| int firstSize = first.size(); |
| if (second.size() != firstSize) { |
| return false; |
| } |
| // Only true values are put into rendererDisabledFlags, so we don't need to compare values. |
| for (int indexInFirst = 0; indexInFirst < firstSize; indexInFirst++) { |
| if (second.indexOfKey(first.keyAt(indexInFirst)) < 0) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static boolean areSelectionOverridesEqual( |
| SparseArray<Map<TrackGroupArray, @NullableType SelectionOverride>> first, |
| SparseArray<Map<TrackGroupArray, @NullableType SelectionOverride>> second) { |
| int firstSize = first.size(); |
| if (second.size() != firstSize) { |
| return false; |
| } |
| for (int indexInFirst = 0; indexInFirst < firstSize; indexInFirst++) { |
| int indexInSecond = second.indexOfKey(first.keyAt(indexInFirst)); |
| if (indexInSecond < 0 |
| || !areSelectionOverridesEqual( |
| first.valueAt(indexInFirst), second.valueAt(indexInSecond))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static boolean areSelectionOverridesEqual( |
| Map<TrackGroupArray, @NullableType SelectionOverride> first, |
| Map<TrackGroupArray, @NullableType SelectionOverride> second) { |
| int firstSize = first.size(); |
| if (second.size() != firstSize) { |
| return false; |
| } |
| for (Map.Entry<TrackGroupArray, @NullableType SelectionOverride> firstEntry : |
| first.entrySet()) { |
| TrackGroupArray key = firstEntry.getKey(); |
| if (!second.containsKey(key) || !Util.areEqual(firstEntry.getValue(), second.get(key))) { |
| return false; |
| } |
| } |
| return true; |
| } |
| } |
| |
| /** A track selection override. */ |
| public static final class SelectionOverride implements Parcelable { |
| |
| public final int groupIndex; |
| public final int[] tracks; |
| public final int length; |
| public final int reason; |
| public final int data; |
| |
| /** |
| * @param groupIndex The overriding track group index. |
| * @param tracks The overriding track indices within the track group. |
| */ |
| public SelectionOverride(int groupIndex, int... tracks) { |
| this(groupIndex, tracks, C.SELECTION_REASON_MANUAL, /* data= */ 0); |
| } |
| |
| /** |
| * @param groupIndex The overriding track group index. |
| * @param tracks The overriding track indices within the track group. |
| * @param reason The reason for the override. One of the {@link C} SELECTION_REASON_ constants. |
| * @param data Optional data associated with this override. |
| */ |
| public SelectionOverride(int groupIndex, int[] tracks, int reason, int data) { |
| this.groupIndex = groupIndex; |
| this.tracks = Arrays.copyOf(tracks, tracks.length); |
| this.length = tracks.length; |
| this.reason = reason; |
| this.data = data; |
| Arrays.sort(this.tracks); |
| } |
| |
| /* package */ SelectionOverride(Parcel in) { |
| groupIndex = in.readInt(); |
| length = in.readByte(); |
| tracks = new int[length]; |
| in.readIntArray(tracks); |
| reason = in.readInt(); |
| data = in.readInt(); |
| } |
| |
| /** Returns whether this override contains the specified track index. */ |
| public boolean containsTrack(int track) { |
| for (int overrideTrack : tracks) { |
| if (overrideTrack == track) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| int hash = 31 * groupIndex + Arrays.hashCode(tracks); |
| hash = 31 * hash + reason; |
| return 31 * hash + data; |
| } |
| |
| @Override |
| public boolean equals(@Nullable Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null || getClass() != obj.getClass()) { |
| return false; |
| } |
| SelectionOverride other = (SelectionOverride) obj; |
| return groupIndex == other.groupIndex |
| && Arrays.equals(tracks, other.tracks) |
| && reason == other.reason |
| && data == other.data; |
| } |
| |
| // Parcelable implementation. |
| |
| @Override |
| public int describeContents() { |
| return 0; |
| } |
| |
| @Override |
| public void writeToParcel(Parcel dest, int flags) { |
| dest.writeInt(groupIndex); |
| dest.writeInt(tracks.length); |
| dest.writeIntArray(tracks); |
| dest.writeInt(reason); |
| dest.writeInt(data); |
| } |
| |
| public static final Parcelable.Creator<SelectionOverride> CREATOR = |
| new Parcelable.Creator<SelectionOverride>() { |
| |
| @Override |
| public SelectionOverride createFromParcel(Parcel in) { |
| return new SelectionOverride(in); |
| } |
| |
| @Override |
| public SelectionOverride[] newArray(int size) { |
| return new SelectionOverride[size]; |
| } |
| }; |
| } |
| |
| /** |
| * If a dimension (i.e. width or height) of a video is greater or equal to this fraction of the |
| * corresponding viewport dimension, then the video is considered as filling the viewport (in that |
| * dimension). |
| */ |
| private static final float FRACTION_TO_CONSIDER_FULLSCREEN = 0.98f; |
| private static final int[] NO_TRACKS = new int[0]; |
| private static final int WITHIN_RENDERER_CAPABILITIES_BONUS = 1000; |
| |
| private final TrackSelection.Factory trackSelectionFactory; |
| private final AtomicReference<Parameters> parametersReference; |
| |
| private boolean allowMultipleAdaptiveSelections; |
| |
| /** @deprecated Use {@link #DefaultTrackSelector(Context)} instead. */ |
| @Deprecated |
| @SuppressWarnings("deprecation") |
| public DefaultTrackSelector() { |
| this(new AdaptiveTrackSelection.Factory()); |
| } |
| |
| /** |
| * @deprecated Use {@link #DefaultTrackSelector(Context)} instead. The bandwidth meter should be |
| * passed directly to the player in {@link |
| * com.google.android.exoplayer2.SimpleExoPlayer.Builder}. |
| */ |
| @Deprecated |
| @SuppressWarnings("deprecation") |
| public DefaultTrackSelector(BandwidthMeter bandwidthMeter) { |
| this(new AdaptiveTrackSelection.Factory(bandwidthMeter)); |
| } |
| |
| /** @deprecated Use {@link #DefaultTrackSelector(Context, TrackSelection.Factory)}. */ |
| @Deprecated |
| public DefaultTrackSelector(TrackSelection.Factory trackSelectionFactory) { |
| this(Parameters.DEFAULT_WITHOUT_CONTEXT, trackSelectionFactory); |
| } |
| |
| /** @param context Any {@link Context}. */ |
| public DefaultTrackSelector(Context context) { |
| this(context, new AdaptiveTrackSelection.Factory()); |
| } |
| |
| /** |
| * @param context Any {@link Context}. |
| * @param trackSelectionFactory A factory for {@link TrackSelection}s. |
| */ |
| public DefaultTrackSelector(Context context, TrackSelection.Factory trackSelectionFactory) { |
| this(Parameters.getDefaults(context), trackSelectionFactory); |
| } |
| |
| /** |
| * @param parameters Initial {@link Parameters}. |
| * @param trackSelectionFactory A factory for {@link TrackSelection}s. |
| */ |
| public DefaultTrackSelector(Parameters parameters, TrackSelection.Factory trackSelectionFactory) { |
| this.trackSelectionFactory = trackSelectionFactory; |
| parametersReference = new AtomicReference<>(parameters); |
| } |
| |
| /** |
| * Atomically sets the provided parameters for track selection. |
| * |
| * @param parameters The parameters for track selection. |
| */ |
| public void setParameters(Parameters parameters) { |
| Assertions.checkNotNull(parameters); |
| if (!parametersReference.getAndSet(parameters).equals(parameters)) { |
| invalidate(); |
| } |
| } |
| |
| /** |
| * Atomically sets the provided parameters for track selection. |
| * |
| * @param parametersBuilder A builder from which to obtain the parameters for track selection. |
| */ |
| public void setParameters(ParametersBuilder parametersBuilder) { |
| setParameters(parametersBuilder.build()); |
| } |
| |
| /** |
| * Gets the current selection parameters. |
| * |
| * @return The current selection parameters. |
| */ |
| public Parameters getParameters() { |
| return parametersReference.get(); |
| } |
| |
| /** Returns a new {@link ParametersBuilder} initialized with the current selection parameters. */ |
| public ParametersBuilder buildUponParameters() { |
| return getParameters().buildUpon(); |
| } |
| |
| /** |
| * Allows the creation of multiple adaptive track selections. |
| * |
| * <p>This method is experimental, and will be renamed or removed in a future release. |
| */ |
| public void experimental_allowMultipleAdaptiveSelections() { |
| this.allowMultipleAdaptiveSelections = true; |
| } |
| |
| // MappingTrackSelector implementation. |
| |
| @Override |
| protected final Pair<@NullableType RendererConfiguration[], @NullableType TrackSelection[]> |
| selectTracks( |
| MappedTrackInfo mappedTrackInfo, |
| @Capabilities int[][][] rendererFormatSupports, |
| @AdaptiveSupport int[] rendererMixedMimeTypeAdaptationSupports) |
| throws ExoPlaybackException { |
| Parameters params = parametersReference.get(); |
| int rendererCount = mappedTrackInfo.getRendererCount(); |
| TrackSelection.@NullableType Definition[] definitions = |
| selectAllTracks( |
| mappedTrackInfo, |
| rendererFormatSupports, |
| rendererMixedMimeTypeAdaptationSupports, |
| params); |
| |
| // Apply track disabling and overriding. |
| for (int i = 0; i < rendererCount; i++) { |
| if (params.getRendererDisabled(i)) { |
| definitions[i] = null; |
| continue; |
| } |
| TrackGroupArray rendererTrackGroups = mappedTrackInfo.getTrackGroups(i); |
| if (params.hasSelectionOverride(i, rendererTrackGroups)) { |
| SelectionOverride override = params.getSelectionOverride(i, rendererTrackGroups); |
| definitions[i] = |
| override == null |
| ? null |
| : new TrackSelection.Definition( |
| rendererTrackGroups.get(override.groupIndex), |
| override.tracks, |
| override.reason, |
| override.data); |
| } |
| } |
| |
| @NullableType |
| TrackSelection[] rendererTrackSelections = |
| trackSelectionFactory.createTrackSelections(definitions, getBandwidthMeter()); |
| |
| // Initialize the renderer configurations to the default configuration for all renderers with |
| // selections, and null otherwise. |
| @NullableType RendererConfiguration[] rendererConfigurations = |
| new RendererConfiguration[rendererCount]; |
| for (int i = 0; i < rendererCount; i++) { |
| boolean forceRendererDisabled = params.getRendererDisabled(i); |
| boolean rendererEnabled = |
| !forceRendererDisabled |
| && (mappedTrackInfo.getRendererType(i) == C.TRACK_TYPE_NONE |
| || rendererTrackSelections[i] != null); |
| rendererConfigurations[i] = rendererEnabled ? RendererConfiguration.DEFAULT : null; |
| } |
| |
| // Configure audio and video renderers to use tunneling if appropriate. |
| maybeConfigureRenderersForTunneling( |
| mappedTrackInfo, |
| rendererFormatSupports, |
| rendererConfigurations, |
| rendererTrackSelections, |
| params.tunnelingAudioSessionId); |
| |
| return Pair.create(rendererConfigurations, rendererTrackSelections); |
| } |
| |
| // Track selection prior to overrides and disabled flags being applied. |
| |
| /** |
| * Called from {@link #selectTracks(MappedTrackInfo, int[][][], int[])} to make a track selection |
| * for each renderer, prior to overrides and disabled flags being applied. |
| * |
| * <p>The implementation should not account for overrides and disabled flags. Track selections |
| * generated by this method will be overridden to account for these properties. |
| * |
| * @param mappedTrackInfo Mapped track information. |
| * @param rendererFormatSupports The {@link Capabilities} for each mapped track, indexed by |
| * renderer, track group and track (in that order). |
| * @param rendererMixedMimeTypeAdaptationSupports The {@link AdaptiveSupport} for mixed MIME type |
| * adaptation for the renderer. |
| * @return The {@link TrackSelection.Definition}s for the renderers. A null entry indicates no |
| * selection was made. |
| * @throws ExoPlaybackException If an error occurs while selecting the tracks. |
| */ |
| protected TrackSelection.@NullableType Definition[] selectAllTracks( |
| MappedTrackInfo mappedTrackInfo, |
| @Capabilities int[][][] rendererFormatSupports, |
| @AdaptiveSupport int[] rendererMixedMimeTypeAdaptationSupports, |
| Parameters params) |
| throws ExoPlaybackException { |
| int rendererCount = mappedTrackInfo.getRendererCount(); |
| TrackSelection.@NullableType Definition[] definitions = |
| new TrackSelection.Definition[rendererCount]; |
| |
| boolean seenVideoRendererWithMappedTracks = false; |
| boolean selectedVideoTracks = false; |
| for (int i = 0; i < rendererCount; i++) { |
| if (C.TRACK_TYPE_VIDEO == mappedTrackInfo.getRendererType(i)) { |
| if (!selectedVideoTracks) { |
| definitions[i] = |
| selectVideoTrack( |
| mappedTrackInfo.getTrackGroups(i), |
| rendererFormatSupports[i], |
| rendererMixedMimeTypeAdaptationSupports[i], |
| params, |
| /* enableAdaptiveTrackSelection= */ true); |
| selectedVideoTracks = definitions[i] != null; |
| } |
| seenVideoRendererWithMappedTracks |= mappedTrackInfo.getTrackGroups(i).length > 0; |
| } |
| } |
| |
| AudioTrackScore selectedAudioTrackScore = null; |
| String selectedAudioLanguage = null; |
| int selectedAudioRendererIndex = C.INDEX_UNSET; |
| for (int i = 0; i < rendererCount; i++) { |
| if (C.TRACK_TYPE_AUDIO == mappedTrackInfo.getRendererType(i)) { |
| boolean enableAdaptiveTrackSelection = |
| allowMultipleAdaptiveSelections || !seenVideoRendererWithMappedTracks; |
| Pair<TrackSelection.Definition, AudioTrackScore> audioSelection = |
| selectAudioTrack( |
| mappedTrackInfo.getTrackGroups(i), |
| rendererFormatSupports[i], |
| rendererMixedMimeTypeAdaptationSupports[i], |
| params, |
| enableAdaptiveTrackSelection); |
| if (audioSelection != null |
| && (selectedAudioTrackScore == null |
| || audioSelection.second.compareTo(selectedAudioTrackScore) > 0)) { |
| if (selectedAudioRendererIndex != C.INDEX_UNSET) { |
| // We've already made a selection for another audio renderer, but it had a lower |
| // score. Clear the selection for that renderer. |
| definitions[selectedAudioRendererIndex] = null; |
| } |
| TrackSelection.Definition definition = audioSelection.first; |
| definitions[i] = definition; |
| // We assume that audio tracks in the same group have matching language. |
| selectedAudioLanguage = definition.group.getFormat(definition.tracks[0]).language; |
| selectedAudioTrackScore = audioSelection.second; |
| selectedAudioRendererIndex = i; |
| } |
| } |
| } |
| |
| TextTrackScore selectedTextTrackScore = null; |
| int selectedTextRendererIndex = C.INDEX_UNSET; |
| for (int i = 0; i < rendererCount; i++) { |
| int trackType = mappedTrackInfo.getRendererType(i); |
| switch (trackType) { |
| case C.TRACK_TYPE_VIDEO: |
| case C.TRACK_TYPE_AUDIO: |
| // Already done. Do nothing. |
| break; |
| case C.TRACK_TYPE_TEXT: |
| Pair<TrackSelection.Definition, TextTrackScore> textSelection = |
| selectTextTrack( |
| mappedTrackInfo.getTrackGroups(i), |
| rendererFormatSupports[i], |
| params, |
| selectedAudioLanguage); |
| if (textSelection != null |
| && (selectedTextTrackScore == null |
| || textSelection.second.compareTo(selectedTextTrackScore) > 0)) { |
| if (selectedTextRendererIndex != C.INDEX_UNSET) { |
| // We've already made a selection for another text renderer, but it had a lower score. |
| // Clear the selection for that renderer. |
| definitions[selectedTextRendererIndex] = null; |
| } |
| definitions[i] = textSelection.first; |
| selectedTextTrackScore = textSelection.second; |
| selectedTextRendererIndex = i; |
| } |
| break; |
| default: |
| definitions[i] = |
| selectOtherTrack( |
| trackType, mappedTrackInfo.getTrackGroups(i), rendererFormatSupports[i], params); |
| break; |
| } |
| } |
| |
| return definitions; |
| } |
| |
| // Video track selection implementation. |
| |
| /** |
| * Called by {@link #selectAllTracks(MappedTrackInfo, int[][][], int[], Parameters)} to create a |
| * {@link TrackSelection} for a video renderer. |
| * |
| * @param groups The {@link TrackGroupArray} mapped to the renderer. |
| * @param formatSupports The {@link Capabilities} for each mapped track, indexed by renderer, |
| * track group and track (in that order). |
| * @param mixedMimeTypeAdaptationSupports The {@link AdaptiveSupport} for mixed MIME type |
| * adaptation for the renderer. |
| * @param params The selector's current constraint parameters. |
| * @param enableAdaptiveTrackSelection Whether adaptive track selection is allowed. |
| * @return The {@link TrackSelection.Definition} for the renderer, or null if no selection was |
| * made. |
| * @throws ExoPlaybackException If an error occurs while selecting the tracks. |
| */ |
| @Nullable |
| protected TrackSelection.Definition selectVideoTrack( |
| TrackGroupArray groups, |
| @Capabilities int[][] formatSupports, |
| @AdaptiveSupport int mixedMimeTypeAdaptationSupports, |
| Parameters params, |
| boolean enableAdaptiveTrackSelection) |
| throws ExoPlaybackException { |
| TrackSelection.Definition definition = null; |
| if (!params.forceHighestSupportedBitrate |
| && !params.forceLowestBitrate |
| && enableAdaptiveTrackSelection) { |
| definition = |
| selectAdaptiveVideoTrack(groups, formatSupports, mixedMimeTypeAdaptationSupports, params); |
| } |
| if (definition == null) { |
| definition = selectFixedVideoTrack(groups, formatSupports, params); |
| } |
| return definition; |
| } |
| |
| @Nullable |
| private static TrackSelection.Definition selectAdaptiveVideoTrack( |
| TrackGroupArray groups, |
| @Capabilities int[][] formatSupport, |
| @AdaptiveSupport int mixedMimeTypeAdaptationSupports, |
| Parameters params) { |
| int requiredAdaptiveSupport = |
| params.allowVideoNonSeamlessAdaptiveness |
| ? (RendererCapabilities.ADAPTIVE_NOT_SEAMLESS | RendererCapabilities.ADAPTIVE_SEAMLESS) |
| : RendererCapabilities.ADAPTIVE_SEAMLESS; |
| boolean allowMixedMimeTypes = |
| params.allowVideoMixedMimeTypeAdaptiveness |
| && (mixedMimeTypeAdaptationSupports & requiredAdaptiveSupport) != 0; |
| for (int i = 0; i < groups.length; i++) { |
| TrackGroup group = groups.get(i); |
| int[] adaptiveTracks = |
| getAdaptiveVideoTracksForGroup( |
| group, |
| formatSupport[i], |
| allowMixedMimeTypes, |
| requiredAdaptiveSupport, |
| params.maxVideoWidth, |
| params.maxVideoHeight, |
| params.maxVideoFrameRate, |
| params.maxVideoBitrate, |
| params.viewportWidth, |
| params.viewportHeight, |
| params.viewportOrientationMayChange); |
| if (adaptiveTracks.length > 0) { |
| return new TrackSelection.Definition(group, adaptiveTracks); |
| } |
| } |
| return null; |
| } |
| |
| private static int[] getAdaptiveVideoTracksForGroup( |
| TrackGroup group, |
| @Capabilities int[] formatSupport, |
| boolean allowMixedMimeTypes, |
| int requiredAdaptiveSupport, |
| int maxVideoWidth, |
| int maxVideoHeight, |
| int maxVideoFrameRate, |
| int maxVideoBitrate, |
| int viewportWidth, |
| int viewportHeight, |
| boolean viewportOrientationMayChange) { |
| if (group.length < 2) { |
| return NO_TRACKS; |
| } |
| |
| List<Integer> selectedTrackIndices = getViewportFilteredTrackIndices(group, viewportWidth, |
| viewportHeight, viewportOrientationMayChange); |
| if (selectedTrackIndices.size() < 2) { |
| return NO_TRACKS; |
| } |
| |
| String selectedMimeType = null; |
| if (!allowMixedMimeTypes) { |
| // Select the mime type for which we have the most adaptive tracks. |
| HashSet<@NullableType String> seenMimeTypes = new HashSet<>(); |
| int selectedMimeTypeTrackCount = 0; |
| for (int i = 0; i < selectedTrackIndices.size(); i++) { |
| int trackIndex = selectedTrackIndices.get(i); |
| String sampleMimeType = group.getFormat(trackIndex).sampleMimeType; |
| if (seenMimeTypes.add(sampleMimeType)) { |
| int countForMimeType = |
| getAdaptiveVideoTrackCountForMimeType( |
| group, |
| formatSupport, |
| requiredAdaptiveSupport, |
| sampleMimeType, |
| maxVideoWidth, |
| maxVideoHeight, |
| maxVideoFrameRate, |
| maxVideoBitrate, |
| selectedTrackIndices); |
| if (countForMimeType > selectedMimeTypeTrackCount) { |
| selectedMimeType = sampleMimeType; |
| selectedMimeTypeTrackCount = countForMimeType; |
| } |
| } |
| } |
| } |
| |
| // Filter by the selected mime type. |
| filterAdaptiveVideoTrackCountForMimeType( |
| group, |
| formatSupport, |
| requiredAdaptiveSupport, |
| selectedMimeType, |
| maxVideoWidth, |
| maxVideoHeight, |
| maxVideoFrameRate, |
| maxVideoBitrate, |
| selectedTrackIndices); |
| |
| return selectedTrackIndices.size() < 2 ? NO_TRACKS : Util.toArray(selectedTrackIndices); |
| } |
| |
| private static int getAdaptiveVideoTrackCountForMimeType( |
| TrackGroup group, |
| @Capabilities int[] formatSupport, |
| int requiredAdaptiveSupport, |
| @Nullable String mimeType, |
| int maxVideoWidth, |
| int maxVideoHeight, |
| int maxVideoFrameRate, |
| int maxVideoBitrate, |
| List<Integer> selectedTrackIndices) { |
| int adaptiveTrackCount = 0; |
| for (int i = 0; i < selectedTrackIndices.size(); i++) { |
| int trackIndex = selectedTrackIndices.get(i); |
| if (isSupportedAdaptiveVideoTrack( |
| group.getFormat(trackIndex), |
| mimeType, |
| formatSupport[trackIndex], |
| requiredAdaptiveSupport, |
| maxVideoWidth, |
| maxVideoHeight, |
| maxVideoFrameRate, |
| maxVideoBitrate)) { |
| adaptiveTrackCount++; |
| } |
| } |
| return adaptiveTrackCount; |
| } |
| |
| private static void filterAdaptiveVideoTrackCountForMimeType( |
| TrackGroup group, |
| @Capabilities int[] formatSupport, |
| int requiredAdaptiveSupport, |
| @Nullable String mimeType, |
| int maxVideoWidth, |
| int maxVideoHeight, |
| int maxVideoFrameRate, |
| int maxVideoBitrate, |
| List<Integer> selectedTrackIndices) { |
| for (int i = selectedTrackIndices.size() - 1; i >= 0; i--) { |
| int trackIndex = selectedTrackIndices.get(i); |
| if (!isSupportedAdaptiveVideoTrack( |
| group.getFormat(trackIndex), |
| mimeType, |
| formatSupport[trackIndex], |
| requiredAdaptiveSupport, |
| maxVideoWidth, |
| maxVideoHeight, |
| maxVideoFrameRate, |
| maxVideoBitrate)) { |
| selectedTrackIndices.remove(i); |
| } |
| } |
| } |
| |
| private static boolean isSupportedAdaptiveVideoTrack( |
| Format format, |
| @Nullable String mimeType, |
| @Capabilities int formatSupport, |
| int requiredAdaptiveSupport, |
| int maxVideoWidth, |
| int maxVideoHeight, |
| int maxVideoFrameRate, |
| int maxVideoBitrate) { |
| if ((format.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0) { |
| // Ignore trick-play tracks for now. |
| return false; |
| } |
| return isSupported(formatSupport, false) |
| && ((formatSupport & requiredAdaptiveSupport) != 0) |
| && (mimeType == null || Util.areEqual(format.sampleMimeType, mimeType)) |
| && (format.width == Format.NO_VALUE || format.width <= maxVideoWidth) |
| && (format.height == Format.NO_VALUE || format.height <= maxVideoHeight) |
| && (format.frameRate == Format.NO_VALUE || format.frameRate <= maxVideoFrameRate) |
| && (format.bitrate == Format.NO_VALUE || format.bitrate <= maxVideoBitrate); |
| } |
| |
| @Nullable |
| private static TrackSelection.Definition selectFixedVideoTrack( |
| TrackGroupArray groups, @Capabilities int[][] formatSupports, Parameters params) { |
| TrackGroup selectedGroup = null; |
| int selectedTrackIndex = 0; |
| int selectedTrackScore = 0; |
| int selectedBitrate = Format.NO_VALUE; |
| int selectedPixelCount = Format.NO_VALUE; |
| for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { |
| TrackGroup trackGroup = groups.get(groupIndex); |
| List<Integer> selectedTrackIndices = getViewportFilteredTrackIndices(trackGroup, |
| params.viewportWidth, params.viewportHeight, params.viewportOrientationMayChange); |
| @Capabilities int[] trackFormatSupport = formatSupports[groupIndex]; |
| for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { |
| Format format = trackGroup.getFormat(trackIndex); |
| if ((format.roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0) { |
| // Ignore trick-play tracks for now. |
| continue; |
| } |
| if (isSupported(trackFormatSupport[trackIndex], |
| params.exceedRendererCapabilitiesIfNecessary)) { |
| boolean isWithinConstraints = |
| selectedTrackIndices.contains(trackIndex) |
| && (format.width == Format.NO_VALUE || format.width <= params.maxVideoWidth) |
| && (format.height == Format.NO_VALUE || format.height <= params.maxVideoHeight) |
| && (format.frameRate == Format.NO_VALUE |
| || format.frameRate <= params.maxVideoFrameRate) |
| && (format.bitrate == Format.NO_VALUE |
| || format.bitrate <= params.maxVideoBitrate); |
| if (!isWithinConstraints && !params.exceedVideoConstraintsIfNecessary) { |
| // Track should not be selected. |
| continue; |
| } |
| int trackScore = isWithinConstraints ? 2 : 1; |
| boolean isWithinCapabilities = isSupported(trackFormatSupport[trackIndex], false); |
| if (isWithinCapabilities) { |
| trackScore += WITHIN_RENDERER_CAPABILITIES_BONUS; |
| } |
| boolean selectTrack = trackScore > selectedTrackScore; |
| if (trackScore == selectedTrackScore) { |
| int bitrateComparison = compareFormatValues(format.bitrate, selectedBitrate); |
| if (params.forceLowestBitrate && bitrateComparison != 0) { |
| // Use bitrate as a tie breaker, preferring the lower bitrate. |
| selectTrack = bitrateComparison < 0; |
| } else { |
| // Use the pixel count as a tie breaker (or bitrate if pixel counts are tied). If |
| // we're within constraints prefer a higher pixel count (or bitrate), else prefer a |
| // lower count (or bitrate). If still tied then prefer the first track (i.e. the one |
| // that's already selected). |
| int formatPixelCount = format.getPixelCount(); |
| int comparisonResult = formatPixelCount != selectedPixelCount |
| ? compareFormatValues(formatPixelCount, selectedPixelCount) |
| : compareFormatValues(format.bitrate, selectedBitrate); |
| selectTrack = isWithinCapabilities && isWithinConstraints |
| ? comparisonResult > 0 : comparisonResult < 0; |
| } |
| } |
| if (selectTrack) { |
| selectedGroup = trackGroup; |
| selectedTrackIndex = trackIndex; |
| selectedTrackScore = trackScore; |
| selectedBitrate = format.bitrate; |
| selectedPixelCount = format.getPixelCount(); |
| } |
| } |
| } |
| } |
| return selectedGroup == null |
| ? null |
| : new TrackSelection.Definition(selectedGroup, selectedTrackIndex); |
| } |
| |
| // Audio track selection implementation. |
| |
| /** |
| * Called by {@link #selectAllTracks(MappedTrackInfo, int[][][], int[], Parameters)} to create a |
| * {@link TrackSelection} for an audio renderer. |
| * |
| * @param groups The {@link TrackGroupArray} mapped to the renderer. |
| * @param formatSupports The {@link Capabilities} for each mapped track, indexed by renderer, |
| * track group and track (in that order). |
| * @param mixedMimeTypeAdaptationSupports The {@link AdaptiveSupport} for mixed MIME type |
| * adaptation for the renderer. |
| * @param params The selector's current constraint parameters. |
| * @param enableAdaptiveTrackSelection Whether adaptive track selection is allowed. |
| * @return The {@link TrackSelection.Definition} and corresponding {@link AudioTrackScore}, or |
| * null if no selection was made. |
| * @throws ExoPlaybackException If an error occurs while selecting the tracks. |
| */ |
| @SuppressWarnings("unused") |
| @Nullable |
| protected Pair<TrackSelection.Definition, AudioTrackScore> selectAudioTrack( |
| TrackGroupArray groups, |
| @Capabilities int[][] formatSupports, |
| @AdaptiveSupport int mixedMimeTypeAdaptationSupports, |
| Parameters params, |
| boolean enableAdaptiveTrackSelection) |
| throws ExoPlaybackException { |
| int selectedTrackIndex = C.INDEX_UNSET; |
| int selectedGroupIndex = C.INDEX_UNSET; |
| AudioTrackScore selectedTrackScore = null; |
| for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { |
| TrackGroup trackGroup = groups.get(groupIndex); |
| @Capabilities int[] trackFormatSupport = formatSupports[groupIndex]; |
| for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { |
| if (isSupported(trackFormatSupport[trackIndex], |
| params.exceedRendererCapabilitiesIfNecessary)) { |
| Format format = trackGroup.getFormat(trackIndex); |
| AudioTrackScore trackScore = |
| new AudioTrackScore(format, params, trackFormatSupport[trackIndex]); |
| if (!trackScore.isWithinConstraints && !params.exceedAudioConstraintsIfNecessary) { |
| // Track should not be selected. |
| continue; |
| } |
| if (selectedTrackScore == null || trackScore.compareTo(selectedTrackScore) > 0) { |
| selectedGroupIndex = groupIndex; |
| selectedTrackIndex = trackIndex; |
| selectedTrackScore = trackScore; |
| } |
| } |
| } |
| } |
| |
| if (selectedGroupIndex == C.INDEX_UNSET) { |
| return null; |
| } |
| |
| TrackGroup selectedGroup = groups.get(selectedGroupIndex); |
| |
| TrackSelection.Definition definition = null; |
| if (!params.forceHighestSupportedBitrate |
| && !params.forceLowestBitrate |
| && enableAdaptiveTrackSelection) { |
| // If the group of the track with the highest score allows it, try to enable adaptation. |
| int[] adaptiveTracks = |
| getAdaptiveAudioTracks( |
| selectedGroup, |
| formatSupports[selectedGroupIndex], |
| selectedTrackIndex, |
| params.maxAudioBitrate, |
| params.allowAudioMixedMimeTypeAdaptiveness, |
| params.allowAudioMixedSampleRateAdaptiveness, |
| params.allowAudioMixedChannelCountAdaptiveness); |
| if (adaptiveTracks.length > 1) { |
| definition = new TrackSelection.Definition(selectedGroup, adaptiveTracks); |
| } |
| } |
| if (definition == null) { |
| // We didn't make an adaptive selection, so make a fixed one instead. |
| definition = new TrackSelection.Definition(selectedGroup, selectedTrackIndex); |
| } |
| |
| return Pair.create(definition, Assertions.checkNotNull(selectedTrackScore)); |
| } |
| |
| private static int[] getAdaptiveAudioTracks( |
| TrackGroup group, |
| @Capabilities int[] formatSupport, |
| int primaryTrackIndex, |
| int maxAudioBitrate, |
| boolean allowMixedMimeTypeAdaptiveness, |
| boolean allowMixedSampleRateAdaptiveness, |
| boolean allowAudioMixedChannelCountAdaptiveness) { |
| Format primaryFormat = group.getFormat(primaryTrackIndex); |
| int[] adaptiveIndices = new int[group.length]; |
| int count = 0; |
| for (int i = 0; i < group.length; i++) { |
| if (i == primaryTrackIndex |
| || isSupportedAdaptiveAudioTrack( |
| group.getFormat(i), |
| formatSupport[i], |
| primaryFormat, |
| maxAudioBitrate, |
| allowMixedMimeTypeAdaptiveness, |
| allowMixedSampleRateAdaptiveness, |
| allowAudioMixedChannelCountAdaptiveness)) { |
| adaptiveIndices[count++] = i; |
| } |
| } |
| return Arrays.copyOf(adaptiveIndices, count); |
| } |
| |
| private static boolean isSupportedAdaptiveAudioTrack( |
| Format format, |
| @Capabilities int formatSupport, |
| Format primaryFormat, |
| int maxAudioBitrate, |
| boolean allowMixedMimeTypeAdaptiveness, |
| boolean allowMixedSampleRateAdaptiveness, |
| boolean allowAudioMixedChannelCountAdaptiveness) { |
| return isSupported(formatSupport, /* allowExceedsCapabilities= */ false) |
| && (format.bitrate == Format.NO_VALUE || format.bitrate <= maxAudioBitrate) |
| && (allowAudioMixedChannelCountAdaptiveness |
| || (format.channelCount != Format.NO_VALUE |
| && format.channelCount == primaryFormat.channelCount)) |
| && (allowMixedMimeTypeAdaptiveness |
| || (format.sampleMimeType != null |
| && TextUtils.equals(format.sampleMimeType, primaryFormat.sampleMimeType))) |
| && (allowMixedSampleRateAdaptiveness |
| || (format.sampleRate != Format.NO_VALUE |
| && format.sampleRate == primaryFormat.sampleRate)); |
| } |
| |
| // Text track selection implementation. |
| |
| /** |
| * Called by {@link #selectAllTracks(MappedTrackInfo, int[][][], int[], Parameters)} to create a |
| * {@link TrackSelection} for a text renderer. |
| * |
| * @param groups The {@link TrackGroupArray} mapped to the renderer. |
| * @param formatSupport The {@link Capabilities} for each mapped track, indexed by renderer, track |
| * group and track (in that order). |
| * @param params The selector's current constraint parameters. |
| * @param selectedAudioLanguage The language of the selected audio track. May be null if the |
| * selected text track declares no language or no text track was selected. |
| * @return The {@link TrackSelection.Definition} and corresponding {@link TextTrackScore}, or null |
| * if no selection was made. |
| * @throws ExoPlaybackException If an error occurs while selecting the tracks. |
| */ |
| @Nullable |
| protected Pair<TrackSelection.Definition, TextTrackScore> selectTextTrack( |
| TrackGroupArray groups, |
| @Capabilities int[][] formatSupport, |
| Parameters params, |
| @Nullable String selectedAudioLanguage) |
| throws ExoPlaybackException { |
| TrackGroup selectedGroup = null; |
| int selectedTrackIndex = C.INDEX_UNSET; |
| TextTrackScore selectedTrackScore = null; |
| for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { |
| TrackGroup trackGroup = groups.get(groupIndex); |
| @Capabilities int[] trackFormatSupport = formatSupport[groupIndex]; |
| for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { |
| if (isSupported(trackFormatSupport[trackIndex], |
| params.exceedRendererCapabilitiesIfNecessary)) { |
| Format format = trackGroup.getFormat(trackIndex); |
| TextTrackScore trackScore = |
| new TextTrackScore( |
| format, params, trackFormatSupport[trackIndex], selectedAudioLanguage); |
| if (trackScore.isWithinConstraints |
| && (selectedTrackScore == null || trackScore.compareTo(selectedTrackScore) > 0)) { |
| selectedGroup = trackGroup; |
| selectedTrackIndex = trackIndex; |
| selectedTrackScore = trackScore; |
| } |
| } |
| } |
| } |
| return selectedGroup == null |
| ? null |
| : Pair.create( |
| new TrackSelection.Definition(selectedGroup, selectedTrackIndex), |
| Assertions.checkNotNull(selectedTrackScore)); |
| } |
| |
| // General track selection methods. |
| |
| /** |
| * Called by {@link #selectAllTracks(MappedTrackInfo, int[][][], int[], Parameters)} to create a |
| * {@link TrackSelection} for a renderer whose type is neither video, audio or text. |
| * |
| * @param trackType The type of the renderer. |
| * @param groups The {@link TrackGroupArray} mapped to the renderer. |
| * @param formatSupport The {@link Capabilities} for each mapped track, indexed by renderer, track |
| * group and track (in that order). |
| * @param params The selector's current constraint parameters. |
| * @return The {@link TrackSelection} for the renderer, or null if no selection was made. |
| * @throws ExoPlaybackException If an error occurs while selecting the tracks. |
| */ |
| @Nullable |
| protected TrackSelection.Definition selectOtherTrack( |
| int trackType, TrackGroupArray groups, @Capabilities int[][] formatSupport, Parameters params) |
| throws ExoPlaybackException { |
| TrackGroup selectedGroup = null; |
| int selectedTrackIndex = 0; |
| int selectedTrackScore = 0; |
| for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) { |
| TrackGroup trackGroup = groups.get(groupIndex); |
| @Capabilities int[] trackFormatSupport = formatSupport[groupIndex]; |
| for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) { |
| if (isSupported(trackFormatSupport[trackIndex], |
| params.exceedRendererCapabilitiesIfNecessary)) { |
| Format format = trackGroup.getFormat(trackIndex); |
| boolean isDefault = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; |
| int trackScore = isDefault ? 2 : 1; |
| if (isSupported(trackFormatSupport[trackIndex], false)) { |
| trackScore += WITHIN_RENDERER_CAPABILITIES_BONUS; |
| } |
| if (trackScore > selectedTrackScore) { |
| selectedGroup = trackGroup; |
| selectedTrackIndex = trackIndex; |
| selectedTrackScore = trackScore; |
| } |
| } |
| } |
| } |
| return selectedGroup == null |
| ? null |
| : new TrackSelection.Definition(selectedGroup, selectedTrackIndex); |
| } |
| |
| // Utility methods. |
| |
| /** |
| * Determines whether tunneling should be enabled, replacing {@link RendererConfiguration}s in |
| * {@code rendererConfigurations} with configurations that enable tunneling on the appropriate |
| * renderers if so. |
| * |
| * @param mappedTrackInfo Mapped track information. |
| * @param renderererFormatSupports The {@link Capabilities} for each mapped track, indexed by |
| * renderer, track group and track (in that order). |
| * @param rendererConfigurations The renderer configurations. Configurations may be replaced with |
| * ones that enable tunneling as a result of this call. |
| * @param trackSelections The renderer track selections. |
| * @param tunnelingAudioSessionId The audio session id to use when tunneling, or {@link |
| * C#AUDIO_SESSION_ID_UNSET} if tunneling should not be enabled. |
| */ |
| private static void maybeConfigureRenderersForTunneling( |
| MappedTrackInfo mappedTrackInfo, |
| @Capabilities int[][][] renderererFormatSupports, |
| @NullableType RendererConfiguration[] rendererConfigurations, |
| @NullableType TrackSelection[] trackSelections, |
| int tunnelingAudioSessionId) { |
| if (tunnelingAudioSessionId == C.AUDIO_SESSION_ID_UNSET) { |
| return; |
| } |
| // Check whether we can enable tunneling. To enable tunneling we require exactly one audio and |
| // one video renderer to support tunneling and have a selection. |
| int tunnelingAudioRendererIndex = -1; |
| int tunnelingVideoRendererIndex = -1; |
| boolean enableTunneling = true; |
| for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) { |
| int rendererType = mappedTrackInfo.getRendererType(i); |
| TrackSelection trackSelection = trackSelections[i]; |
| if ((rendererType == C.TRACK_TYPE_AUDIO || rendererType == C.TRACK_TYPE_VIDEO) |
| && trackSelection != null) { |
| if (rendererSupportsTunneling( |
| renderererFormatSupports[i], mappedTrackInfo.getTrackGroups(i), trackSelection)) { |
| if (rendererType == C.TRACK_TYPE_AUDIO) { |
| if (tunnelingAudioRendererIndex != -1) { |
| enableTunneling = false; |
| break; |
| } else { |
| tunnelingAudioRendererIndex = i; |
| } |
| } else { |
| if (tunnelingVideoRendererIndex != -1) { |
| enableTunneling = false; |
| break; |
| } else { |
| tunnelingVideoRendererIndex = i; |
| } |
| } |
| } |
| } |
| } |
| enableTunneling &= tunnelingAudioRendererIndex != -1 && tunnelingVideoRendererIndex != -1; |
| if (enableTunneling) { |
| RendererConfiguration tunnelingRendererConfiguration = |
| new RendererConfiguration(tunnelingAudioSessionId); |
| rendererConfigurations[tunnelingAudioRendererIndex] = tunnelingRendererConfiguration; |
| rendererConfigurations[tunnelingVideoRendererIndex] = tunnelingRendererConfiguration; |
| } |
| } |
| |
| /** |
| * Returns whether a renderer supports tunneling for a {@link TrackSelection}. |
| * |
| * @param formatSupports The {@link Capabilities} for each track, indexed by group index and track |
| * index (in that order). |
| * @param trackGroups The {@link TrackGroupArray}s for the renderer. |
| * @param selection The track selection. |
| * @return Whether the renderer supports tunneling for the {@link TrackSelection}. |
| */ |
| private static boolean rendererSupportsTunneling( |
| @Capabilities int[][] formatSupports, TrackGroupArray trackGroups, TrackSelection selection) { |
| if (selection == null) { |
| return false; |
| } |
| int trackGroupIndex = trackGroups.indexOf(selection.getTrackGroup()); |
| for (int i = 0; i < selection.length(); i++) { |
| @Capabilities |
| int trackFormatSupport = formatSupports[trackGroupIndex][selection.getIndexInTrackGroup(i)]; |
| if (RendererCapabilities.getTunnelingSupport(trackFormatSupport) |
| != RendererCapabilities.TUNNELING_SUPPORTED) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Compares two format values for order. A known value is considered greater than {@link |
| * Format#NO_VALUE}. |
| * |
| * @param first The first value. |
| * @param second The second value. |
| * @return A negative integer if the first value is less than the second. Zero if they are equal. |
| * A positive integer if the first value is greater than the second. |
| */ |
| private static int compareFormatValues(int first, int second) { |
| return first == Format.NO_VALUE |
| ? (second == Format.NO_VALUE ? 0 : -1) |
| : (second == Format.NO_VALUE ? 1 : (first - second)); |
| } |
| |
| /** |
| * Returns true if the {@link FormatSupport} in the given {@link Capabilities} is {@link |
| * RendererCapabilities#FORMAT_HANDLED} or if {@code allowExceedsCapabilities} is set and the |
| * format support is {@link RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES}. |
| * |
| * @param formatSupport {@link Capabilities}. |
| * @param allowExceedsCapabilities Whether to return true if {@link FormatSupport} is {@link |
| * RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES}. |
| * @return True if {@link FormatSupport} is {@link RendererCapabilities#FORMAT_HANDLED}, or if |
| * {@code allowExceedsCapabilities} is set and the format support is {@link |
| * RendererCapabilities#FORMAT_EXCEEDS_CAPABILITIES}. |
| */ |
| protected static boolean isSupported( |
| @Capabilities int formatSupport, boolean allowExceedsCapabilities) { |
| @FormatSupport int maskedSupport = RendererCapabilities.getFormatSupport(formatSupport); |
| return maskedSupport == RendererCapabilities.FORMAT_HANDLED || (allowExceedsCapabilities |
| && maskedSupport == RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES); |
| } |
| |
| /** |
| * Normalizes the input string to null if it does not define a language, or returns it otherwise. |
| * |
| * @param language The string. |
| * @return The string, optionally normalized to null if it does not define a language. |
| */ |
| @Nullable |
| protected static String normalizeUndeterminedLanguageToNull(@Nullable String language) { |
| return TextUtils.isEmpty(language) || TextUtils.equals(language, C.LANGUAGE_UNDETERMINED) |
| ? null |
| : language; |
| } |
| |
| /** |
| * Returns a score for how well a language specified in a {@link Format} matches a given language. |
| * |
| * @param format The {@link Format}. |
| * @param language The language, or null. |
| * @param allowUndeterminedFormatLanguage Whether matches with an empty or undetermined format |
| * language tag are allowed. |
| * @return A score of 4 if the languages match fully, a score of 3 if the languages match partly, |
| * a score of 2 if the languages don't match but belong to the same main language, a score of |
| * 1 if the format language is undetermined and such a match is allowed, and a score of 0 if |
| * the languages don't match at all. |
| */ |
| protected static int getFormatLanguageScore( |
| Format format, @Nullable String language, boolean allowUndeterminedFormatLanguage) { |
| if (!TextUtils.isEmpty(language) && language.equals(format.language)) { |
| // Full literal match of non-empty languages, including matches of an explicit "und" query. |
| return 4; |
| } |
| language = normalizeUndeterminedLanguageToNull(language); |
| String formatLanguage = normalizeUndeterminedLanguageToNull(format.language); |
| if (formatLanguage == null || language == null) { |
| // At least one of the languages is undetermined. |
| return allowUndeterminedFormatLanguage && formatLanguage == null ? 1 : 0; |
| } |
| if (formatLanguage.startsWith(language) || language.startsWith(formatLanguage)) { |
| // Partial match where one language is a subset of the other (e.g. "zh-hans" and "zh-hans-hk") |
| return 3; |
| } |
| String formatMainLanguage = Util.splitAtFirst(formatLanguage, "-")[0]; |
| String queryMainLanguage = Util.splitAtFirst(language, "-")[0]; |
| if (formatMainLanguage.equals(queryMainLanguage)) { |
| // Partial match where only the main language tag is the same (e.g. "fr-fr" and "fr-ca") |
| return 2; |
| } |
| return 0; |
| } |
| |
| private static List<Integer> getViewportFilteredTrackIndices(TrackGroup group, int viewportWidth, |
| int viewportHeight, boolean orientationMayChange) { |
| // Initially include all indices. |
| ArrayList<Integer> selectedTrackIndices = new ArrayList<>(group.length); |
| for (int i = 0; i < group.length; i++) { |
| selectedTrackIndices.add(i); |
| } |
| |
| if (viewportWidth == Integer.MAX_VALUE || viewportHeight == Integer.MAX_VALUE) { |
| // Viewport dimensions not set. Return the full set of indices. |
| return selectedTrackIndices; |
| } |
| |
| int maxVideoPixelsToRetain = Integer.MAX_VALUE; |
| for (int i = 0; i < group.length; i++) { |
| Format format = group.getFormat(i); |
| // Keep track of the number of pixels of the selected format whose resolution is the |
| // smallest to exceed the maximum size at which it can be displayed within the viewport. |
| // We'll discard formats of higher resolution. |
| if (format.width > 0 && format.height > 0) { |
| Point maxVideoSizeInViewport = getMaxVideoSizeInViewport(orientationMayChange, |
| viewportWidth, viewportHeight, format.width, format.height); |
| int videoPixels = format.width * format.height; |
| if (format.width >= (int) (maxVideoSizeInViewport.x * FRACTION_TO_CONSIDER_FULLSCREEN) |
| && format.height >= (int) (maxVideoSizeInViewport.y * FRACTION_TO_CONSIDER_FULLSCREEN) |
| && videoPixels < maxVideoPixelsToRetain) { |
| maxVideoPixelsToRetain = videoPixels; |
| } |
| } |
| } |
| |
| // Filter out formats that exceed maxVideoPixelsToRetain. These formats have an unnecessarily |
| // high resolution given the size at which the video will be displayed within the viewport. Also |
| // filter out formats with unknown dimensions, since we have some whose dimensions are known. |
| if (maxVideoPixelsToRetain != Integer.MAX_VALUE) { |
| for (int i = selectedTrackIndices.size() - 1; i >= 0; i--) { |
| Format format = group.getFormat(selectedTrackIndices.get(i)); |
| int pixelCount = format.getPixelCount(); |
| if (pixelCount == Format.NO_VALUE || pixelCount > maxVideoPixelsToRetain) { |
| selectedTrackIndices.remove(i); |
| } |
| } |
| } |
| |
| return selectedTrackIndices; |
| } |
| |
| /** |
| * Given viewport dimensions and video dimensions, computes the maximum size of the video as it |
| * will be rendered to fit inside of the viewport. |
| */ |
| private static Point getMaxVideoSizeInViewport(boolean orientationMayChange, int viewportWidth, |
| int viewportHeight, int videoWidth, int videoHeight) { |
| if (orientationMayChange && (videoWidth > videoHeight) != (viewportWidth > viewportHeight)) { |
| // Rotation is allowed, and the video will be larger in the rotated viewport. |
| int tempViewportWidth = viewportWidth; |
| viewportWidth = viewportHeight; |
| viewportHeight = tempViewportWidth; |
| } |
| |
| if (videoWidth * viewportHeight >= videoHeight * viewportWidth) { |
| // Horizontal letter-boxing along top and bottom. |
| return new Point(viewportWidth, Util.ceilDivide(viewportWidth * videoHeight, videoWidth)); |
| } else { |
| // Vertical letter-boxing along edges. |
| return new Point(Util.ceilDivide(viewportHeight * videoWidth, videoHeight), viewportHeight); |
| } |
| } |
| |
| /** |
| * Compares two integers in a safe way avoiding potential overflow. |
| * |
| * @param first The first value. |
| * @param second The second value. |
| * @return A negative integer if the first value is less than the second. Zero if they are equal. |
| * A positive integer if the first value is greater than the second. |
| */ |
| private static int compareInts(int first, int second) { |
| return first > second ? 1 : (second > first ? -1 : 0); |
| } |
| |
| /** Represents how well an audio track matches the selection {@link Parameters}. */ |
| protected static final class AudioTrackScore implements Comparable<AudioTrackScore> { |
| |
| /** |
| * Whether the provided format is within the parameter constraints. If {@code false}, the format |
| * should not be selected. |
| */ |
| public final boolean isWithinConstraints; |
| |
| @Nullable private final String language; |
| private final Parameters parameters; |
| private final boolean isWithinRendererCapabilities; |
| private final int preferredLanguageScore; |
| private final int localeLanguageMatchIndex; |
| private final int localeLanguageScore; |
| private final boolean isDefaultSelectionFlag; |
| private final int channelCount; |
| private final int sampleRate; |
| private final int bitrate; |
| |
| public AudioTrackScore(Format format, Parameters parameters, @Capabilities int formatSupport) { |
| this.parameters = parameters; |
| this.language = normalizeUndeterminedLanguageToNull(format.language); |
| isWithinRendererCapabilities = isSupported(formatSupport, false); |
| preferredLanguageScore = |
| getFormatLanguageScore( |
| format, |
| parameters.preferredAudioLanguage, |
| /* allowUndeterminedFormatLanguage= */ false); |
| isDefaultSelectionFlag = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; |
| channelCount = format.channelCount; |
| sampleRate = format.sampleRate; |
| bitrate = format.bitrate; |
| isWithinConstraints = |
| (format.bitrate == Format.NO_VALUE || format.bitrate <= parameters.maxAudioBitrate) |
| && (format.channelCount == Format.NO_VALUE |
| || format.channelCount <= parameters.maxAudioChannelCount); |
| String[] localeLanguages = Util.getSystemLanguageCodes(); |
| int bestMatchIndex = Integer.MAX_VALUE; |
| int bestMatchScore = 0; |
| for (int i = 0; i < localeLanguages.length; i++) { |
| int score = |
| getFormatLanguageScore( |
| format, localeLanguages[i], /* allowUndeterminedFormatLanguage= */ false); |
| if (score > 0) { |
| bestMatchIndex = i; |
| bestMatchScore = score; |
| break; |
| } |
| } |
| localeLanguageMatchIndex = bestMatchIndex; |
| localeLanguageScore = bestMatchScore; |
| } |
| |
| /** |
| * Compares this score with another. |
| * |
| * @param other The other score to compare to. |
| * @return A positive integer if this score is better than the other. Zero if they are equal. A |
| * negative integer if this score is worse than the other. |
| */ |
| @Override |
| public int compareTo(AudioTrackScore other) { |
| if (this.isWithinRendererCapabilities != other.isWithinRendererCapabilities) { |
| return this.isWithinRendererCapabilities ? 1 : -1; |
| } |
| if (this.preferredLanguageScore != other.preferredLanguageScore) { |
| return compareInts(this.preferredLanguageScore, other.preferredLanguageScore); |
| } |
| if (this.isWithinConstraints != other.isWithinConstraints) { |
| return this.isWithinConstraints ? 1 : -1; |
| } |
| if (parameters.forceLowestBitrate) { |
| int bitrateComparison = compareFormatValues(bitrate, other.bitrate); |
| if (bitrateComparison != 0) { |
| return bitrateComparison > 0 ? -1 : 1; |
| } |
| } |
| if (this.isDefaultSelectionFlag != other.isDefaultSelectionFlag) { |
| return this.isDefaultSelectionFlag ? 1 : -1; |
| } |
| if (this.localeLanguageMatchIndex != other.localeLanguageMatchIndex) { |
| return -compareInts(this.localeLanguageMatchIndex, other.localeLanguageMatchIndex); |
| } |
| if (this.localeLanguageScore != other.localeLanguageScore) { |
| return compareInts(this.localeLanguageScore, other.localeLanguageScore); |
| } |
| // If the formats are within constraints and renderer capabilities then prefer higher values |
| // of channel count, sample rate and bit rate in that order. Otherwise, prefer lower values. |
| int resultSign = isWithinConstraints && isWithinRendererCapabilities ? 1 : -1; |
| if (this.channelCount != other.channelCount) { |
| return resultSign * compareInts(this.channelCount, other.channelCount); |
| } |
| if (this.sampleRate != other.sampleRate) { |
| return resultSign * compareInts(this.sampleRate, other.sampleRate); |
| } |
| if (Util.areEqual(this.language, other.language)) { |
| // Only compare bit rates of tracks with the same or unknown language. |
| return resultSign * compareInts(this.bitrate, other.bitrate); |
| } |
| return 0; |
| } |
| } |
| |
| /** Represents how well a text track matches the selection {@link Parameters}. */ |
| protected static final class TextTrackScore implements Comparable<TextTrackScore> { |
| |
| /** |
| * Whether the provided format is within the parameter constraints. If {@code false}, the format |
| * should not be selected. |
| */ |
| public final boolean isWithinConstraints; |
| |
| private final boolean isWithinRendererCapabilities; |
| private final boolean isDefault; |
| private final boolean hasPreferredIsForcedFlag; |
| private final int preferredLanguageScore; |
| private final int preferredRoleFlagsScore; |
| private final int selectedAudioLanguageScore; |
| private final boolean hasCaptionRoleFlags; |
| |
| public TextTrackScore( |
| Format format, |
| Parameters parameters, |
| @Capabilities int trackFormatSupport, |
| @Nullable String selectedAudioLanguage) { |
| isWithinRendererCapabilities = |
| isSupported(trackFormatSupport, /* allowExceedsCapabilities= */ false); |
| int maskedSelectionFlags = |
| format.selectionFlags & ~parameters.disabledTextTrackSelectionFlags; |
| isDefault = (maskedSelectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; |
| boolean isForced = (maskedSelectionFlags & C.SELECTION_FLAG_FORCED) != 0; |
| preferredLanguageScore = |
| getFormatLanguageScore( |
| format, parameters.preferredTextLanguage, parameters.selectUndeterminedTextLanguage); |
| preferredRoleFlagsScore = |
| Integer.bitCount(format.roleFlags & parameters.preferredTextRoleFlags); |
| hasCaptionRoleFlags = |
| (format.roleFlags & (C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND)) != 0; |
| // Prefer non-forced to forced if a preferred text language has been matched. Where both are |
| // provided the non-forced track will usually contain the forced subtitles as a subset. |
| // Otherwise, prefer a forced track. |
| hasPreferredIsForcedFlag = |
| (preferredLanguageScore > 0 && !isForced) || (preferredLanguageScore == 0 && isForced); |
| boolean selectedAudioLanguageUndetermined = |
| normalizeUndeterminedLanguageToNull(selectedAudioLanguage) == null; |
| selectedAudioLanguageScore = |
| getFormatLanguageScore(format, selectedAudioLanguage, selectedAudioLanguageUndetermined); |
| isWithinConstraints = |
| preferredLanguageScore > 0 |
| || (parameters.preferredTextLanguage == null && preferredRoleFlagsScore > 0) |
| || isDefault |
| || (isForced && selectedAudioLanguageScore > 0); |
| } |
| |
| /** |
| * Compares this score with another. |
| * |
| * @param other The other score to compare to. |
| * @return A positive integer if this score is better than the other. Zero if they are equal. A |
| * negative integer if this score is worse than the other. |
| */ |
| @Override |
| public int compareTo(TextTrackScore other) { |
| if (this.isWithinRendererCapabilities != other.isWithinRendererCapabilities) { |
| return this.isWithinRendererCapabilities ? 1 : -1; |
| } |
| if (this.preferredLanguageScore != other.preferredLanguageScore) { |
| return compareInts(this.preferredLanguageScore, other.preferredLanguageScore); |
| } |
| if (this.preferredRoleFlagsScore != other.preferredRoleFlagsScore) { |
| return compareInts(this.preferredRoleFlagsScore, other.preferredRoleFlagsScore); |
| } |
| if (this.isDefault != other.isDefault) { |
| return this.isDefault ? 1 : -1; |
| } |
| if (this.hasPreferredIsForcedFlag != other.hasPreferredIsForcedFlag) { |
| return this.hasPreferredIsForcedFlag ? 1 : -1; |
| } |
| if (this.selectedAudioLanguageScore != other.selectedAudioLanguageScore) { |
| return compareInts(this.selectedAudioLanguageScore, other.selectedAudioLanguageScore); |
| } |
| if (preferredRoleFlagsScore == 0 && this.hasCaptionRoleFlags != other.hasCaptionRoleFlags) { |
| return this.hasCaptionRoleFlags ? -1 : 1; |
| } |
| return 0; |
| } |
| } |
| } |