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
This commit is contained in:
tonihei 2021-11-25 17:01:15 +00:00
parent a520e7f559
commit a7d7c7b73a
3 changed files with 141 additions and 3 deletions

View File

@ -79,6 +79,7 @@ public class TrackSelectionParameters implements Bundleable {
private int viewportHeight;
private boolean viewportOrientationMayChange;
private ImmutableList<String> preferredVideoMimeTypes;
private @C.RoleFlags int preferredVideoRoleFlags;
// Audio
private ImmutableList<String> preferredAudioLanguages;
private @C.RoleFlags int preferredAudioRoleFlags;
@ -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<String> preferredVideoMimeTypes;
/**
* The preferred {@link C.RoleFlags} for video tracks. {@code 0} selects the default track if
* there is one, or the first track if there's no default. The default value is {@code 0}.
*/
public final @C.RoleFlags int preferredVideoRoleFlags;
// Audio
/**
* The preferred languages for audio and forced text tracks as IETF BCP 47 conformant tags in
@ -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),

View File

@ -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<VideoTrackScore> {
@ -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 =

View File

@ -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 {