Add fail-early checks for TrackSelectorResult correctness

The two arrays need to have the same length and the selection
must match in their nullness (unless for TYPE_NONE
renderers). Clarify this more clearly in the docs and add
new asssertions for it. This avoids that the player is failing
in obscure ways much later.

Issue: androidx/media#1473
#cherrypick
PiperOrigin-RevId: 646086833
This commit is contained in:
tonihei 2024-06-24 07:27:32 -07:00 committed by Copybara-Service
parent bb568b5150
commit 71ef848ec3
3 changed files with 23 additions and 6 deletions

View File

@ -15,6 +15,7 @@
*/ */
package androidx.media3.exoplayer; package androidx.media3.exoplayer;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.exoplayer.MediaPeriodQueue.areDurationsCompatible; import static androidx.media3.exoplayer.MediaPeriodQueue.areDurationsCompatible;
import static java.lang.Math.max; import static java.lang.Math.max;
@ -22,7 +23,6 @@ import androidx.annotation.Nullable;
import androidx.media3.common.C; import androidx.media3.common.C;
import androidx.media3.common.Format; import androidx.media3.common.Format;
import androidx.media3.common.Timeline; import androidx.media3.common.Timeline;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Log; import androidx.media3.common.util.Log;
import androidx.media3.common.util.NullableType; import androidx.media3.common.util.NullableType;
import androidx.media3.exoplayer.source.ClippingMediaPeriod; import androidx.media3.exoplayer.source.ClippingMediaPeriod;
@ -212,7 +212,7 @@ import androidx.media3.exoplayer.upstream.Allocator;
* @param rendererPositionUs The playing position in renderer time, in microseconds. * @param rendererPositionUs The playing position in renderer time, in microseconds.
*/ */
public void reevaluateBuffer(long rendererPositionUs) { public void reevaluateBuffer(long rendererPositionUs) {
Assertions.checkState(isLoadingMediaPeriod()); checkState(isLoadingMediaPeriod());
if (prepared) { if (prepared) {
mediaPeriod.reevaluateBuffer(toPeriodTime(rendererPositionUs)); mediaPeriod.reevaluateBuffer(toPeriodTime(rendererPositionUs));
} }
@ -230,7 +230,7 @@ import androidx.media3.exoplayer.upstream.Allocator;
*/ */
public void continueLoading( public void continueLoading(
long rendererPositionUs, float playbackSpeed, long lastRebufferRealtimeMs) { long rendererPositionUs, float playbackSpeed, long lastRebufferRealtimeMs) {
Assertions.checkState(isLoadingMediaPeriod()); checkState(isLoadingMediaPeriod());
long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs); long loadingPeriodPositionUs = toPeriodTime(rendererPositionUs);
mediaPeriod.continueLoading( mediaPeriod.continueLoading(
new LoadingInfo.Builder() new LoadingInfo.Builder()
@ -255,6 +255,15 @@ import androidx.media3.exoplayer.upstream.Allocator;
throws ExoPlaybackException { throws ExoPlaybackException {
TrackSelectorResult selectorResult = TrackSelectorResult selectorResult =
trackSelector.selectTracks(rendererCapabilities, getTrackGroups(), info.id, timeline); trackSelector.selectTracks(rendererCapabilities, getTrackGroups(), info.id, timeline);
for (int i = 0; i < selectorResult.length; i++) {
if (selectorResult.isRendererEnabled(i)) {
checkState(
selectorResult.selections[i] != null
|| rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE);
} else {
checkState(selectorResult.selections[i] == null);
}
}
for (ExoTrackSelection trackSelection : selectorResult.selections) { for (ExoTrackSelection trackSelection : selectorResult.selections) {
if (trackSelection != null) { if (trackSelection != null) {
trackSelection.onPlaybackSpeed(playbackSpeed); trackSelection.onPlaybackSpeed(playbackSpeed);
@ -324,13 +333,13 @@ import androidx.media3.exoplayer.upstream.Allocator;
hasEnabledTracks = false; hasEnabledTracks = false;
for (int i = 0; i < sampleStreams.length; i++) { for (int i = 0; i < sampleStreams.length; i++) {
if (sampleStreams[i] != null) { if (sampleStreams[i] != null) {
Assertions.checkState(newTrackSelectorResult.isRendererEnabled(i)); checkState(newTrackSelectorResult.isRendererEnabled(i));
// hasEnabledTracks should be true only when non-empty streams exists. // hasEnabledTracks should be true only when non-empty streams exists.
if (rendererCapabilities[i].getTrackType() != C.TRACK_TYPE_NONE) { if (rendererCapabilities[i].getTrackType() != C.TRACK_TYPE_NONE) {
hasEnabledTracks = true; hasEnabledTracks = true;
} }
} else { } else {
Assertions.checkState(newTrackSelectorResult.selections[i] == null); checkState(newTrackSelectorResult.selections[i] == null);
} }
} }
return positionUs; return positionUs;

View File

@ -450,7 +450,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
* @param timeline The {@link Timeline} holding the period for which tracks are to be selected. * @param timeline The {@link Timeline} holding the period for which tracks are to be selected.
* @return A pair consisting of the track selections and configurations for each renderer. A null * @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 * 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 * must 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}. * RendererCapabilities#getTrackType()} is {@link C#TRACK_TYPE_NONE}.
* @throws ExoPlaybackException If an error occurs while selecting the tracks. * @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/ */

View File

@ -15,11 +15,15 @@
*/ */
package androidx.media3.exoplayer.trackselection; package androidx.media3.exoplayer.trackselection;
import static androidx.media3.common.util.Assertions.checkArgument;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Tracks; import androidx.media3.common.Tracks;
import androidx.media3.common.util.NullableType; import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util; import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.RendererCapabilities;
import androidx.media3.exoplayer.RendererConfiguration; import androidx.media3.exoplayer.RendererConfiguration;
/** The result of a {@link TrackSelector} operation. */ /** The result of a {@link TrackSelector} operation. */
@ -69,6 +73,9 @@ public final class TrackSelectorResult {
* @param rendererConfigurations A {@link RendererConfiguration} for each renderer. A null entry * @param rendererConfigurations A {@link RendererConfiguration} for each renderer. A null entry
* indicates the corresponding renderer should be disabled. * indicates the corresponding renderer should be disabled.
* @param selections A {@link ExoTrackSelection} array containing the selection for each renderer. * @param selections A {@link ExoTrackSelection} array containing the selection for each renderer.
* If the renderer is disabled with a null {@link RendererConfiguration}, then it must have a
* null selection too. It can also have a null selection if it has a {@linkplain
* RendererCapabilities#getTrackType() track type} of {@link C#TRACK_TYPE_NONE}.
* @param tracks Description of the available tracks and which one were selected. * @param tracks Description of the available tracks and which one were selected.
* @param info An opaque object that will be returned to {@link * @param info An opaque object that will be returned to {@link
* TrackSelector#onSelectionActivated(Object)} should the selection be activated. May be * TrackSelector#onSelectionActivated(Object)} should the selection be activated. May be
@ -79,6 +86,7 @@ public final class TrackSelectorResult {
@NullableType ExoTrackSelection[] selections, @NullableType ExoTrackSelection[] selections,
Tracks tracks, Tracks tracks,
@Nullable Object info) { @Nullable Object info) {
checkArgument(rendererConfigurations.length == selections.length);
this.rendererConfigurations = rendererConfigurations; this.rendererConfigurations = rendererConfigurations;
this.selections = selections.clone(); this.selections = selections.clone();
this.tracks = tracks; this.tracks = tracks;