From a7d7c7b73a72e29cc63145caf9a7e3914e1f3558 Mon Sep 17 00:00:00 2001 From: tonihei Date: Thu, 25 Nov 2021 17:01:15 +0000 Subject: [PATCH] Add preferredVideoRoleFlags to TrackSelectionParameters. And also tweak existing role flag logic to strictly prefer perfect matches over partial matches. Caveat: Video role flags only supported for fixed track selections (same issue as Issue: google/ExoPlayer#9519). Issue: google/ExoPlayer#9402 PiperOrigin-RevId: 412292835 --- .../common/TrackSelectionParameters.java | 29 +++++++ .../trackselection/DefaultTrackSelector.java | 30 ++++++- .../DefaultTrackSelectorTest.java | 85 ++++++++++++++++++- 3 files changed, 141 insertions(+), 3 deletions(-) diff --git a/libraries/common/src/main/java/androidx/media3/common/TrackSelectionParameters.java b/libraries/common/src/main/java/androidx/media3/common/TrackSelectionParameters.java index c79bd720a6..4f7f6b8e8d 100644 --- a/libraries/common/src/main/java/androidx/media3/common/TrackSelectionParameters.java +++ b/libraries/common/src/main/java/androidx/media3/common/TrackSelectionParameters.java @@ -79,6 +79,7 @@ public class TrackSelectionParameters implements Bundleable { private int viewportHeight; private boolean viewportOrientationMayChange; private ImmutableList preferredVideoMimeTypes; + private @C.RoleFlags int preferredVideoRoleFlags; // Audio private ImmutableList preferredAudioLanguages; private @C.RoleFlags int preferredAudioRoleFlags; @@ -111,6 +112,7 @@ public class TrackSelectionParameters implements Bundleable { viewportHeight = Integer.MAX_VALUE; viewportOrientationMayChange = true; preferredVideoMimeTypes = ImmutableList.of(); + preferredVideoRoleFlags = 0; // Audio preferredAudioLanguages = ImmutableList.of(); preferredAudioRoleFlags = 0; @@ -185,6 +187,10 @@ public class TrackSelectionParameters implements Bundleable { firstNonNull( bundle.getStringArray(keyForField(FIELD_PREFERRED_VIDEO_MIMETYPES)), new String[0])); + preferredVideoRoleFlags = + bundle.getInt( + keyForField(FIELD_PREFERRED_VIDEO_ROLE_FLAGS), + DEFAULT_WITHOUT_CONTEXT.preferredVideoRoleFlags); // Audio String[] preferredAudioLanguages1 = firstNonNull( @@ -263,6 +269,7 @@ public class TrackSelectionParameters implements Bundleable { viewportHeight = parameters.viewportHeight; viewportOrientationMayChange = parameters.viewportOrientationMayChange; preferredVideoMimeTypes = parameters.preferredVideoMimeTypes; + preferredVideoRoleFlags = parameters.preferredVideoRoleFlags; // Audio preferredAudioLanguages = parameters.preferredAudioLanguages; preferredAudioRoleFlags = parameters.preferredAudioRoleFlags; @@ -444,6 +451,17 @@ public class TrackSelectionParameters implements Bundleable { return this; } + /** + * Sets the preferred {@link C.RoleFlags} for video tracks. + * + * @param preferredVideoRoleFlags Preferred video role flags. + * @return This builder. + */ + public Builder setPreferredVideoRoleFlags(@C.RoleFlags int preferredVideoRoleFlags) { + this.preferredVideoRoleFlags = preferredVideoRoleFlags; + return this; + } + // Audio /** @@ -775,6 +793,11 @@ public class TrackSelectionParameters implements Bundleable { * no preference. The default is an empty list. */ public final ImmutableList preferredVideoMimeTypes; + /** + * The preferred {@link C.RoleFlags} for video tracks. {@code 0} selects the default track if + * there is one, or the first track if there's no default. The default value is {@code 0}. + */ + public final @C.RoleFlags int preferredVideoRoleFlags; // Audio /** * The preferred languages for audio and forced text tracks as IETF BCP 47 conformant tags in @@ -859,6 +882,7 @@ public class TrackSelectionParameters implements Bundleable { this.viewportHeight = builder.viewportHeight; this.viewportOrientationMayChange = builder.viewportOrientationMayChange; this.preferredVideoMimeTypes = builder.preferredVideoMimeTypes; + this.preferredVideoRoleFlags = builder.preferredVideoRoleFlags; // Audio this.preferredAudioLanguages = builder.preferredAudioLanguages; this.preferredAudioRoleFlags = builder.preferredAudioRoleFlags; @@ -904,6 +928,7 @@ public class TrackSelectionParameters implements Bundleable { && viewportWidth == other.viewportWidth && viewportHeight == other.viewportHeight && preferredVideoMimeTypes.equals(other.preferredVideoMimeTypes) + && preferredVideoRoleFlags == other.preferredVideoRoleFlags // Audio && preferredAudioLanguages.equals(other.preferredAudioLanguages) && preferredAudioRoleFlags == other.preferredAudioRoleFlags @@ -936,6 +961,7 @@ public class TrackSelectionParameters implements Bundleable { result = 31 * result + viewportWidth; result = 31 * result + viewportHeight; result = 31 * result + preferredVideoMimeTypes.hashCode(); + result = 31 * result + preferredVideoRoleFlags; // Audio result = 31 * result + preferredAudioLanguages.hashCode(); result = 31 * result + preferredAudioRoleFlags; @@ -984,6 +1010,7 @@ public class TrackSelectionParameters implements Bundleable { FIELD_SELECTION_OVERRIDE_KEYS, FIELD_SELECTION_OVERRIDE_VALUES, FIELD_DISABLED_TRACK_TYPE, + FIELD_PREFERRED_VIDEO_ROLE_FLAGS }) private @interface FieldNumber {} @@ -1012,6 +1039,7 @@ public class TrackSelectionParameters implements Bundleable { private static final int FIELD_SELECTION_OVERRIDE_KEYS = 23; private static final int FIELD_SELECTION_OVERRIDE_VALUES = 24; private static final int FIELD_DISABLED_TRACK_TYPE = 25; + private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 26; @UnstableApi @Override @@ -1034,6 +1062,7 @@ public class TrackSelectionParameters implements Bundleable { bundle.putStringArray( keyForField(FIELD_PREFERRED_VIDEO_MIMETYPES), preferredVideoMimeTypes.toArray(new String[0])); + bundle.putInt(keyForField(FIELD_PREFERRED_VIDEO_ROLE_FLAGS), preferredVideoRoleFlags); // Audio bundle.putStringArray( keyForField(FIELD_PREFERRED_AUDIO_LANGUAGES), diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java index 48057a1f4a..757051ab49 100644 --- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java +++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelector.java @@ -27,6 +27,7 @@ import androidx.annotation.Nullable; import androidx.media3.common.Bundleable; import androidx.media3.common.C; import androidx.media3.common.C.FormatSupport; +import androidx.media3.common.C.RoleFlags; import androidx.media3.common.Format; import androidx.media3.common.Timeline; import androidx.media3.common.TrackGroup; @@ -373,6 +374,13 @@ public class DefaultTrackSelector extends MappingTrackSelector { return this; } + @Override + public DefaultTrackSelector.ParametersBuilder setPreferredVideoRoleFlags( + @RoleFlags int preferredVideoRoleFlags) { + super.setPreferredVideoRoleFlags(preferredVideoRoleFlags); + return this; + } + // Audio @Override @@ -2476,6 +2484,14 @@ public class DefaultTrackSelector extends MappingTrackSelector { } } + private static int getRoleFlagMatchScore(int trackRoleFlags, int preferredRoleFlags) { + if (trackRoleFlags != 0 && trackRoleFlags == preferredRoleFlags) { + // Prefer perfect match over partial matches. + return Integer.MAX_VALUE; + } + return Integer.bitCount(trackRoleFlags & preferredRoleFlags); + } + /** Represents how well a video track matches the selection {@link Parameters}. */ protected static final class VideoTrackScore implements Comparable { @@ -2491,6 +2507,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { private final int bitrate; private final int pixelCount; private final int preferredMimeTypeMatchIndex; + private final int preferredRoleFlagsScore; + private final boolean hasMainOrNoRoleFlag; public VideoTrackScore( Format format, @@ -2518,6 +2536,9 @@ public class DefaultTrackSelector extends MappingTrackSelector { isSupported(formatSupport, /* allowExceedsCapabilities= */ false); bitrate = format.bitrate; pixelCount = format.getPixelCount(); + preferredRoleFlagsScore = + getRoleFlagMatchScore(format.roleFlags, parameters.preferredVideoRoleFlags); + hasMainOrNoRoleFlag = format.roleFlags == 0 || (format.roleFlags & C.ROLE_FLAG_MAIN) != 0; int bestMimeTypeMatchIndex = Integer.MAX_VALUE; for (int i = 0; i < parameters.preferredVideoMimeTypes.size(); i++) { if (format.sampleMimeType != null @@ -2545,6 +2566,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { : FORMAT_VALUE_ORDERING.reverse(); return ComparisonChain.start() .compareFalseFirst(this.isWithinRendererCapabilities, other.isWithinRendererCapabilities) + .compare(this.preferredRoleFlagsScore, other.preferredRoleFlagsScore) + .compareFalseFirst(this.hasMainOrNoRoleFlag, other.hasMainOrNoRoleFlag) .compareFalseFirst(this.isWithinMaxConstraints, other.isWithinMaxConstraints) .compareFalseFirst(this.isWithinMinConstraints, other.isWithinMinConstraints) .compare( @@ -2576,6 +2599,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { private final int preferredLanguageScore; private final int preferredLanguageIndex; private final int preferredRoleFlagsScore; + private final boolean hasMainOrNoRoleFlag; private final int localeLanguageMatchIndex; private final int localeLanguageScore; private final boolean isDefaultSelectionFlag; @@ -2606,7 +2630,8 @@ public class DefaultTrackSelector extends MappingTrackSelector { preferredLanguageIndex = bestLanguageIndex; preferredLanguageScore = bestLanguageScore; preferredRoleFlagsScore = - Integer.bitCount(format.roleFlags & parameters.preferredAudioRoleFlags); + getRoleFlagMatchScore(format.roleFlags, parameters.preferredAudioRoleFlags); + hasMainOrNoRoleFlag = format.roleFlags == 0 || (format.roleFlags & C.ROLE_FLAG_MAIN) != 0; isDefaultSelectionFlag = (format.selectionFlags & C.SELECTION_FLAG_DEFAULT) != 0; channelCount = format.channelCount; sampleRate = format.sampleRate; @@ -2664,6 +2689,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { Ordering.natural().reverse()) .compare(this.preferredLanguageScore, other.preferredLanguageScore) .compare(this.preferredRoleFlagsScore, other.preferredRoleFlagsScore) + .compareFalseFirst(this.hasMainOrNoRoleFlag, other.hasMainOrNoRoleFlag) .compareFalseFirst(this.isWithinConstraints, other.isWithinConstraints) .compare( this.preferredMimeTypeMatchIndex, @@ -2740,7 +2766,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { preferredLanguageIndex = bestLanguageIndex; preferredLanguageScore = bestLanguageScore; preferredRoleFlagsScore = - Integer.bitCount(format.roleFlags & parameters.preferredTextRoleFlags); + getRoleFlagMatchScore(format.roleFlags, parameters.preferredTextRoleFlags); hasCaptionRoleFlags = (format.roleFlags & (C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND)) != 0; boolean selectedAudioLanguageUndetermined = diff --git a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java index ff2d0a01ba..d3edb5407f 100644 --- a/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java +++ b/libraries/exoplayer/src/test/java/androidx/media3/exoplayer/trackselection/DefaultTrackSelectorTest.java @@ -596,7 +596,7 @@ public final class DefaultTrackSelectorTest { } /** - * Tests that track selector will select audio track with the highest number of matching role + * Tests that track selector will select the audio track with the highest number of matching role * flags given by {@link Parameters}. */ @Test @@ -621,6 +621,17 @@ public final class DefaultTrackSelectorTest { periodId, TIMELINE); assertFixedSelection(result.selections[0], trackGroups, moreRoleFlags); + + // Also verify that exact match between parameters and tracks is preferred. + trackSelector.setParameters( + defaultParameters.buildUpon().setPreferredAudioRoleFlags(C.ROLE_FLAG_CAPTION)); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_AUDIO_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, + trackGroups, + periodId, + TIMELINE); + assertFixedSelection(result.selections[0], trackGroups, lessRoleFlags); } /** @@ -1281,6 +1292,45 @@ public final class DefaultTrackSelectorTest { assertFixedSelection(result.selections[1], trackGroups, german); } + /** + * Tests that track selector will select the text track with the highest number of matching role + * flags given by {@link Parameters}. + */ + @Test + public void selectTracks_withPreferredTextRoleFlags_selectPreferredTrack() throws Exception { + Format.Builder formatBuilder = TEXT_FORMAT.buildUpon(); + Format noRoleFlags = formatBuilder.build(); + Format lessRoleFlags = formatBuilder.setRoleFlags(C.ROLE_FLAG_CAPTION).build(); + Format moreRoleFlags = + formatBuilder + .setRoleFlags(C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_COMMENTARY | C.ROLE_FLAG_DUB) + .build(); + TrackGroupArray trackGroups = wrapFormats(noRoleFlags, moreRoleFlags, lessRoleFlags); + + trackSelector.setParameters( + defaultParameters + .buildUpon() + .setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_COMMENTARY)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_TEXT_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, + trackGroups, + periodId, + TIMELINE); + assertFixedSelection(result.selections[0], trackGroups, moreRoleFlags); + + // Also verify that exact match between parameters and tracks is preferred. + trackSelector.setParameters( + defaultParameters.buildUpon().setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION)); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {ALL_TEXT_FORMAT_SUPPORTED_RENDERER_CAPABILITIES}, + trackGroups, + periodId, + TIMELINE); + assertFixedSelection(result.selections[0], trackGroups, lessRoleFlags); + } + /** * Tests that track selector will select the lowest bitrate supported audio track when {@link * Parameters#forceLowestBitrate} is set. @@ -1811,6 +1861,39 @@ public final class DefaultTrackSelectorTest { assertFixedSelection(result.selections[0], trackGroups, formatAv1); } + /** + * Tests that track selector will select the video track with the highest number of matching role + * flags given by {@link Parameters}. + */ + @Test + public void selectTracks_withPreferredVideoRoleFlags_selectPreferredTrack() throws Exception { + Format.Builder formatBuilder = VIDEO_FORMAT.buildUpon(); + Format noRoleFlags = formatBuilder.build(); + Format lessRoleFlags = formatBuilder.setRoleFlags(C.ROLE_FLAG_CAPTION).build(); + Format moreRoleFlags = + formatBuilder + .setRoleFlags(C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_COMMENTARY | C.ROLE_FLAG_DUB) + .build(); + TrackGroupArray trackGroups = wrapFormats(noRoleFlags, moreRoleFlags, lessRoleFlags); + + trackSelector.setParameters( + defaultParameters + .buildUpon() + .setPreferredVideoRoleFlags(C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_COMMENTARY)); + TrackSelectorResult result = + trackSelector.selectTracks( + new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections[0], trackGroups, moreRoleFlags); + + // Also verify that exact match between parameters and tracks is preferred. + trackSelector.setParameters( + defaultParameters.buildUpon().setPreferredVideoRoleFlags(C.ROLE_FLAG_CAPTION)); + result = + trackSelector.selectTracks( + new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE); + assertFixedSelection(result.selections[0], trackGroups, lessRoleFlags); + } + @Test public void selectTracks_withPreferredAudioMimeTypes_selectsTrackWithPreferredMimeType() throws Exception {