diff --git a/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java b/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java index e033b91eef..5fcc0cd90b 100644 --- a/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java +++ b/demos/main/src/main/java/com/google/android/exoplayer2/demo/TrackSelectionHelper.java @@ -29,10 +29,10 @@ import android.widget.CheckedTextView; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; import com.google.android.exoplayer2.trackselection.FixedTrackSelection; -import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo; -import com.google.android.exoplayer2.trackselection.MappingTrackSelector.SelectionOverride; import com.google.android.exoplayer2.trackselection.RandomTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; import java.util.Arrays; @@ -46,7 +46,7 @@ import java.util.Arrays; private static final TrackSelection.Factory FIXED_FACTORY = new FixedTrackSelection.Factory(); private static final TrackSelection.Factory RANDOM_FACTORY = new RandomTrackSelection.Factory(); - private final MappingTrackSelector selector; + private final DefaultTrackSelector selector; private final TrackSelection.Factory adaptiveTrackSelectionFactory; private MappedTrackInfo trackInfo; @@ -63,11 +63,11 @@ import java.util.Arrays; /** * @param selector The track selector. - * @param adaptiveTrackSelectionFactory A factory for adaptive {@link TrackSelection}s, or null - * if the selection helper should not support adaptive tracks. + * @param adaptiveTrackSelectionFactory A factory for adaptive {@link TrackSelection}s, or null if + * the selection helper should not support adaptive tracks. */ - public TrackSelectionHelper(MappingTrackSelector selector, - TrackSelection.Factory adaptiveTrackSelectionFactory) { + public TrackSelectionHelper( + DefaultTrackSelector selector, TrackSelection.Factory adaptiveTrackSelectionFactory) { this.selector = selector; this.adaptiveTrackSelectionFactory = adaptiveTrackSelectionFactory; } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java index 50b002893c..7edcf65320 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelector.java @@ -21,59 +21,101 @@ import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; import android.text.TextUtils; +import android.util.Pair; +import android.util.SparseArray; +import android.util.SparseBooleanArray; 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.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.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.concurrent.atomic.AtomicReference; /** * A default {@link TrackSelector} suitable for most use cases. * *
- * {@code + * + * Whilst this selector supports setting specific track overrides, the recommended way of changing + * which tracks are selected is by setting {@link Parameters} that constrain the track selection + * process. For example an instance can specify a preferred language for the audio track, and impose + * constraints on the maximum video resolution that should be selected for adaptive playbacks. + * Modifying the parameters is simple: + * + * {@code * Parameters currentParameters = trackSelector.getParameters(); * // Generate new parameters to prefer German audio and impose a maximum video size constraint. * Parameters newParameters = currentParameters * .withPreferredAudioLanguage("deu") * .withMaxVideoSize(1024, 768); * // Set the new parameters on the selector. - * trackSelector.setParameters(newParameters);} - * + * trackSelector.setParameters(newParameters); + * }
{@code * Parameters currentParameters = trackSelector.getParameters(); * // Generate new parameters to prefer German audio and impose a maximum video size constraint. * Parameters newParameters = currentParameters * .withPreferredAudioLanguage("deu") * .withMaxVideoSize(1024, 768); * // Set the new parameters on the selector. - * trackSelector.setParameters(newParameters);} - *
{@code + * MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); + * TrackGroupArray rendererTrackGroups = mappedTrackInfo == null ? null + * : mappedTrackInfo.getTrackGroups(rendererIndex); + * }
{@code + * trackSelector.setSelectionOverride(rendererIndex, rendererTrackGroups, + * new SelectionOverride(groupIndex, trackIndices)); + * }
Note that an override applies only when the track groups available to the renderer match the + * {@link TrackGroupArray} for which the override was specified. Overrides can be cleared using the + * {@code clearSelectionOverride} methods. + * + *
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}. + * + *
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. + * + *
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. + */ + public final void setSelectionOverride( + int rendererIndex, TrackGroupArray groups, SelectionOverride override) { + Map 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; + } + overrides.put(groups, override); + invalidate(); + } + + /** + * 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 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. + */ + public final SelectionOverride getSelectionOverride(int rendererIndex, TrackGroupArray groups) { + Map overrides = selectionOverrides.get(rendererIndex); + return overrides != null ? overrides.get(groups) : null; + } + + /** + * 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. + */ + public final void clearSelectionOverride(int rendererIndex, TrackGroupArray groups) { + Map overrides = selectionOverrides.get(rendererIndex); + if (overrides == null || !overrides.containsKey(groups)) { + // Nothing to clear. + return; + } + overrides.remove(groups); + if (overrides.isEmpty()) { + selectionOverrides.remove(rendererIndex); + } + invalidate(); + } + + /** + * Clears all track selection overrides for the specified renderer. + * + * @param rendererIndex The renderer index. + */ + public final void clearSelectionOverrides(int rendererIndex) { + Map overrides = selectionOverrides.get(rendererIndex); + if (overrides == null || overrides.isEmpty()) { + // Nothing to clear. + return; + } + selectionOverrides.remove(rendererIndex); + invalidate(); + } + + /** Clears all track selection overrides for all renderers. */ + public final void clearSelectionOverrides() { + if (selectionOverrides.size() == 0) { + // Nothing to clear. + return; + } + selectionOverrides.clear(); + invalidate(); + } + + /** + * 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. + */ + public void setTunnelingAudioSessionId(int tunnelingAudioSessionId) { + if (this.tunnelingAudioSessionId != tunnelingAudioSessionId) { + this.tunnelingAudioSessionId = tunnelingAudioSessionId; + invalidate(); + } + } + // MappingTrackSelector implementation. @Override - protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, - TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + protected final Pair selectTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) + throws ExoPlaybackException { + int rendererCount = rendererCapabilities.length; + TrackSelection[] rendererTrackSelections = + selectAllTracks(rendererCapabilities, mappedTrackInfo); + + // Apply track disabling and overriding. + for (int i = 0; i < rendererCount; i++) { + if (rendererDisabledFlags.get(i)) { + rendererTrackSelections[i] = null; + } else { + TrackGroupArray rendererTrackGroup = mappedTrackInfo.getTrackGroups(i); + if (hasSelectionOverride(i, rendererTrackGroup)) { + SelectionOverride override = selectionOverrides.get(i).get(rendererTrackGroup); + rendererTrackSelections[i] = + override == null ? null : override.createTrackSelection(rendererTrackGroup); + } + } + } + + // Initialize the renderer configurations to the default configuration for all renderers with + // selections, and null otherwise. + RendererConfiguration[] rendererConfigurations = + new RendererConfiguration[rendererCapabilities.length]; + for (int i = 0; i < rendererCount; i++) { + boolean forceRendererDisabled = rendererDisabledFlags.get(i); + boolean rendererEnabled = + !forceRendererDisabled + && (rendererCapabilities[i].getTrackType() == 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, + rendererCapabilities, + rendererConfigurations, + rendererTrackSelections, + tunnelingAudioSessionId); + + return Pair.create(rendererConfigurations, rendererTrackSelections); + } + + // Track selection prior to overrides and disabled flags being applied. + + /** + * Called from {@link #selectTracks(RendererCapabilities[], MappedTrackInfo)} to make a track + * selection for each renderer, prior to overrides and disabled flags being applied. + * + * 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 rendererCapabilities The {@link RendererCapabilities} of each renderer. + * @param mappedTrackInfo Mapped track information. + * @return Track selections for each renderer. A null selection indicates the renderer should be + * disabled, unless RendererCapabilities#getTrackType()} is {@link C#TRACK_TYPE_NONE}. + * @throws ExoPlaybackException If an error occurs while selecting the tracks. + */ + protected TrackSelection[] selectAllTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) throws ExoPlaybackException { - // Make a track selection for each renderer. int rendererCount = rendererCapabilities.length; TrackSelection[] rendererTrackSelections = new TrackSelection[rendererCount]; Parameters params = paramsReference.get(); @@ -665,12 +962,16 @@ public class DefaultTrackSelector extends MappingTrackSelector { for (int i = 0; i < rendererCount; i++) { if (C.TRACK_TYPE_VIDEO == rendererCapabilities[i].getTrackType()) { if (!selectedVideoTracks) { - rendererTrackSelections[i] = selectVideoTrack(rendererCapabilities[i], - rendererTrackGroupArrays[i], rendererFormatSupports[i], params, - adaptiveTrackSelectionFactory); + rendererTrackSelections[i] = + selectVideoTrack( + rendererCapabilities[i], + mappedTrackInfo.getTrackGroups(i), + mappedTrackInfo.getRendererTrackSupport(i), + params, + adaptiveTrackSelectionFactory); selectedVideoTracks = rendererTrackSelections[i] != null; } - seenVideoRendererWithMappedTracks |= rendererTrackGroupArrays[i].length > 0; + seenVideoRendererWithMappedTracks |= mappedTrackInfo.getTrackGroups(i).length > 0; } } @@ -683,33 +984,44 @@ public class DefaultTrackSelector extends MappingTrackSelector { break; case C.TRACK_TYPE_AUDIO: if (!selectedAudioTracks) { - rendererTrackSelections[i] = selectAudioTrack(rendererTrackGroupArrays[i], - rendererFormatSupports[i], params, - seenVideoRendererWithMappedTracks ? null : adaptiveTrackSelectionFactory); + rendererTrackSelections[i] = + selectAudioTrack( + mappedTrackInfo.getTrackGroups(i), + mappedTrackInfo.getRendererTrackSupport(i), + params, + seenVideoRendererWithMappedTracks ? null : adaptiveTrackSelectionFactory); selectedAudioTracks = rendererTrackSelections[i] != null; } break; case C.TRACK_TYPE_TEXT: if (!selectedTextTracks) { - rendererTrackSelections[i] = selectTextTrack(rendererTrackGroupArrays[i], - rendererFormatSupports[i], params); + rendererTrackSelections[i] = + selectTextTrack( + mappedTrackInfo.getTrackGroups(i), + mappedTrackInfo.getRendererTrackSupport(i), + params); selectedTextTracks = rendererTrackSelections[i] != null; } break; default: - rendererTrackSelections[i] = selectOtherTrack(rendererCapabilities[i].getTrackType(), - rendererTrackGroupArrays[i], rendererFormatSupports[i], params); + rendererTrackSelections[i] = + selectOtherTrack( + rendererCapabilities[i].getTrackType(), + mappedTrackInfo.getTrackGroups(i), + mappedTrackInfo.getRendererTrackSupport(i), + params); break; } } + return rendererTrackSelections; } // Video track selection implementation. /** - * Called by {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])} to - * create a {@link TrackSelection} for a video renderer. + * Called by {@link #selectTracks(RendererCapabilities[], MappedTrackInfo)} to create a {@link + * TrackSelection} for a video renderer. * * @param rendererCapabilities The {@link RendererCapabilities} for the renderer. * @param groups The {@link TrackGroupArray} mapped to the renderer. @@ -721,9 +1033,13 @@ public class DefaultTrackSelector extends MappingTrackSelector { * @return The {@link TrackSelection} for the renderer, or null if no selection was made. * @throws ExoPlaybackException If an error occurs while selecting the tracks. */ - protected TrackSelection selectVideoTrack(RendererCapabilities rendererCapabilities, - TrackGroupArray groups, int[][] formatSupport, Parameters params, - TrackSelection.Factory adaptiveTrackSelectionFactory) throws ExoPlaybackException { + protected TrackSelection selectVideoTrack( + RendererCapabilities rendererCapabilities, + TrackGroupArray groups, + int[][] formatSupport, + Parameters params, + TrackSelection.Factory adaptiveTrackSelectionFactory) + throws ExoPlaybackException { TrackSelection selection = null; if (!params.forceLowestBitrate && adaptiveTrackSelectionFactory != null) { selection = selectAdaptiveVideoTrack(rendererCapabilities, groups, formatSupport, @@ -896,25 +1212,11 @@ public class DefaultTrackSelector extends MappingTrackSelector { : new FixedTrackSelection(selectedGroup, selectedTrackIndex); } - /** - * 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)); - } - // Audio track selection implementation. /** - * Called by {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])} to - * create a {@link TrackSelection} for an audio renderer. + * Called by {@link #selectTracks(RendererCapabilities[], MappedTrackInfo)} to create a {@link + * TrackSelection} for an audio renderer. * * @param groups The {@link TrackGroupArray} mapped to the renderer. * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each mapped @@ -925,8 +1227,11 @@ public class DefaultTrackSelector extends MappingTrackSelector { * @return The {@link TrackSelection} for the renderer, or null if no selection was made. * @throws ExoPlaybackException If an error occurs while selecting the tracks. */ - protected TrackSelection selectAudioTrack(TrackGroupArray groups, int[][] formatSupport, - Parameters params, TrackSelection.Factory adaptiveTrackSelectionFactory) + protected TrackSelection selectAudioTrack( + TrackGroupArray groups, + int[][] formatSupport, + Parameters params, + TrackSelection.Factory adaptiveTrackSelectionFactory) throws ExoPlaybackException { int selectedTrackIndex = C.INDEX_UNSET; int selectedGroupIndex = C.INDEX_UNSET; @@ -1021,8 +1326,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { // Text track selection implementation. /** - * Called by {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])} to - * create a {@link TrackSelection} for a text renderer. + * Called by {@link #selectTracks(RendererCapabilities[], MappedTrackInfo)} to create a {@link + * TrackSelection} for a text renderer. * * @param groups The {@link TrackGroupArray} mapped to the renderer. * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each mapped @@ -1031,8 +1336,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { * @return The {@link TrackSelection} for the renderer, or null if no selection was made. * @throws ExoPlaybackException If an error occurs while selecting the tracks. */ - protected TrackSelection selectTextTrack(TrackGroupArray groups, int[][] formatSupport, - Parameters params) throws ExoPlaybackException { + protected TrackSelection selectTextTrack( + TrackGroupArray groups, int[][] formatSupport, Parameters params) + throws ExoPlaybackException { TrackGroup selectedGroup = null; int selectedTrackIndex = 0; int selectedTrackScore = 0; @@ -1092,8 +1398,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { // General track selection methods. /** - * Called by {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])} to - * create a {@link TrackSelection} for a renderer whose type is neither video, audio or text. + * Called by {@link #selectTracks(RendererCapabilities[], MappedTrackInfo)} 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. @@ -1103,8 +1409,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { * @return The {@link TrackSelection} for the renderer, or null if no selection was made. * @throws ExoPlaybackException If an error occurs while selecting the tracks. */ - protected TrackSelection selectOtherTrack(int trackType, TrackGroupArray groups, - int[][] formatSupport, Parameters params) throws ExoPlaybackException { + protected TrackSelection selectOtherTrack( + int trackType, TrackGroupArray groups, int[][] formatSupport, Parameters params) + throws ExoPlaybackException { TrackGroup selectedGroup = null; int selectedTrackIndex = 0; int selectedTrackScore = 0; @@ -1132,6 +1439,111 @@ public class DefaultTrackSelector extends MappingTrackSelector { : new FixedTrackSelection(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 rendererCapabilities The {@link RendererCapabilities} of the renderers for which {@link + * TrackSelection}s are to be generated. + * @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, + RendererCapabilities[] rendererCapabilities, + RendererConfiguration[] rendererConfigurations, + 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 < rendererCapabilities.length; i++) { + int rendererType = rendererCapabilities[i].getTrackType(); + TrackSelection trackSelection = trackSelections[i]; + if ((rendererType == C.TRACK_TYPE_AUDIO || rendererType == C.TRACK_TYPE_VIDEO) + && trackSelection != null) { + if (rendererSupportsTunneling( + mappedTrackInfo.getRendererTrackSupport(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 formatSupport The result of {@link RendererCapabilities#supportsFormat} 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( + int[][] formatSupport, TrackGroupArray trackGroups, TrackSelection selection) { + if (selection == null) { + return false; + } + int trackGroupIndex = trackGroups.indexOf(selection.getTrackGroup()); + for (int i = 0; i < selection.length(); i++) { + int trackFormatSupport = formatSupport[trackGroupIndex][selection.getIndexInTrackGroup(i)]; + if ((trackFormatSupport & RendererCapabilities.TUNNELING_SUPPORT_MASK) + != 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)); + } + /** * Applies the {@link RendererCapabilities#FORMAT_SUPPORT_MASK} to a value obtained from * {@link RendererCapabilities#supportsFormat(Format)}, returning true if the result is @@ -1175,8 +1587,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { && TextUtils.equals(language, Util.normalizeLanguageCode(format.language)); } - // Viewport size util methods. - private static List getViewportFilteredTrackIndices(TrackGroup group, int viewportWidth, int viewportHeight, boolean orientationMayChange) { // Initially include all indices. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index abb72b5cb1..75a7565b98 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -15,13 +15,10 @@ */ package com.google.android.exoplayer2.trackselection; -import android.content.Context; import android.support.annotation.IntDef; -import android.util.SparseArray; -import android.util.SparseBooleanArray; +import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererConfiguration; @@ -31,51 +28,11 @@ import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; /** * Base class for {@link TrackSelector}s that first establish a mapping between {@link TrackGroup}s * and {@link Renderer}s, and then from that mapping create a {@link TrackSelection} for each * renderer. - * - * Track overrides - * Mapping track selectors support overriding of track selections for each renderer. To specify an - * override for a renderer it's first necessary to obtain the tracks that have been mapped to it: - * - * {@code - * MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); - * TrackGroupArray rendererTrackGroups = mappedTrackInfo == null ? null - * : mappedTrackInfo.getTrackGroups(rendererIndex);} - * - * 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} of the track group you - * want to select and the {@code trackIndices} within it. You can then create and set the override: - * - * {@code - * trackSelector.setSelectionOverride(rendererIndex, rendererTrackGroups, - * new SelectionOverride(trackSelectionFactory, groupIndex, trackIndices));} - * - * where {@code trackSelectionFactory} is a {@link TrackSelection.Factory} for generating concrete - * {@link TrackSelection} instances for the override. It's also possible to pass {@code null} as the - * selection override if you don't want any tracks to be selected. - * - * Note that an override applies only when the track groups available to the renderer match the - * {@link TrackGroupArray} for which the override was specified. Overrides can be cleared using - * the {@code clearSelectionOverride} methods. - * - * Disabling renderers - * Renderers can be disabled using {@link #setRendererDisabled(int, boolean)}. 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. - * - * Tunneling - * Tunneled playback can be enabled in cases where the combination of renderers and selected tracks - * support it. See {@link #setTunnelingAudioSessionId(int)} for more details. */ public abstract class MappingTrackSelector extends TrackSelector { @@ -147,6 +104,18 @@ public abstract class MappingTrackSelector extends TrackSelector { return trackGroups[rendererIndex]; } + /** + * Returns the extent to which a renderer can play each of the tracks in the track groups mapped + * to it. + * + * @param rendererIndex The renderer index. + * @return The result of {@link RendererCapabilities#supportsFormat} for each track mapped to + * the renderer, indexed by track group and track index (in that order). + */ + public int[][] getRendererTrackSupport(int rendererIndex) { + return formatSupport[rendererIndex]; + } + /** * Returns the extent to which a renderer can play the tracks in the track groups mapped to it. * @@ -295,64 +264,19 @@ public abstract class MappingTrackSelector extends TrackSelector { } - /** - * A track selection override. - */ - public static final class SelectionOverride { + // TODO: Make DefaultTrackSelector.SelectionOverride final when this is removed. + /** @deprecated Use {@link DefaultTrackSelector.SelectionOverride} */ + @Deprecated + public static final class SelectionOverride extends DefaultTrackSelector.SelectionOverride { - public final TrackSelection.Factory factory; - public final int groupIndex; - public final int[] tracks; - public final int length; - - /** - * @param factory A factory for creating selections from this override. - * @param groupIndex The overriding track group index. - * @param tracks The overriding track indices within the track group. - */ public SelectionOverride(TrackSelection.Factory factory, int groupIndex, int... tracks) { - this.factory = factory; - this.groupIndex = groupIndex; - this.tracks = tracks; - this.length = tracks.length; - } - - /** - * Creates an selection from this override. - * - * @param groups The track groups whose selection is being overridden. - * @return The selection. - */ - public TrackSelection createTrackSelection(TrackGroupArray groups) { - return factory.createTrackSelection(groups.get(groupIndex), tracks); - } - - /** - * 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; + super(factory, groupIndex, tracks); } } - private final SparseArray> selectionOverrides; - private final SparseBooleanArray rendererDisabledFlags; - private int tunnelingAudioSessionId; - private MappedTrackInfo currentMappedTrackInfo; - public MappingTrackSelector() { - selectionOverrides = new SparseArray<>(); - rendererDisabledFlags = new SparseBooleanArray(); - tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET; - } - /** * Returns the mapping information for the currently active track selection, or null if no * selection is currently active. @@ -361,158 +285,13 @@ public abstract class MappingTrackSelector extends TrackSelector { return currentMappedTrackInfo; } - /** - * 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. - */ - public final void setRendererDisabled(int rendererIndex, boolean disabled) { - if (rendererDisabledFlags.get(rendererIndex) == disabled) { - // The disabled flag is unchanged. - return; - } - rendererDisabledFlags.put(rendererIndex, disabled); - invalidate(); - } - - /** - * 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); - } - - /** - * Overrides the track selection for the renderer at the specified index. - * - * 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}. - * - * 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. - * - * 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. - */ - public final void setSelectionOverride(int rendererIndex, TrackGroupArray groups, - SelectionOverride override) { - Map 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; - } - overrides.put(groups, override); - invalidate(); - } - - /** - * 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 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. - */ - public final SelectionOverride getSelectionOverride(int rendererIndex, TrackGroupArray groups) { - Map overrides = selectionOverrides.get(rendererIndex); - return overrides != null ? overrides.get(groups) : null; - } - - /** - * 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. - */ - public final void clearSelectionOverride(int rendererIndex, TrackGroupArray groups) { - Map overrides = selectionOverrides.get(rendererIndex); - if (overrides == null || !overrides.containsKey(groups)) { - // Nothing to clear. - return; - } - overrides.remove(groups); - if (overrides.isEmpty()) { - selectionOverrides.remove(rendererIndex); - } - invalidate(); - } - - /** - * Clears all track selection overrides for the specified renderer. - * - * @param rendererIndex The renderer index. - */ - public final void clearSelectionOverrides(int rendererIndex) { - Map overrides = selectionOverrides.get(rendererIndex); - if (overrides == null || overrides.isEmpty()) { - // Nothing to clear. - return; - } - selectionOverrides.remove(rendererIndex); - invalidate(); - } - - /** - * Clears all track selection overrides for all renderers. - */ - public final void clearSelectionOverrides() { - if (selectionOverrides.size() == 0) { - // Nothing to clear. - return; - } - selectionOverrides.clear(); - invalidate(); - } - - /** - * 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. - */ - public void setTunnelingAudioSessionId(int tunnelingAudioSessionId) { - if (this.tunnelingAudioSessionId != tunnelingAudioSessionId) { - this.tunnelingAudioSessionId = tunnelingAudioSessionId; - invalidate(); - } - } - // TrackSelector implementation. + @Override + public final void onSelectionActivated(Object info) { + currentMappedTrackInfo = (MappedTrackInfo) info; + } + @Override public final TrackSelectorResult selectTracks(RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups) throws ExoPlaybackException { @@ -562,75 +341,33 @@ public abstract class MappingTrackSelector extends TrackSelector { TrackGroupArray unassociatedTrackGroupArray = new TrackGroupArray(Arrays.copyOf( rendererTrackGroups[rendererCapabilities.length], unassociatedTrackGroupCount)); - TrackSelection[] trackSelections = selectTracks(rendererCapabilities, rendererTrackGroupArrays, - rendererFormatSupports); - - // Apply track disabling and overriding. - for (int i = 0; i < rendererCapabilities.length; i++) { - if (rendererDisabledFlags.get(i)) { - trackSelections[i] = null; - } else { - TrackGroupArray rendererTrackGroup = rendererTrackGroupArrays[i]; - if (hasSelectionOverride(i, rendererTrackGroup)) { - SelectionOverride override = selectionOverrides.get(i).get(rendererTrackGroup); - trackSelections[i] = override == null ? null - : override.createTrackSelection(rendererTrackGroup); - } - } - } - - boolean[] rendererEnabled = determineEnabledRenderers(rendererCapabilities, trackSelections); - // Package up the track information and selections. - MappedTrackInfo mappedTrackInfo = new MappedTrackInfo(rendererTrackTypes, - rendererTrackGroupArrays, mixedMimeTypeAdaptationSupport, rendererFormatSupports, - unassociatedTrackGroupArray); + MappedTrackInfo mappedTrackInfo = + new MappedTrackInfo( + rendererTrackTypes, + rendererTrackGroupArrays, + mixedMimeTypeAdaptationSupport, + rendererFormatSupports, + unassociatedTrackGroupArray); - // Initialize the renderer configurations to the default configuration for all renderers with - // selections, and null otherwise. - RendererConfiguration[] rendererConfigurations = - new RendererConfiguration[rendererCapabilities.length]; - for (int i = 0; i < rendererCapabilities.length; i++) { - rendererConfigurations[i] = rendererEnabled[i] ? RendererConfiguration.DEFAULT : null; - } - // Configure audio and video renderers to use tunneling if appropriate. - maybeConfigureRenderersForTunneling(rendererCapabilities, rendererTrackGroupArrays, - rendererFormatSupports, rendererConfigurations, trackSelections, tunnelingAudioSessionId); - - return new TrackSelectorResult(rendererConfigurations, trackSelections, mappedTrackInfo); - } - - private boolean[] determineEnabledRenderers(RendererCapabilities[] rendererCapabilities, - TrackSelection[] trackSelections) { - boolean[] rendererEnabled = new boolean[trackSelections.length]; - for (int i = 0; i < rendererEnabled.length; i++) { - boolean forceRendererDisabled = rendererDisabledFlags.get(i); - rendererEnabled[i] = !forceRendererDisabled - && (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE - || trackSelections[i] != null); - } - return rendererEnabled; - } - - @Override - public final void onSelectionActivated(Object info) { - currentMappedTrackInfo = (MappedTrackInfo) info; + Pair result = + selectTracks(rendererCapabilities, mappedTrackInfo); + return new TrackSelectorResult(result.first, result.second, mappedTrackInfo); } /** - * Given an array of renderer capabilities and the {@link TrackGroupArray}s mapped to each of - * them, provides a {@link TrackSelection} per renderer. + * Given mapped track information, returns a track selection and configuration for each renderer. * - * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which - * {@link TrackSelection}s are to be generated. - * @param rendererTrackGroupArrays The {@link TrackGroupArray}s mapped to each of the renderers. - * @param rendererFormatSupports The result of {@link RendererCapabilities#supportsFormat} for - * each mapped track, indexed by renderer index, track group index and track index (in that - * order). + * @param rendererCapabilities The {@link RendererCapabilities} of each renderer. + * @param mappedTrackInfo Mapped track information. + * @return A pair consisting of the track selections and configurations for each renderer. A null + * configuration indicates the renderer should be disabled, in which case the track selection + * will also be null. A track selection may also be null for a non-disabled renderer if {@link + * RendererCapabilities#getTrackType()} is {@link C#TRACK_TYPE_NONE}. * @throws ExoPlaybackException If an error occurs while selecting the tracks. */ - protected abstract TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, - TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + protected abstract Pair selectTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) throws ExoPlaybackException; /** @@ -712,92 +449,4 @@ public abstract class MappingTrackSelector extends TrackSelector { return mixedMimeTypeAdaptationSupport; } - /** - * 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 rendererCapabilities The {@link RendererCapabilities} of the renderers for which - * {@link TrackSelection}s are to be generated. - * @param rendererTrackGroupArrays An array of {@link TrackGroupArray}s where each entry - * corresponds to the renderer of equal index in {@code renderers}. - * @param rendererFormatSupports Maps every available track to a specific level of support as - * defined by the renderer {@code FORMAT_*} constants. - * @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( - RendererCapabilities[] rendererCapabilities, TrackGroupArray[] rendererTrackGroupArrays, - int[][][] rendererFormatSupports, RendererConfiguration[] rendererConfigurations, - 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 < rendererCapabilities.length; i++) { - int rendererType = rendererCapabilities[i].getTrackType(); - TrackSelection trackSelection = trackSelections[i]; - if ((rendererType == C.TRACK_TYPE_AUDIO || rendererType == C.TRACK_TYPE_VIDEO) - && trackSelection != null) { - if (rendererSupportsTunneling(rendererFormatSupports[i], rendererTrackGroupArrays[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 formatSupport The result of {@link RendererCapabilities#supportsFormat} 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(int[][] formatSupport, - TrackGroupArray trackGroups, TrackSelection selection) { - if (selection == null) { - return false; - } - int trackGroupIndex = trackGroups.indexOf(selection.getTrackGroup()); - for (int i = 0; i < selection.length(); i++) { - int trackFormatSupport = formatSupport[trackGroupIndex][selection.getIndexInTrackGroup(i)]; - if ((trackFormatSupport & RendererCapabilities.TUNNELING_SUPPORT_MASK) - != RendererCapabilities.TUNNELING_SUPPORTED) { - return false; - } - } - return true; - } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 90daf0ef03..ed91f6651c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -668,7 +668,7 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - List createdTrackSelections = trackSelector.getSelectedTrackSelections(); + List createdTrackSelections = trackSelector.getAllTrackSelections(); int numSelectionsEnabled = 0; // Assert that all tracks selection are disabled at the end of the playback. for (FakeTrackSelection trackSelection : createdTrackSelections) { @@ -676,9 +676,7 @@ public final class ExoPlayerTest { numSelectionsEnabled += trackSelection.enableCount; } // There are 2 renderers, and track selections are made once (1 period). - // Track selections are not reused, so there are 2 track selections made. assertThat(createdTrackSelections).hasSize(2); - // There should be 2 track selections enabled in total. assertThat(numSelectionsEnabled).isEqualTo(2); } @@ -699,7 +697,7 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - List createdTrackSelections = trackSelector.getSelectedTrackSelections(); + List createdTrackSelections = trackSelector.getAllTrackSelections(); int numSelectionsEnabled = 0; // Assert that all tracks selection are disabled at the end of the playback. for (FakeTrackSelection trackSelection : createdTrackSelections) { @@ -707,9 +705,7 @@ public final class ExoPlayerTest { numSelectionsEnabled += trackSelection.enableCount; } // There are 2 renderers, and track selections are made twice (2 periods). - // Track selections are not reused, so there are 4 track selections made. assertThat(createdTrackSelections).hasSize(4); - // There should be 4 track selections enabled in total. assertThat(numSelectionsEnabled).isEqualTo(4); } @@ -739,23 +735,21 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - List createdTrackSelections = trackSelector.getSelectedTrackSelections(); + List createdTrackSelections = trackSelector.getAllTrackSelections(); int numSelectionsEnabled = 0; // Assert that all tracks selection are disabled at the end of the playback. for (FakeTrackSelection trackSelection : createdTrackSelections) { assertThat(trackSelection.isEnabled).isFalse(); numSelectionsEnabled += trackSelection.enableCount; } - // There are 2 renderers, and track selections are made twice. - // Track selections are not reused, so there are 4 track selections made. + // There are 2 renderers, and track selections are made twice. The second time one renderer is + // disabled, so only one out of the two track selections is enabled. assertThat(createdTrackSelections).hasSize(4); - // Initially there are 2 track selections enabled. - // The second time one renderer is disabled, so only 1 track selection should be enabled. assertThat(numSelectionsEnabled).isEqualTo(3); } @Test - public void testAllActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreUsed() + public void testAllActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreReused() throws Exception { Timeline timeline = new FakeTimeline(/* windowCount= */ 1); MediaSource mediaSource = @@ -780,18 +774,17 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - List createdTrackSelections = trackSelector.getSelectedTrackSelections(); + List createdTrackSelections = trackSelector.getAllTrackSelections(); int numSelectionsEnabled = 0; // Assert that all tracks selection are disabled at the end of the playback. for (FakeTrackSelection trackSelection : createdTrackSelections) { assertThat(trackSelection.isEnabled).isFalse(); numSelectionsEnabled += trackSelection.enableCount; } - // There are 2 renderers, and track selections are made twice. - // TrackSelections are reused, so there are only 2 track selections made for 2 renderers. + // There are 2 renderers, and track selections are made twice. The second time one renderer is + // disabled, and the selector re-uses the previous selection for the enabled renderer. So we + // expect two track selections, one of which will have been enabled twice. assertThat(createdTrackSelections).hasSize(2); - // Initially there are 2 track selections enabled. - // The second time one renderer is disabled, so only 1 track selection should be enabled. assertThat(numSelectionsEnabled).isEqualTo(3); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java index 9bc4ae5382..ab6cea94ad 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java @@ -17,10 +17,12 @@ package com.google.android.exoplayer2.trackselection; import static com.google.common.truth.Truth.assertThat; +import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; +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.util.MimeTypes; @@ -90,25 +92,28 @@ public final class MappingTrackSelectorTest { } /** - * A {@link MappingTrackSelector} that returns a fixed result from - * {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])}. + * A {@link MappingTrackSelector} that stashes the {@link MappedTrackInfo} passed to {@link + * #selectTracks(RendererCapabilities[], MappedTrackInfo)}. */ private static final class FakeMappingTrackSelector extends MappingTrackSelector { - private TrackGroupArray[] lastRendererTrackGroupArrays; + private MappedTrackInfo lastMappedTrackInfo; @Override - protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, - TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + protected Pair selectTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) throws ExoPlaybackException { - lastRendererTrackGroupArrays = rendererTrackGroupArrays; - return new TrackSelection[rendererCapabilities.length]; + lastMappedTrackInfo = mappedTrackInfo; + return Pair.create( + new RendererConfiguration[rendererCapabilities.length], + new TrackSelection[rendererCapabilities.length]); } public void assertMappedTrackGroups(int rendererIndex, TrackGroup... expected) { - assertThat(lastRendererTrackGroupArrays[rendererIndex].length).isEqualTo(expected.length); + TrackGroupArray rendererTrackGroupArray = lastMappedTrackInfo.getTrackGroups(rendererIndex); + assertThat(rendererTrackGroupArray.length).isEqualTo(expected.length); for (int i = 0; i < expected.length; i++) { - assertThat(lastRendererTrackGroupArrays[rendererIndex].get(i)).isEqualTo(expected[i]); + assertThat(rendererTrackGroupArray.get(i)).isEqualTo(expected[i]); } } diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java index d5585f57a0..0c0a5879ca 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java @@ -394,25 +394,30 @@ public final class DashTestRunner { } @Override - protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, - TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + protected TrackSelection[] selectAllTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) throws ExoPlaybackException { Assertions.checkState(rendererCapabilities[VIDEO_RENDERER_INDEX].getTrackType() == C.TRACK_TYPE_VIDEO); Assertions.checkState(rendererCapabilities[AUDIO_RENDERER_INDEX].getTrackType() == C.TRACK_TYPE_AUDIO); - Assertions.checkState(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].length == 1); - Assertions.checkState(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].length == 1); + TrackGroupArray videoTrackGroups = mappedTrackInfo.getTrackGroups(VIDEO_RENDERER_INDEX); + TrackGroupArray audioTrackGroups = mappedTrackInfo.getTrackGroups(AUDIO_RENDERER_INDEX); + Assertions.checkState(videoTrackGroups.length == 1); + Assertions.checkState(audioTrackGroups.length == 1); TrackSelection[] selections = new TrackSelection[rendererCapabilities.length]; - selections[VIDEO_RENDERER_INDEX] = new RandomTrackSelection( - rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0), - getVideoTrackIndices(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0), - rendererFormatSupports[VIDEO_RENDERER_INDEX][0], videoFormatIds, - canIncludeAdditionalVideoFormats), - 0 /* seed */); - selections[AUDIO_RENDERER_INDEX] = new FixedTrackSelection( - rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), - getTrackIndex(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), audioFormatId)); + selections[VIDEO_RENDERER_INDEX] = + new RandomTrackSelection( + videoTrackGroups.get(0), + getVideoTrackIndices( + videoTrackGroups.get(0), + mappedTrackInfo.getRendererTrackSupport(VIDEO_RENDERER_INDEX)[0], + videoFormatIds, + canIncludeAdditionalVideoFormats), + 0 /* seed */); + selections[AUDIO_RENDERER_INDEX] = + new FixedTrackSelection( + audioTrackGroups.get(0), getTrackIndex(audioTrackGroups.get(0), audioFormatId)); includedAdditionalVideoFormats = selections[VIDEO_RENDERER_INDEX].length() > videoFormatIds.length; return selections; diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java index ecf93ba775..0e8045d489 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java @@ -21,14 +21,15 @@ import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelection; import java.util.ArrayList; import java.util.List; -/** A fake {@link DefaultTrackSelector} that returns {@link FakeTrackSelection}s. */ +/** A fake {@link MappingTrackSelector} that returns {@link FakeTrackSelection}s. */ public class FakeTrackSelector extends DefaultTrackSelector { - private final List selectedTrackSelections = new ArrayList<>(); + private final List trackSelections = new ArrayList<>(); private final boolean mayReuseTrackSelection; public FakeTrackSelector() { @@ -45,39 +46,35 @@ public class FakeTrackSelector extends DefaultTrackSelector { } @Override - protected TrackSelection[] selectTracks( - RendererCapabilities[] rendererCapabilities, - TrackGroupArray[] rendererTrackGroupArrays, - int[][][] rendererFormatSupports) + protected TrackSelection[] selectAllTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) throws ExoPlaybackException { - List resultList = new ArrayList<>(); - for (TrackGroupArray trackGroupArray : rendererTrackGroupArrays) { - TrackGroup trackGroup = trackGroupArray.get(0); - FakeTrackSelection trackSelectionForRenderer = reuseOrCreateTrackSelection(trackGroup); - resultList.add(trackSelectionForRenderer); + TrackSelection[] selections = new TrackSelection[mappedTrackInfo.length]; + for (int i = 0; i < mappedTrackInfo.length; i++) { + TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(i); + boolean hasTracks = trackGroupArray.length > 0; + selections[i] = hasTracks ? reuseOrCreateTrackSelection(trackGroupArray.get(0)) : null; } - return resultList.toArray(new TrackSelection[resultList.size()]); + return selections; } @NonNull private FakeTrackSelection reuseOrCreateTrackSelection(TrackGroup trackGroup) { - FakeTrackSelection trackSelectionForRenderer = null; if (mayReuseTrackSelection) { - for (FakeTrackSelection selectedTrackSelection : selectedTrackSelections) { - if (selectedTrackSelection.getTrackGroup().equals(trackGroup)) { - trackSelectionForRenderer = selectedTrackSelection; + for (FakeTrackSelection trackSelection : trackSelections) { + if (trackSelection.getTrackGroup().equals(trackGroup)) { + return trackSelection; } } } - if (trackSelectionForRenderer == null) { - trackSelectionForRenderer = new FakeTrackSelection(trackGroup); - selectedTrackSelections.add(trackSelectionForRenderer); - } - return trackSelectionForRenderer; + FakeTrackSelection trackSelection = new FakeTrackSelection(trackGroup); + trackSelections.add(trackSelection); + return trackSelection; } /** Returns list of all {@link FakeTrackSelection}s that this track selector has made so far. */ - public List getSelectedTrackSelections() { - return selectedTrackSelections; + public List getAllTrackSelections() { + return trackSelections; } + }
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 rendererCapabilities The {@link RendererCapabilities} of each renderer. + * @param mappedTrackInfo Mapped track information. + * @return Track selections for each renderer. A null selection indicates the renderer should be + * disabled, unless RendererCapabilities#getTrackType()} is {@link C#TRACK_TYPE_NONE}. + * @throws ExoPlaybackException If an error occurs while selecting the tracks. + */ + protected TrackSelection[] selectAllTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) throws ExoPlaybackException { - // Make a track selection for each renderer. int rendererCount = rendererCapabilities.length; TrackSelection[] rendererTrackSelections = new TrackSelection[rendererCount]; Parameters params = paramsReference.get(); @@ -665,12 +962,16 @@ public class DefaultTrackSelector extends MappingTrackSelector { for (int i = 0; i < rendererCount; i++) { if (C.TRACK_TYPE_VIDEO == rendererCapabilities[i].getTrackType()) { if (!selectedVideoTracks) { - rendererTrackSelections[i] = selectVideoTrack(rendererCapabilities[i], - rendererTrackGroupArrays[i], rendererFormatSupports[i], params, - adaptiveTrackSelectionFactory); + rendererTrackSelections[i] = + selectVideoTrack( + rendererCapabilities[i], + mappedTrackInfo.getTrackGroups(i), + mappedTrackInfo.getRendererTrackSupport(i), + params, + adaptiveTrackSelectionFactory); selectedVideoTracks = rendererTrackSelections[i] != null; } - seenVideoRendererWithMappedTracks |= rendererTrackGroupArrays[i].length > 0; + seenVideoRendererWithMappedTracks |= mappedTrackInfo.getTrackGroups(i).length > 0; } } @@ -683,33 +984,44 @@ public class DefaultTrackSelector extends MappingTrackSelector { break; case C.TRACK_TYPE_AUDIO: if (!selectedAudioTracks) { - rendererTrackSelections[i] = selectAudioTrack(rendererTrackGroupArrays[i], - rendererFormatSupports[i], params, - seenVideoRendererWithMappedTracks ? null : adaptiveTrackSelectionFactory); + rendererTrackSelections[i] = + selectAudioTrack( + mappedTrackInfo.getTrackGroups(i), + mappedTrackInfo.getRendererTrackSupport(i), + params, + seenVideoRendererWithMappedTracks ? null : adaptiveTrackSelectionFactory); selectedAudioTracks = rendererTrackSelections[i] != null; } break; case C.TRACK_TYPE_TEXT: if (!selectedTextTracks) { - rendererTrackSelections[i] = selectTextTrack(rendererTrackGroupArrays[i], - rendererFormatSupports[i], params); + rendererTrackSelections[i] = + selectTextTrack( + mappedTrackInfo.getTrackGroups(i), + mappedTrackInfo.getRendererTrackSupport(i), + params); selectedTextTracks = rendererTrackSelections[i] != null; } break; default: - rendererTrackSelections[i] = selectOtherTrack(rendererCapabilities[i].getTrackType(), - rendererTrackGroupArrays[i], rendererFormatSupports[i], params); + rendererTrackSelections[i] = + selectOtherTrack( + rendererCapabilities[i].getTrackType(), + mappedTrackInfo.getTrackGroups(i), + mappedTrackInfo.getRendererTrackSupport(i), + params); break; } } + return rendererTrackSelections; } // Video track selection implementation. /** - * Called by {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])} to - * create a {@link TrackSelection} for a video renderer. + * Called by {@link #selectTracks(RendererCapabilities[], MappedTrackInfo)} to create a {@link + * TrackSelection} for a video renderer. * * @param rendererCapabilities The {@link RendererCapabilities} for the renderer. * @param groups The {@link TrackGroupArray} mapped to the renderer. @@ -721,9 +1033,13 @@ public class DefaultTrackSelector extends MappingTrackSelector { * @return The {@link TrackSelection} for the renderer, or null if no selection was made. * @throws ExoPlaybackException If an error occurs while selecting the tracks. */ - protected TrackSelection selectVideoTrack(RendererCapabilities rendererCapabilities, - TrackGroupArray groups, int[][] formatSupport, Parameters params, - TrackSelection.Factory adaptiveTrackSelectionFactory) throws ExoPlaybackException { + protected TrackSelection selectVideoTrack( + RendererCapabilities rendererCapabilities, + TrackGroupArray groups, + int[][] formatSupport, + Parameters params, + TrackSelection.Factory adaptiveTrackSelectionFactory) + throws ExoPlaybackException { TrackSelection selection = null; if (!params.forceLowestBitrate && adaptiveTrackSelectionFactory != null) { selection = selectAdaptiveVideoTrack(rendererCapabilities, groups, formatSupport, @@ -896,25 +1212,11 @@ public class DefaultTrackSelector extends MappingTrackSelector { : new FixedTrackSelection(selectedGroup, selectedTrackIndex); } - /** - * 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)); - } - // Audio track selection implementation. /** - * Called by {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])} to - * create a {@link TrackSelection} for an audio renderer. + * Called by {@link #selectTracks(RendererCapabilities[], MappedTrackInfo)} to create a {@link + * TrackSelection} for an audio renderer. * * @param groups The {@link TrackGroupArray} mapped to the renderer. * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each mapped @@ -925,8 +1227,11 @@ public class DefaultTrackSelector extends MappingTrackSelector { * @return The {@link TrackSelection} for the renderer, or null if no selection was made. * @throws ExoPlaybackException If an error occurs while selecting the tracks. */ - protected TrackSelection selectAudioTrack(TrackGroupArray groups, int[][] formatSupport, - Parameters params, TrackSelection.Factory adaptiveTrackSelectionFactory) + protected TrackSelection selectAudioTrack( + TrackGroupArray groups, + int[][] formatSupport, + Parameters params, + TrackSelection.Factory adaptiveTrackSelectionFactory) throws ExoPlaybackException { int selectedTrackIndex = C.INDEX_UNSET; int selectedGroupIndex = C.INDEX_UNSET; @@ -1021,8 +1326,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { // Text track selection implementation. /** - * Called by {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])} to - * create a {@link TrackSelection} for a text renderer. + * Called by {@link #selectTracks(RendererCapabilities[], MappedTrackInfo)} to create a {@link + * TrackSelection} for a text renderer. * * @param groups The {@link TrackGroupArray} mapped to the renderer. * @param formatSupport The result of {@link RendererCapabilities#supportsFormat} for each mapped @@ -1031,8 +1336,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { * @return The {@link TrackSelection} for the renderer, or null if no selection was made. * @throws ExoPlaybackException If an error occurs while selecting the tracks. */ - protected TrackSelection selectTextTrack(TrackGroupArray groups, int[][] formatSupport, - Parameters params) throws ExoPlaybackException { + protected TrackSelection selectTextTrack( + TrackGroupArray groups, int[][] formatSupport, Parameters params) + throws ExoPlaybackException { TrackGroup selectedGroup = null; int selectedTrackIndex = 0; int selectedTrackScore = 0; @@ -1092,8 +1398,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { // General track selection methods. /** - * Called by {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])} to - * create a {@link TrackSelection} for a renderer whose type is neither video, audio or text. + * Called by {@link #selectTracks(RendererCapabilities[], MappedTrackInfo)} 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. @@ -1103,8 +1409,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { * @return The {@link TrackSelection} for the renderer, or null if no selection was made. * @throws ExoPlaybackException If an error occurs while selecting the tracks. */ - protected TrackSelection selectOtherTrack(int trackType, TrackGroupArray groups, - int[][] formatSupport, Parameters params) throws ExoPlaybackException { + protected TrackSelection selectOtherTrack( + int trackType, TrackGroupArray groups, int[][] formatSupport, Parameters params) + throws ExoPlaybackException { TrackGroup selectedGroup = null; int selectedTrackIndex = 0; int selectedTrackScore = 0; @@ -1132,6 +1439,111 @@ public class DefaultTrackSelector extends MappingTrackSelector { : new FixedTrackSelection(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 rendererCapabilities The {@link RendererCapabilities} of the renderers for which {@link + * TrackSelection}s are to be generated. + * @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, + RendererCapabilities[] rendererCapabilities, + RendererConfiguration[] rendererConfigurations, + 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 < rendererCapabilities.length; i++) { + int rendererType = rendererCapabilities[i].getTrackType(); + TrackSelection trackSelection = trackSelections[i]; + if ((rendererType == C.TRACK_TYPE_AUDIO || rendererType == C.TRACK_TYPE_VIDEO) + && trackSelection != null) { + if (rendererSupportsTunneling( + mappedTrackInfo.getRendererTrackSupport(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 formatSupport The result of {@link RendererCapabilities#supportsFormat} 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( + int[][] formatSupport, TrackGroupArray trackGroups, TrackSelection selection) { + if (selection == null) { + return false; + } + int trackGroupIndex = trackGroups.indexOf(selection.getTrackGroup()); + for (int i = 0; i < selection.length(); i++) { + int trackFormatSupport = formatSupport[trackGroupIndex][selection.getIndexInTrackGroup(i)]; + if ((trackFormatSupport & RendererCapabilities.TUNNELING_SUPPORT_MASK) + != 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)); + } + /** * Applies the {@link RendererCapabilities#FORMAT_SUPPORT_MASK} to a value obtained from * {@link RendererCapabilities#supportsFormat(Format)}, returning true if the result is @@ -1175,8 +1587,6 @@ public class DefaultTrackSelector extends MappingTrackSelector { && TextUtils.equals(language, Util.normalizeLanguageCode(format.language)); } - // Viewport size util methods. - private static List getViewportFilteredTrackIndices(TrackGroup group, int viewportWidth, int viewportHeight, boolean orientationMayChange) { // Initially include all indices. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java index abb72b5cb1..75a7565b98 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/trackselection/MappingTrackSelector.java @@ -15,13 +15,10 @@ */ package com.google.android.exoplayer2.trackselection; -import android.content.Context; import android.support.annotation.IntDef; -import android.util.SparseArray; -import android.util.SparseBooleanArray; +import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; -import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Renderer; import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.RendererConfiguration; @@ -31,51 +28,11 @@ import com.google.android.exoplayer2.util.Util; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; /** * Base class for {@link TrackSelector}s that first establish a mapping between {@link TrackGroup}s * and {@link Renderer}s, and then from that mapping create a {@link TrackSelection} for each * renderer. - * - * Track overrides - * Mapping track selectors support overriding of track selections for each renderer. To specify an - * override for a renderer it's first necessary to obtain the tracks that have been mapped to it: - * - * {@code - * MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); - * TrackGroupArray rendererTrackGroups = mappedTrackInfo == null ? null - * : mappedTrackInfo.getTrackGroups(rendererIndex);} - * - * 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} of the track group you - * want to select and the {@code trackIndices} within it. You can then create and set the override: - * - * {@code - * trackSelector.setSelectionOverride(rendererIndex, rendererTrackGroups, - * new SelectionOverride(trackSelectionFactory, groupIndex, trackIndices));} - * - * where {@code trackSelectionFactory} is a {@link TrackSelection.Factory} for generating concrete - * {@link TrackSelection} instances for the override. It's also possible to pass {@code null} as the - * selection override if you don't want any tracks to be selected. - * - * Note that an override applies only when the track groups available to the renderer match the - * {@link TrackGroupArray} for which the override was specified. Overrides can be cleared using - * the {@code clearSelectionOverride} methods. - * - * Disabling renderers - * Renderers can be disabled using {@link #setRendererDisabled(int, boolean)}. 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. - * - * Tunneling - * Tunneled playback can be enabled in cases where the combination of renderers and selected tracks - * support it. See {@link #setTunnelingAudioSessionId(int)} for more details. */ public abstract class MappingTrackSelector extends TrackSelector { @@ -147,6 +104,18 @@ public abstract class MappingTrackSelector extends TrackSelector { return trackGroups[rendererIndex]; } + /** + * Returns the extent to which a renderer can play each of the tracks in the track groups mapped + * to it. + * + * @param rendererIndex The renderer index. + * @return The result of {@link RendererCapabilities#supportsFormat} for each track mapped to + * the renderer, indexed by track group and track index (in that order). + */ + public int[][] getRendererTrackSupport(int rendererIndex) { + return formatSupport[rendererIndex]; + } + /** * Returns the extent to which a renderer can play the tracks in the track groups mapped to it. * @@ -295,64 +264,19 @@ public abstract class MappingTrackSelector extends TrackSelector { } - /** - * A track selection override. - */ - public static final class SelectionOverride { + // TODO: Make DefaultTrackSelector.SelectionOverride final when this is removed. + /** @deprecated Use {@link DefaultTrackSelector.SelectionOverride} */ + @Deprecated + public static final class SelectionOverride extends DefaultTrackSelector.SelectionOverride { - public final TrackSelection.Factory factory; - public final int groupIndex; - public final int[] tracks; - public final int length; - - /** - * @param factory A factory for creating selections from this override. - * @param groupIndex The overriding track group index. - * @param tracks The overriding track indices within the track group. - */ public SelectionOverride(TrackSelection.Factory factory, int groupIndex, int... tracks) { - this.factory = factory; - this.groupIndex = groupIndex; - this.tracks = tracks; - this.length = tracks.length; - } - - /** - * Creates an selection from this override. - * - * @param groups The track groups whose selection is being overridden. - * @return The selection. - */ - public TrackSelection createTrackSelection(TrackGroupArray groups) { - return factory.createTrackSelection(groups.get(groupIndex), tracks); - } - - /** - * 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; + super(factory, groupIndex, tracks); } } - private final SparseArray> selectionOverrides; - private final SparseBooleanArray rendererDisabledFlags; - private int tunnelingAudioSessionId; - private MappedTrackInfo currentMappedTrackInfo; - public MappingTrackSelector() { - selectionOverrides = new SparseArray<>(); - rendererDisabledFlags = new SparseBooleanArray(); - tunnelingAudioSessionId = C.AUDIO_SESSION_ID_UNSET; - } - /** * Returns the mapping information for the currently active track selection, or null if no * selection is currently active. @@ -361,158 +285,13 @@ public abstract class MappingTrackSelector extends TrackSelector { return currentMappedTrackInfo; } - /** - * 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. - */ - public final void setRendererDisabled(int rendererIndex, boolean disabled) { - if (rendererDisabledFlags.get(rendererIndex) == disabled) { - // The disabled flag is unchanged. - return; - } - rendererDisabledFlags.put(rendererIndex, disabled); - invalidate(); - } - - /** - * 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); - } - - /** - * Overrides the track selection for the renderer at the specified index. - * - * 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}. - * - * 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. - * - * 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. - */ - public final void setSelectionOverride(int rendererIndex, TrackGroupArray groups, - SelectionOverride override) { - Map 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; - } - overrides.put(groups, override); - invalidate(); - } - - /** - * 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 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. - */ - public final SelectionOverride getSelectionOverride(int rendererIndex, TrackGroupArray groups) { - Map overrides = selectionOverrides.get(rendererIndex); - return overrides != null ? overrides.get(groups) : null; - } - - /** - * 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. - */ - public final void clearSelectionOverride(int rendererIndex, TrackGroupArray groups) { - Map overrides = selectionOverrides.get(rendererIndex); - if (overrides == null || !overrides.containsKey(groups)) { - // Nothing to clear. - return; - } - overrides.remove(groups); - if (overrides.isEmpty()) { - selectionOverrides.remove(rendererIndex); - } - invalidate(); - } - - /** - * Clears all track selection overrides for the specified renderer. - * - * @param rendererIndex The renderer index. - */ - public final void clearSelectionOverrides(int rendererIndex) { - Map overrides = selectionOverrides.get(rendererIndex); - if (overrides == null || overrides.isEmpty()) { - // Nothing to clear. - return; - } - selectionOverrides.remove(rendererIndex); - invalidate(); - } - - /** - * Clears all track selection overrides for all renderers. - */ - public final void clearSelectionOverrides() { - if (selectionOverrides.size() == 0) { - // Nothing to clear. - return; - } - selectionOverrides.clear(); - invalidate(); - } - - /** - * 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. - */ - public void setTunnelingAudioSessionId(int tunnelingAudioSessionId) { - if (this.tunnelingAudioSessionId != tunnelingAudioSessionId) { - this.tunnelingAudioSessionId = tunnelingAudioSessionId; - invalidate(); - } - } - // TrackSelector implementation. + @Override + public final void onSelectionActivated(Object info) { + currentMappedTrackInfo = (MappedTrackInfo) info; + } + @Override public final TrackSelectorResult selectTracks(RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups) throws ExoPlaybackException { @@ -562,75 +341,33 @@ public abstract class MappingTrackSelector extends TrackSelector { TrackGroupArray unassociatedTrackGroupArray = new TrackGroupArray(Arrays.copyOf( rendererTrackGroups[rendererCapabilities.length], unassociatedTrackGroupCount)); - TrackSelection[] trackSelections = selectTracks(rendererCapabilities, rendererTrackGroupArrays, - rendererFormatSupports); - - // Apply track disabling and overriding. - for (int i = 0; i < rendererCapabilities.length; i++) { - if (rendererDisabledFlags.get(i)) { - trackSelections[i] = null; - } else { - TrackGroupArray rendererTrackGroup = rendererTrackGroupArrays[i]; - if (hasSelectionOverride(i, rendererTrackGroup)) { - SelectionOverride override = selectionOverrides.get(i).get(rendererTrackGroup); - trackSelections[i] = override == null ? null - : override.createTrackSelection(rendererTrackGroup); - } - } - } - - boolean[] rendererEnabled = determineEnabledRenderers(rendererCapabilities, trackSelections); - // Package up the track information and selections. - MappedTrackInfo mappedTrackInfo = new MappedTrackInfo(rendererTrackTypes, - rendererTrackGroupArrays, mixedMimeTypeAdaptationSupport, rendererFormatSupports, - unassociatedTrackGroupArray); + MappedTrackInfo mappedTrackInfo = + new MappedTrackInfo( + rendererTrackTypes, + rendererTrackGroupArrays, + mixedMimeTypeAdaptationSupport, + rendererFormatSupports, + unassociatedTrackGroupArray); - // Initialize the renderer configurations to the default configuration for all renderers with - // selections, and null otherwise. - RendererConfiguration[] rendererConfigurations = - new RendererConfiguration[rendererCapabilities.length]; - for (int i = 0; i < rendererCapabilities.length; i++) { - rendererConfigurations[i] = rendererEnabled[i] ? RendererConfiguration.DEFAULT : null; - } - // Configure audio and video renderers to use tunneling if appropriate. - maybeConfigureRenderersForTunneling(rendererCapabilities, rendererTrackGroupArrays, - rendererFormatSupports, rendererConfigurations, trackSelections, tunnelingAudioSessionId); - - return new TrackSelectorResult(rendererConfigurations, trackSelections, mappedTrackInfo); - } - - private boolean[] determineEnabledRenderers(RendererCapabilities[] rendererCapabilities, - TrackSelection[] trackSelections) { - boolean[] rendererEnabled = new boolean[trackSelections.length]; - for (int i = 0; i < rendererEnabled.length; i++) { - boolean forceRendererDisabled = rendererDisabledFlags.get(i); - rendererEnabled[i] = !forceRendererDisabled - && (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE - || trackSelections[i] != null); - } - return rendererEnabled; - } - - @Override - public final void onSelectionActivated(Object info) { - currentMappedTrackInfo = (MappedTrackInfo) info; + Pair result = + selectTracks(rendererCapabilities, mappedTrackInfo); + return new TrackSelectorResult(result.first, result.second, mappedTrackInfo); } /** - * Given an array of renderer capabilities and the {@link TrackGroupArray}s mapped to each of - * them, provides a {@link TrackSelection} per renderer. + * Given mapped track information, returns a track selection and configuration for each renderer. * - * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which - * {@link TrackSelection}s are to be generated. - * @param rendererTrackGroupArrays The {@link TrackGroupArray}s mapped to each of the renderers. - * @param rendererFormatSupports The result of {@link RendererCapabilities#supportsFormat} for - * each mapped track, indexed by renderer index, track group index and track index (in that - * order). + * @param rendererCapabilities The {@link RendererCapabilities} of each renderer. + * @param mappedTrackInfo Mapped track information. + * @return A pair consisting of the track selections and configurations for each renderer. A null + * configuration indicates the renderer should be disabled, in which case the track selection + * will also be null. A track selection may also be null for a non-disabled renderer if {@link + * RendererCapabilities#getTrackType()} is {@link C#TRACK_TYPE_NONE}. * @throws ExoPlaybackException If an error occurs while selecting the tracks. */ - protected abstract TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, - TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + protected abstract Pair selectTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) throws ExoPlaybackException; /** @@ -712,92 +449,4 @@ public abstract class MappingTrackSelector extends TrackSelector { return mixedMimeTypeAdaptationSupport; } - /** - * 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 rendererCapabilities The {@link RendererCapabilities} of the renderers for which - * {@link TrackSelection}s are to be generated. - * @param rendererTrackGroupArrays An array of {@link TrackGroupArray}s where each entry - * corresponds to the renderer of equal index in {@code renderers}. - * @param rendererFormatSupports Maps every available track to a specific level of support as - * defined by the renderer {@code FORMAT_*} constants. - * @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( - RendererCapabilities[] rendererCapabilities, TrackGroupArray[] rendererTrackGroupArrays, - int[][][] rendererFormatSupports, RendererConfiguration[] rendererConfigurations, - 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 < rendererCapabilities.length; i++) { - int rendererType = rendererCapabilities[i].getTrackType(); - TrackSelection trackSelection = trackSelections[i]; - if ((rendererType == C.TRACK_TYPE_AUDIO || rendererType == C.TRACK_TYPE_VIDEO) - && trackSelection != null) { - if (rendererSupportsTunneling(rendererFormatSupports[i], rendererTrackGroupArrays[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 formatSupport The result of {@link RendererCapabilities#supportsFormat} 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(int[][] formatSupport, - TrackGroupArray trackGroups, TrackSelection selection) { - if (selection == null) { - return false; - } - int trackGroupIndex = trackGroups.indexOf(selection.getTrackGroup()); - for (int i = 0; i < selection.length(); i++) { - int trackFormatSupport = formatSupport[trackGroupIndex][selection.getIndexInTrackGroup(i)]; - if ((trackFormatSupport & RendererCapabilities.TUNNELING_SUPPORT_MASK) - != RendererCapabilities.TUNNELING_SUPPORTED) { - return false; - } - } - return true; - } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 90daf0ef03..ed91f6651c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -668,7 +668,7 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - List createdTrackSelections = trackSelector.getSelectedTrackSelections(); + List createdTrackSelections = trackSelector.getAllTrackSelections(); int numSelectionsEnabled = 0; // Assert that all tracks selection are disabled at the end of the playback. for (FakeTrackSelection trackSelection : createdTrackSelections) { @@ -676,9 +676,7 @@ public final class ExoPlayerTest { numSelectionsEnabled += trackSelection.enableCount; } // There are 2 renderers, and track selections are made once (1 period). - // Track selections are not reused, so there are 2 track selections made. assertThat(createdTrackSelections).hasSize(2); - // There should be 2 track selections enabled in total. assertThat(numSelectionsEnabled).isEqualTo(2); } @@ -699,7 +697,7 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - List createdTrackSelections = trackSelector.getSelectedTrackSelections(); + List createdTrackSelections = trackSelector.getAllTrackSelections(); int numSelectionsEnabled = 0; // Assert that all tracks selection are disabled at the end of the playback. for (FakeTrackSelection trackSelection : createdTrackSelections) { @@ -707,9 +705,7 @@ public final class ExoPlayerTest { numSelectionsEnabled += trackSelection.enableCount; } // There are 2 renderers, and track selections are made twice (2 periods). - // Track selections are not reused, so there are 4 track selections made. assertThat(createdTrackSelections).hasSize(4); - // There should be 4 track selections enabled in total. assertThat(numSelectionsEnabled).isEqualTo(4); } @@ -739,23 +735,21 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - List createdTrackSelections = trackSelector.getSelectedTrackSelections(); + List createdTrackSelections = trackSelector.getAllTrackSelections(); int numSelectionsEnabled = 0; // Assert that all tracks selection are disabled at the end of the playback. for (FakeTrackSelection trackSelection : createdTrackSelections) { assertThat(trackSelection.isEnabled).isFalse(); numSelectionsEnabled += trackSelection.enableCount; } - // There are 2 renderers, and track selections are made twice. - // Track selections are not reused, so there are 4 track selections made. + // There are 2 renderers, and track selections are made twice. The second time one renderer is + // disabled, so only one out of the two track selections is enabled. assertThat(createdTrackSelections).hasSize(4); - // Initially there are 2 track selections enabled. - // The second time one renderer is disabled, so only 1 track selection should be enabled. assertThat(numSelectionsEnabled).isEqualTo(3); } @Test - public void testAllActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreUsed() + public void testAllActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreReused() throws Exception { Timeline timeline = new FakeTimeline(/* windowCount= */ 1); MediaSource mediaSource = @@ -780,18 +774,17 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - List createdTrackSelections = trackSelector.getSelectedTrackSelections(); + List createdTrackSelections = trackSelector.getAllTrackSelections(); int numSelectionsEnabled = 0; // Assert that all tracks selection are disabled at the end of the playback. for (FakeTrackSelection trackSelection : createdTrackSelections) { assertThat(trackSelection.isEnabled).isFalse(); numSelectionsEnabled += trackSelection.enableCount; } - // There are 2 renderers, and track selections are made twice. - // TrackSelections are reused, so there are only 2 track selections made for 2 renderers. + // There are 2 renderers, and track selections are made twice. The second time one renderer is + // disabled, and the selector re-uses the previous selection for the enabled renderer. So we + // expect two track selections, one of which will have been enabled twice. assertThat(createdTrackSelections).hasSize(2); - // Initially there are 2 track selections enabled. - // The second time one renderer is disabled, so only 1 track selection should be enabled. assertThat(numSelectionsEnabled).isEqualTo(3); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java index 9bc4ae5382..ab6cea94ad 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java @@ -17,10 +17,12 @@ package com.google.android.exoplayer2.trackselection; import static com.google.common.truth.Truth.assertThat; +import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; +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.util.MimeTypes; @@ -90,25 +92,28 @@ public final class MappingTrackSelectorTest { } /** - * A {@link MappingTrackSelector} that returns a fixed result from - * {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])}. + * A {@link MappingTrackSelector} that stashes the {@link MappedTrackInfo} passed to {@link + * #selectTracks(RendererCapabilities[], MappedTrackInfo)}. */ private static final class FakeMappingTrackSelector extends MappingTrackSelector { - private TrackGroupArray[] lastRendererTrackGroupArrays; + private MappedTrackInfo lastMappedTrackInfo; @Override - protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, - TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + protected Pair selectTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) throws ExoPlaybackException { - lastRendererTrackGroupArrays = rendererTrackGroupArrays; - return new TrackSelection[rendererCapabilities.length]; + lastMappedTrackInfo = mappedTrackInfo; + return Pair.create( + new RendererConfiguration[rendererCapabilities.length], + new TrackSelection[rendererCapabilities.length]); } public void assertMappedTrackGroups(int rendererIndex, TrackGroup... expected) { - assertThat(lastRendererTrackGroupArrays[rendererIndex].length).isEqualTo(expected.length); + TrackGroupArray rendererTrackGroupArray = lastMappedTrackInfo.getTrackGroups(rendererIndex); + assertThat(rendererTrackGroupArray.length).isEqualTo(expected.length); for (int i = 0; i < expected.length; i++) { - assertThat(lastRendererTrackGroupArrays[rendererIndex].get(i)).isEqualTo(expected[i]); + assertThat(rendererTrackGroupArray.get(i)).isEqualTo(expected[i]); } } diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java index d5585f57a0..0c0a5879ca 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java @@ -394,25 +394,30 @@ public final class DashTestRunner { } @Override - protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, - TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + protected TrackSelection[] selectAllTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) throws ExoPlaybackException { Assertions.checkState(rendererCapabilities[VIDEO_RENDERER_INDEX].getTrackType() == C.TRACK_TYPE_VIDEO); Assertions.checkState(rendererCapabilities[AUDIO_RENDERER_INDEX].getTrackType() == C.TRACK_TYPE_AUDIO); - Assertions.checkState(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].length == 1); - Assertions.checkState(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].length == 1); + TrackGroupArray videoTrackGroups = mappedTrackInfo.getTrackGroups(VIDEO_RENDERER_INDEX); + TrackGroupArray audioTrackGroups = mappedTrackInfo.getTrackGroups(AUDIO_RENDERER_INDEX); + Assertions.checkState(videoTrackGroups.length == 1); + Assertions.checkState(audioTrackGroups.length == 1); TrackSelection[] selections = new TrackSelection[rendererCapabilities.length]; - selections[VIDEO_RENDERER_INDEX] = new RandomTrackSelection( - rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0), - getVideoTrackIndices(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0), - rendererFormatSupports[VIDEO_RENDERER_INDEX][0], videoFormatIds, - canIncludeAdditionalVideoFormats), - 0 /* seed */); - selections[AUDIO_RENDERER_INDEX] = new FixedTrackSelection( - rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), - getTrackIndex(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), audioFormatId)); + selections[VIDEO_RENDERER_INDEX] = + new RandomTrackSelection( + videoTrackGroups.get(0), + getVideoTrackIndices( + videoTrackGroups.get(0), + mappedTrackInfo.getRendererTrackSupport(VIDEO_RENDERER_INDEX)[0], + videoFormatIds, + canIncludeAdditionalVideoFormats), + 0 /* seed */); + selections[AUDIO_RENDERER_INDEX] = + new FixedTrackSelection( + audioTrackGroups.get(0), getTrackIndex(audioTrackGroups.get(0), audioFormatId)); includedAdditionalVideoFormats = selections[VIDEO_RENDERER_INDEX].length() > videoFormatIds.length; return selections; diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java index ecf93ba775..0e8045d489 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java @@ -21,14 +21,15 @@ import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelection; import java.util.ArrayList; import java.util.List; -/** A fake {@link DefaultTrackSelector} that returns {@link FakeTrackSelection}s. */ +/** A fake {@link MappingTrackSelector} that returns {@link FakeTrackSelection}s. */ public class FakeTrackSelector extends DefaultTrackSelector { - private final List selectedTrackSelections = new ArrayList<>(); + private final List trackSelections = new ArrayList<>(); private final boolean mayReuseTrackSelection; public FakeTrackSelector() { @@ -45,39 +46,35 @@ public class FakeTrackSelector extends DefaultTrackSelector { } @Override - protected TrackSelection[] selectTracks( - RendererCapabilities[] rendererCapabilities, - TrackGroupArray[] rendererTrackGroupArrays, - int[][][] rendererFormatSupports) + protected TrackSelection[] selectAllTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) throws ExoPlaybackException { - List resultList = new ArrayList<>(); - for (TrackGroupArray trackGroupArray : rendererTrackGroupArrays) { - TrackGroup trackGroup = trackGroupArray.get(0); - FakeTrackSelection trackSelectionForRenderer = reuseOrCreateTrackSelection(trackGroup); - resultList.add(trackSelectionForRenderer); + TrackSelection[] selections = new TrackSelection[mappedTrackInfo.length]; + for (int i = 0; i < mappedTrackInfo.length; i++) { + TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(i); + boolean hasTracks = trackGroupArray.length > 0; + selections[i] = hasTracks ? reuseOrCreateTrackSelection(trackGroupArray.get(0)) : null; } - return resultList.toArray(new TrackSelection[resultList.size()]); + return selections; } @NonNull private FakeTrackSelection reuseOrCreateTrackSelection(TrackGroup trackGroup) { - FakeTrackSelection trackSelectionForRenderer = null; if (mayReuseTrackSelection) { - for (FakeTrackSelection selectedTrackSelection : selectedTrackSelections) { - if (selectedTrackSelection.getTrackGroup().equals(trackGroup)) { - trackSelectionForRenderer = selectedTrackSelection; + for (FakeTrackSelection trackSelection : trackSelections) { + if (trackSelection.getTrackGroup().equals(trackGroup)) { + return trackSelection; } } } - if (trackSelectionForRenderer == null) { - trackSelectionForRenderer = new FakeTrackSelection(trackGroup); - selectedTrackSelections.add(trackSelectionForRenderer); - } - return trackSelectionForRenderer; + FakeTrackSelection trackSelection = new FakeTrackSelection(trackGroup); + trackSelections.add(trackSelection); + return trackSelection; } /** Returns list of all {@link FakeTrackSelection}s that this track selector has made so far. */ - public List getSelectedTrackSelections() { - return selectedTrackSelections; + public List getAllTrackSelections() { + return trackSelections; } + }
- * {@code - * MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); - * TrackGroupArray rendererTrackGroups = mappedTrackInfo == null ? null - * : mappedTrackInfo.getTrackGroups(rendererIndex);} - *
- * {@code - * trackSelector.setSelectionOverride(rendererIndex, rendererTrackGroups, - * new SelectionOverride(trackSelectionFactory, groupIndex, trackIndices));} - *
- * Note that an override applies only when the track groups available to the renderer match the - * {@link TrackGroupArray} for which the override was specified. Overrides can be cleared using - * the {@code clearSelectionOverride} methods. - * - *
- * 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}. - *
- * 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. - *
- * 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. - */ - public final void setSelectionOverride(int rendererIndex, TrackGroupArray groups, - SelectionOverride override) { - Map 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; - } - overrides.put(groups, override); - invalidate(); - } - - /** - * 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 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. - */ - public final SelectionOverride getSelectionOverride(int rendererIndex, TrackGroupArray groups) { - Map overrides = selectionOverrides.get(rendererIndex); - return overrides != null ? overrides.get(groups) : null; - } - - /** - * 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. - */ - public final void clearSelectionOverride(int rendererIndex, TrackGroupArray groups) { - Map overrides = selectionOverrides.get(rendererIndex); - if (overrides == null || !overrides.containsKey(groups)) { - // Nothing to clear. - return; - } - overrides.remove(groups); - if (overrides.isEmpty()) { - selectionOverrides.remove(rendererIndex); - } - invalidate(); - } - - /** - * Clears all track selection overrides for the specified renderer. - * - * @param rendererIndex The renderer index. - */ - public final void clearSelectionOverrides(int rendererIndex) { - Map overrides = selectionOverrides.get(rendererIndex); - if (overrides == null || overrides.isEmpty()) { - // Nothing to clear. - return; - } - selectionOverrides.remove(rendererIndex); - invalidate(); - } - - /** - * Clears all track selection overrides for all renderers. - */ - public final void clearSelectionOverrides() { - if (selectionOverrides.size() == 0) { - // Nothing to clear. - return; - } - selectionOverrides.clear(); - invalidate(); - } - - /** - * 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. - */ - public void setTunnelingAudioSessionId(int tunnelingAudioSessionId) { - if (this.tunnelingAudioSessionId != tunnelingAudioSessionId) { - this.tunnelingAudioSessionId = tunnelingAudioSessionId; - invalidate(); - } - } - // TrackSelector implementation. + @Override + public final void onSelectionActivated(Object info) { + currentMappedTrackInfo = (MappedTrackInfo) info; + } + @Override public final TrackSelectorResult selectTracks(RendererCapabilities[] rendererCapabilities, TrackGroupArray trackGroups) throws ExoPlaybackException { @@ -562,75 +341,33 @@ public abstract class MappingTrackSelector extends TrackSelector { TrackGroupArray unassociatedTrackGroupArray = new TrackGroupArray(Arrays.copyOf( rendererTrackGroups[rendererCapabilities.length], unassociatedTrackGroupCount)); - TrackSelection[] trackSelections = selectTracks(rendererCapabilities, rendererTrackGroupArrays, - rendererFormatSupports); - - // Apply track disabling and overriding. - for (int i = 0; i < rendererCapabilities.length; i++) { - if (rendererDisabledFlags.get(i)) { - trackSelections[i] = null; - } else { - TrackGroupArray rendererTrackGroup = rendererTrackGroupArrays[i]; - if (hasSelectionOverride(i, rendererTrackGroup)) { - SelectionOverride override = selectionOverrides.get(i).get(rendererTrackGroup); - trackSelections[i] = override == null ? null - : override.createTrackSelection(rendererTrackGroup); - } - } - } - - boolean[] rendererEnabled = determineEnabledRenderers(rendererCapabilities, trackSelections); - // Package up the track information and selections. - MappedTrackInfo mappedTrackInfo = new MappedTrackInfo(rendererTrackTypes, - rendererTrackGroupArrays, mixedMimeTypeAdaptationSupport, rendererFormatSupports, - unassociatedTrackGroupArray); + MappedTrackInfo mappedTrackInfo = + new MappedTrackInfo( + rendererTrackTypes, + rendererTrackGroupArrays, + mixedMimeTypeAdaptationSupport, + rendererFormatSupports, + unassociatedTrackGroupArray); - // Initialize the renderer configurations to the default configuration for all renderers with - // selections, and null otherwise. - RendererConfiguration[] rendererConfigurations = - new RendererConfiguration[rendererCapabilities.length]; - for (int i = 0; i < rendererCapabilities.length; i++) { - rendererConfigurations[i] = rendererEnabled[i] ? RendererConfiguration.DEFAULT : null; - } - // Configure audio and video renderers to use tunneling if appropriate. - maybeConfigureRenderersForTunneling(rendererCapabilities, rendererTrackGroupArrays, - rendererFormatSupports, rendererConfigurations, trackSelections, tunnelingAudioSessionId); - - return new TrackSelectorResult(rendererConfigurations, trackSelections, mappedTrackInfo); - } - - private boolean[] determineEnabledRenderers(RendererCapabilities[] rendererCapabilities, - TrackSelection[] trackSelections) { - boolean[] rendererEnabled = new boolean[trackSelections.length]; - for (int i = 0; i < rendererEnabled.length; i++) { - boolean forceRendererDisabled = rendererDisabledFlags.get(i); - rendererEnabled[i] = !forceRendererDisabled - && (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE - || trackSelections[i] != null); - } - return rendererEnabled; - } - - @Override - public final void onSelectionActivated(Object info) { - currentMappedTrackInfo = (MappedTrackInfo) info; + Pair result = + selectTracks(rendererCapabilities, mappedTrackInfo); + return new TrackSelectorResult(result.first, result.second, mappedTrackInfo); } /** - * Given an array of renderer capabilities and the {@link TrackGroupArray}s mapped to each of - * them, provides a {@link TrackSelection} per renderer. + * Given mapped track information, returns a track selection and configuration for each renderer. * - * @param rendererCapabilities The {@link RendererCapabilities} of the renderers for which - * {@link TrackSelection}s are to be generated. - * @param rendererTrackGroupArrays The {@link TrackGroupArray}s mapped to each of the renderers. - * @param rendererFormatSupports The result of {@link RendererCapabilities#supportsFormat} for - * each mapped track, indexed by renderer index, track group index and track index (in that - * order). + * @param rendererCapabilities The {@link RendererCapabilities} of each renderer. + * @param mappedTrackInfo Mapped track information. + * @return A pair consisting of the track selections and configurations for each renderer. A null + * configuration indicates the renderer should be disabled, in which case the track selection + * will also be null. A track selection may also be null for a non-disabled renderer if {@link + * RendererCapabilities#getTrackType()} is {@link C#TRACK_TYPE_NONE}. * @throws ExoPlaybackException If an error occurs while selecting the tracks. */ - protected abstract TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, - TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + protected abstract Pair selectTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) throws ExoPlaybackException; /** @@ -712,92 +449,4 @@ public abstract class MappingTrackSelector extends TrackSelector { return mixedMimeTypeAdaptationSupport; } - /** - * 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 rendererCapabilities The {@link RendererCapabilities} of the renderers for which - * {@link TrackSelection}s are to be generated. - * @param rendererTrackGroupArrays An array of {@link TrackGroupArray}s where each entry - * corresponds to the renderer of equal index in {@code renderers}. - * @param rendererFormatSupports Maps every available track to a specific level of support as - * defined by the renderer {@code FORMAT_*} constants. - * @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( - RendererCapabilities[] rendererCapabilities, TrackGroupArray[] rendererTrackGroupArrays, - int[][][] rendererFormatSupports, RendererConfiguration[] rendererConfigurations, - 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 < rendererCapabilities.length; i++) { - int rendererType = rendererCapabilities[i].getTrackType(); - TrackSelection trackSelection = trackSelections[i]; - if ((rendererType == C.TRACK_TYPE_AUDIO || rendererType == C.TRACK_TYPE_VIDEO) - && trackSelection != null) { - if (rendererSupportsTunneling(rendererFormatSupports[i], rendererTrackGroupArrays[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 formatSupport The result of {@link RendererCapabilities#supportsFormat} 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(int[][] formatSupport, - TrackGroupArray trackGroups, TrackSelection selection) { - if (selection == null) { - return false; - } - int trackGroupIndex = trackGroups.indexOf(selection.getTrackGroup()); - for (int i = 0; i < selection.length(); i++) { - int trackFormatSupport = formatSupport[trackGroupIndex][selection.getIndexInTrackGroup(i)]; - if ((trackFormatSupport & RendererCapabilities.TUNNELING_SUPPORT_MASK) - != RendererCapabilities.TUNNELING_SUPPORTED) { - return false; - } - } - return true; - } - } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java index 90daf0ef03..ed91f6651c 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/ExoPlayerTest.java @@ -668,7 +668,7 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - List createdTrackSelections = trackSelector.getSelectedTrackSelections(); + List createdTrackSelections = trackSelector.getAllTrackSelections(); int numSelectionsEnabled = 0; // Assert that all tracks selection are disabled at the end of the playback. for (FakeTrackSelection trackSelection : createdTrackSelections) { @@ -676,9 +676,7 @@ public final class ExoPlayerTest { numSelectionsEnabled += trackSelection.enableCount; } // There are 2 renderers, and track selections are made once (1 period). - // Track selections are not reused, so there are 2 track selections made. assertThat(createdTrackSelections).hasSize(2); - // There should be 2 track selections enabled in total. assertThat(numSelectionsEnabled).isEqualTo(2); } @@ -699,7 +697,7 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - List createdTrackSelections = trackSelector.getSelectedTrackSelections(); + List createdTrackSelections = trackSelector.getAllTrackSelections(); int numSelectionsEnabled = 0; // Assert that all tracks selection are disabled at the end of the playback. for (FakeTrackSelection trackSelection : createdTrackSelections) { @@ -707,9 +705,7 @@ public final class ExoPlayerTest { numSelectionsEnabled += trackSelection.enableCount; } // There are 2 renderers, and track selections are made twice (2 periods). - // Track selections are not reused, so there are 4 track selections made. assertThat(createdTrackSelections).hasSize(4); - // There should be 4 track selections enabled in total. assertThat(numSelectionsEnabled).isEqualTo(4); } @@ -739,23 +735,21 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - List createdTrackSelections = trackSelector.getSelectedTrackSelections(); + List createdTrackSelections = trackSelector.getAllTrackSelections(); int numSelectionsEnabled = 0; // Assert that all tracks selection are disabled at the end of the playback. for (FakeTrackSelection trackSelection : createdTrackSelections) { assertThat(trackSelection.isEnabled).isFalse(); numSelectionsEnabled += trackSelection.enableCount; } - // There are 2 renderers, and track selections are made twice. - // Track selections are not reused, so there are 4 track selections made. + // There are 2 renderers, and track selections are made twice. The second time one renderer is + // disabled, so only one out of the two track selections is enabled. assertThat(createdTrackSelections).hasSize(4); - // Initially there are 2 track selections enabled. - // The second time one renderer is disabled, so only 1 track selection should be enabled. assertThat(numSelectionsEnabled).isEqualTo(3); } @Test - public void testAllActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreUsed() + public void testAllActivatedTrackSelectionAreReleasedWhenTrackSelectionsAreReused() throws Exception { Timeline timeline = new FakeTimeline(/* windowCount= */ 1); MediaSource mediaSource = @@ -780,18 +774,17 @@ public final class ExoPlayerTest { .start() .blockUntilEnded(TIMEOUT_MS); - List createdTrackSelections = trackSelector.getSelectedTrackSelections(); + List createdTrackSelections = trackSelector.getAllTrackSelections(); int numSelectionsEnabled = 0; // Assert that all tracks selection are disabled at the end of the playback. for (FakeTrackSelection trackSelection : createdTrackSelections) { assertThat(trackSelection.isEnabled).isFalse(); numSelectionsEnabled += trackSelection.enableCount; } - // There are 2 renderers, and track selections are made twice. - // TrackSelections are reused, so there are only 2 track selections made for 2 renderers. + // There are 2 renderers, and track selections are made twice. The second time one renderer is + // disabled, and the selector re-uses the previous selection for the enabled renderer. So we + // expect two track selections, one of which will have been enabled twice. assertThat(createdTrackSelections).hasSize(2); - // Initially there are 2 track selections enabled. - // The second time one renderer is disabled, so only 1 track selection should be enabled. assertThat(numSelectionsEnabled).isEqualTo(3); } diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java index 9bc4ae5382..ab6cea94ad 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/MappingTrackSelectorTest.java @@ -17,10 +17,12 @@ package com.google.android.exoplayer2.trackselection; import static com.google.common.truth.Truth.assertThat; +import android.util.Pair; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.RendererCapabilities; +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.util.MimeTypes; @@ -90,25 +92,28 @@ public final class MappingTrackSelectorTest { } /** - * A {@link MappingTrackSelector} that returns a fixed result from - * {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])}. + * A {@link MappingTrackSelector} that stashes the {@link MappedTrackInfo} passed to {@link + * #selectTracks(RendererCapabilities[], MappedTrackInfo)}. */ private static final class FakeMappingTrackSelector extends MappingTrackSelector { - private TrackGroupArray[] lastRendererTrackGroupArrays; + private MappedTrackInfo lastMappedTrackInfo; @Override - protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, - TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + protected Pair selectTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) throws ExoPlaybackException { - lastRendererTrackGroupArrays = rendererTrackGroupArrays; - return new TrackSelection[rendererCapabilities.length]; + lastMappedTrackInfo = mappedTrackInfo; + return Pair.create( + new RendererConfiguration[rendererCapabilities.length], + new TrackSelection[rendererCapabilities.length]); } public void assertMappedTrackGroups(int rendererIndex, TrackGroup... expected) { - assertThat(lastRendererTrackGroupArrays[rendererIndex].length).isEqualTo(expected.length); + TrackGroupArray rendererTrackGroupArray = lastMappedTrackInfo.getTrackGroups(rendererIndex); + assertThat(rendererTrackGroupArray.length).isEqualTo(expected.length); for (int i = 0; i < expected.length; i++) { - assertThat(lastRendererTrackGroupArrays[rendererIndex].get(i)).isEqualTo(expected[i]); + assertThat(rendererTrackGroupArray.get(i)).isEqualTo(expected[i]); } } diff --git a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java index d5585f57a0..0c0a5879ca 100644 --- a/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java +++ b/playbacktests/src/androidTest/java/com/google/android/exoplayer2/playbacktests/gts/DashTestRunner.java @@ -394,25 +394,30 @@ public final class DashTestRunner { } @Override - protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities, - TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports) + protected TrackSelection[] selectAllTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) throws ExoPlaybackException { Assertions.checkState(rendererCapabilities[VIDEO_RENDERER_INDEX].getTrackType() == C.TRACK_TYPE_VIDEO); Assertions.checkState(rendererCapabilities[AUDIO_RENDERER_INDEX].getTrackType() == C.TRACK_TYPE_AUDIO); - Assertions.checkState(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].length == 1); - Assertions.checkState(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].length == 1); + TrackGroupArray videoTrackGroups = mappedTrackInfo.getTrackGroups(VIDEO_RENDERER_INDEX); + TrackGroupArray audioTrackGroups = mappedTrackInfo.getTrackGroups(AUDIO_RENDERER_INDEX); + Assertions.checkState(videoTrackGroups.length == 1); + Assertions.checkState(audioTrackGroups.length == 1); TrackSelection[] selections = new TrackSelection[rendererCapabilities.length]; - selections[VIDEO_RENDERER_INDEX] = new RandomTrackSelection( - rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0), - getVideoTrackIndices(rendererTrackGroupArrays[VIDEO_RENDERER_INDEX].get(0), - rendererFormatSupports[VIDEO_RENDERER_INDEX][0], videoFormatIds, - canIncludeAdditionalVideoFormats), - 0 /* seed */); - selections[AUDIO_RENDERER_INDEX] = new FixedTrackSelection( - rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), - getTrackIndex(rendererTrackGroupArrays[AUDIO_RENDERER_INDEX].get(0), audioFormatId)); + selections[VIDEO_RENDERER_INDEX] = + new RandomTrackSelection( + videoTrackGroups.get(0), + getVideoTrackIndices( + videoTrackGroups.get(0), + mappedTrackInfo.getRendererTrackSupport(VIDEO_RENDERER_INDEX)[0], + videoFormatIds, + canIncludeAdditionalVideoFormats), + 0 /* seed */); + selections[AUDIO_RENDERER_INDEX] = + new FixedTrackSelection( + audioTrackGroups.get(0), getTrackIndex(audioTrackGroups.get(0), audioFormatId)); includedAdditionalVideoFormats = selections[VIDEO_RENDERER_INDEX].length() > videoFormatIds.length; return selections; diff --git a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java index ecf93ba775..0e8045d489 100644 --- a/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java +++ b/testutils_robolectric/src/main/java/com/google/android/exoplayer2/testutil/FakeTrackSelector.java @@ -21,14 +21,15 @@ import com.google.android.exoplayer2.RendererCapabilities; import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; +import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelection; import java.util.ArrayList; import java.util.List; -/** A fake {@link DefaultTrackSelector} that returns {@link FakeTrackSelection}s. */ +/** A fake {@link MappingTrackSelector} that returns {@link FakeTrackSelection}s. */ public class FakeTrackSelector extends DefaultTrackSelector { - private final List selectedTrackSelections = new ArrayList<>(); + private final List trackSelections = new ArrayList<>(); private final boolean mayReuseTrackSelection; public FakeTrackSelector() { @@ -45,39 +46,35 @@ public class FakeTrackSelector extends DefaultTrackSelector { } @Override - protected TrackSelection[] selectTracks( - RendererCapabilities[] rendererCapabilities, - TrackGroupArray[] rendererTrackGroupArrays, - int[][][] rendererFormatSupports) + protected TrackSelection[] selectAllTracks( + RendererCapabilities[] rendererCapabilities, MappedTrackInfo mappedTrackInfo) throws ExoPlaybackException { - List resultList = new ArrayList<>(); - for (TrackGroupArray trackGroupArray : rendererTrackGroupArrays) { - TrackGroup trackGroup = trackGroupArray.get(0); - FakeTrackSelection trackSelectionForRenderer = reuseOrCreateTrackSelection(trackGroup); - resultList.add(trackSelectionForRenderer); + TrackSelection[] selections = new TrackSelection[mappedTrackInfo.length]; + for (int i = 0; i < mappedTrackInfo.length; i++) { + TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(i); + boolean hasTracks = trackGroupArray.length > 0; + selections[i] = hasTracks ? reuseOrCreateTrackSelection(trackGroupArray.get(0)) : null; } - return resultList.toArray(new TrackSelection[resultList.size()]); + return selections; } @NonNull private FakeTrackSelection reuseOrCreateTrackSelection(TrackGroup trackGroup) { - FakeTrackSelection trackSelectionForRenderer = null; if (mayReuseTrackSelection) { - for (FakeTrackSelection selectedTrackSelection : selectedTrackSelections) { - if (selectedTrackSelection.getTrackGroup().equals(trackGroup)) { - trackSelectionForRenderer = selectedTrackSelection; + for (FakeTrackSelection trackSelection : trackSelections) { + if (trackSelection.getTrackGroup().equals(trackGroup)) { + return trackSelection; } } } - if (trackSelectionForRenderer == null) { - trackSelectionForRenderer = new FakeTrackSelection(trackGroup); - selectedTrackSelections.add(trackSelectionForRenderer); - } - return trackSelectionForRenderer; + FakeTrackSelection trackSelection = new FakeTrackSelection(trackGroup); + trackSelections.add(trackSelection); + return trackSelection; } /** Returns list of all {@link FakeTrackSelection}s that this track selector has made so far. */ - public List getSelectedTrackSelections() { - return selectedTrackSelections; + public List getAllTrackSelections() { + return trackSelections; } + }