Add preferred MIME type(s) to DefaultTrackSelector parameters.

This allows to set preferences based on MIME type for video and audio.
The MIME type preference is applied after other explicit preferences
and restrictions (e.g. language or max resolution), but before implicit
preferences like bitrate.

Issue: #8320
PiperOrigin-RevId: 350550543
This commit is contained in:
tonihei 2021-01-07 14:51:12 +00:00 committed by Ian Baker
parent 6e8af81ddc
commit c2d7fae535
3 changed files with 213 additions and 8 deletions

View File

@ -36,7 +36,7 @@
* Deprecate `HttpDataSource.Factory.getDefaultRequestProperties` and add
`HttpDataSource.Factory.setDefaultRequestProperties` instead.
* Fix playback issues after seeking during an ad
([#8349](https://github.com/google/ExoPlayer/issues/8349))
([#8349](https://github.com/google/ExoPlayer/issues/8349)).
* Add `DefaultHttpDataSource.Factory` and deprecate
`DefaultHttpDataSourceFactory`.
* Populate codecs string for H.264/AVC in MP4, Matroska and FLV streams to
@ -46,6 +46,8 @@
* Allow parallel adaptation for video and audio
([#5111](https://github.com/google/ExoPlayer/issues/5111)).
* Add option to specify multiple preferred audio or text languages.
* Add option to specify preferred MIME type(s) for video and audio
([#8320](https://github.com/google/ExoPlayer/issues/8320)).
* Forward `Timeline` and `MediaPeriodId` to `TrackSelection.Factory`.
* DASH:
* Support low-latency DASH playback (`availabilityTimeOffset` and

View File

@ -53,6 +53,7 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.checkerframework.checker.initialization.qual.UnderInitialization;
import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
/**
* A default {@link TrackSelector} suitable for most use cases. Track selections are made according
@ -183,6 +184,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private int viewportWidth;
private int viewportHeight;
private boolean viewportOrientationMayChange;
private ImmutableList<String> preferredVideoMimeTypes;
// Audio
private int maxAudioChannelCount;
private int maxAudioBitrate;
@ -190,6 +192,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private boolean allowAudioMixedMimeTypeAdaptiveness;
private boolean allowAudioMixedSampleRateAdaptiveness;
private boolean allowAudioMixedChannelCountAdaptiveness;
private ImmutableList<String> preferredAudioMimeTypes;
// General
private boolean forceLowestBitrate;
private boolean forceHighestSupportedBitrate;
@ -249,6 +252,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
viewportWidth = initialValues.viewportWidth;
viewportHeight = initialValues.viewportHeight;
viewportOrientationMayChange = initialValues.viewportOrientationMayChange;
preferredVideoMimeTypes = initialValues.preferredVideoMimeTypes;
// Audio
maxAudioChannelCount = initialValues.maxAudioChannelCount;
maxAudioBitrate = initialValues.maxAudioBitrate;
@ -257,6 +261,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
allowAudioMixedSampleRateAdaptiveness = initialValues.allowAudioMixedSampleRateAdaptiveness;
allowAudioMixedChannelCountAdaptiveness =
initialValues.allowAudioMixedChannelCountAdaptiveness;
preferredAudioMimeTypes = initialValues.preferredAudioMimeTypes;
// General
forceLowestBitrate = initialValues.forceLowestBitrate;
forceHighestSupportedBitrate = initialValues.forceHighestSupportedBitrate;
@ -447,6 +452,29 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return this;
}
/**
* Sets the preferred sample MIME type for video tracks.
*
* @param mimeType The preferred MIME type for video tracks, or {@code null} to clear a
* previously set preference.
* @return This builder.
*/
public ParametersBuilder setPreferredVideoMimeType(@Nullable String mimeType) {
return mimeType == null ? setPreferredVideoMimeTypes() : setPreferredVideoMimeTypes(mimeType);
}
/**
* Sets the preferred sample MIME types for video tracks.
*
* @param mimeTypes The preferred MIME types for video tracks in order of preference, or an
* empty list for no preference.
* @return This builder.
*/
public ParametersBuilder setPreferredVideoMimeTypes(String... mimeTypes) {
preferredVideoMimeTypes = ImmutableList.copyOf(mimeTypes);
return this;
}
// Audio
@Override
@ -542,6 +570,29 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return this;
}
/**
* Sets the preferred sample MIME type for audio tracks.
*
* @param mimeType The preferred MIME type for audio tracks, or {@code null} to clear a
* previously set preference.
* @return This builder.
*/
public ParametersBuilder setPreferredAudioMimeType(@Nullable String mimeType) {
return mimeType == null ? setPreferredAudioMimeTypes() : setPreferredAudioMimeTypes(mimeType);
}
/**
* Sets the preferred sample MIME types for audio tracks.
*
* @param mimeTypes The preferred MIME types for audio tracks in order of preference, or an
* empty list for no preference.
* @return This builder.
*/
public ParametersBuilder setPreferredAudioMimeTypes(String... mimeTypes) {
preferredAudioMimeTypes = ImmutableList.copyOf(mimeTypes);
return this;
}
// Text
@Override
@ -795,6 +846,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
viewportWidth,
viewportHeight,
viewportOrientationMayChange,
preferredVideoMimeTypes,
// Audio
preferredAudioLanguages,
maxAudioChannelCount,
@ -803,6 +855,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
allowAudioMixedMimeTypeAdaptiveness,
allowAudioMixedSampleRateAdaptiveness,
allowAudioMixedChannelCountAdaptiveness,
preferredAudioMimeTypes,
// Text
preferredTextLanguages,
preferredTextRoleFlags,
@ -818,6 +871,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
rendererDisabledFlags);
}
@EnsuresNonNull({"preferredVideoMimeTypes", "preferredAudioMimeTypes"})
private void setInitialValuesWithoutContext(@UnderInitialization ParametersBuilder this) {
// Video
maxVideoWidth = Integer.MAX_VALUE;
@ -830,6 +884,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
viewportWidth = Integer.MAX_VALUE;
viewportHeight = Integer.MAX_VALUE;
viewportOrientationMayChange = true;
preferredVideoMimeTypes = ImmutableList.of();
// Audio
maxAudioChannelCount = Integer.MAX_VALUE;
maxAudioBitrate = Integer.MAX_VALUE;
@ -837,6 +892,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
allowAudioMixedMimeTypeAdaptiveness = false;
allowAudioMixedSampleRateAdaptiveness = false;
allowAudioMixedChannelCountAdaptiveness = false;
preferredAudioMimeTypes = ImmutableList.of();
// General
forceLowestBitrate = false;
forceHighestSupportedBitrate = false;
@ -963,6 +1019,11 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* The default value is {@code true}.
*/
public final boolean viewportOrientationMayChange;
/**
* The preferred sample MIME types for video tracks in order of preference, or an empty list for
* no preference. The default is an empty list.
*/
public final ImmutableList<String> preferredVideoMimeTypes;
// Audio
/**
* Maximum allowed audio channel count. The default value is {@link Integer#MAX_VALUE} (i.e. no
@ -995,7 +1056,11 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* false}.
*/
public final boolean allowAudioMixedChannelCountAdaptiveness;
/**
* The preferred sample MIME types for audio tracks in order of preference, or an empty list for
* no preference. The default is an empty list.
*/
public final ImmutableList<String> preferredAudioMimeTypes;
// General
/**
* Whether to force selection of the single lowest bitrate audio and video tracks that comply
@ -1054,6 +1119,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
int viewportWidth,
int viewportHeight,
boolean viewportOrientationMayChange,
ImmutableList<String> preferredVideoMimeTypes,
// Audio
ImmutableList<String> preferredAudioLanguages,
int maxAudioChannelCount,
@ -1062,6 +1128,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
boolean allowAudioMixedMimeTypeAdaptiveness,
boolean allowAudioMixedSampleRateAdaptiveness,
boolean allowAudioMixedChannelCountAdaptiveness,
ImmutableList<String> preferredAudioMimeTypes,
// Text
ImmutableList<String> preferredTextLanguages,
@C.RoleFlags int preferredTextRoleFlags,
@ -1097,6 +1164,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
this.viewportWidth = viewportWidth;
this.viewportHeight = viewportHeight;
this.viewportOrientationMayChange = viewportOrientationMayChange;
this.preferredVideoMimeTypes = preferredVideoMimeTypes;
// Audio
this.maxAudioChannelCount = maxAudioChannelCount;
this.maxAudioBitrate = maxAudioBitrate;
@ -1104,6 +1172,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
this.allowAudioMixedMimeTypeAdaptiveness = allowAudioMixedMimeTypeAdaptiveness;
this.allowAudioMixedSampleRateAdaptiveness = allowAudioMixedSampleRateAdaptiveness;
this.allowAudioMixedChannelCountAdaptiveness = allowAudioMixedChannelCountAdaptiveness;
this.preferredAudioMimeTypes = preferredAudioMimeTypes;
// General
this.forceLowestBitrate = forceLowestBitrate;
this.forceHighestSupportedBitrate = forceHighestSupportedBitrate;
@ -1132,6 +1201,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
this.viewportWidth = in.readInt();
this.viewportHeight = in.readInt();
this.viewportOrientationMayChange = Util.readBoolean(in);
ArrayList<String> preferredVideoMimeTypes = new ArrayList<>();
in.readList(preferredVideoMimeTypes, /* loader= */ null);
this.preferredVideoMimeTypes = ImmutableList.copyOf(preferredVideoMimeTypes);
// Audio
this.maxAudioChannelCount = in.readInt();
this.maxAudioBitrate = in.readInt();
@ -1139,6 +1211,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
this.allowAudioMixedMimeTypeAdaptiveness = Util.readBoolean(in);
this.allowAudioMixedSampleRateAdaptiveness = Util.readBoolean(in);
this.allowAudioMixedChannelCountAdaptiveness = Util.readBoolean(in);
ArrayList<String> preferredAudioMimeTypes = new ArrayList<>();
in.readList(preferredAudioMimeTypes, /* loader= */ null);
this.preferredAudioMimeTypes = ImmutableList.copyOf(preferredAudioMimeTypes);
// General
this.forceLowestBitrate = Util.readBoolean(in);
this.forceHighestSupportedBitrate = Util.readBoolean(in);
@ -1218,6 +1293,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
&& viewportOrientationMayChange == other.viewportOrientationMayChange
&& viewportWidth == other.viewportWidth
&& viewportHeight == other.viewportHeight
&& preferredVideoMimeTypes.equals(other.preferredVideoMimeTypes)
// Audio
&& maxAudioChannelCount == other.maxAudioChannelCount
&& maxAudioBitrate == other.maxAudioBitrate
@ -1226,6 +1302,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
&& allowAudioMixedSampleRateAdaptiveness == other.allowAudioMixedSampleRateAdaptiveness
&& allowAudioMixedChannelCountAdaptiveness
== other.allowAudioMixedChannelCountAdaptiveness
&& preferredAudioMimeTypes.equals(other.preferredAudioMimeTypes)
// General
&& forceLowestBitrate == other.forceLowestBitrate
&& forceHighestSupportedBitrate == other.forceHighestSupportedBitrate
@ -1255,6 +1332,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
result = 31 * result + (viewportOrientationMayChange ? 1 : 0);
result = 31 * result + viewportWidth;
result = 31 * result + viewportHeight;
result = 31 * result + preferredVideoMimeTypes.hashCode();
// Audio
result = 31 * result + maxAudioChannelCount;
result = 31 * result + maxAudioBitrate;
@ -1262,6 +1340,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
result = 31 * result + (allowAudioMixedMimeTypeAdaptiveness ? 1 : 0);
result = 31 * result + (allowAudioMixedSampleRateAdaptiveness ? 1 : 0);
result = 31 * result + (allowAudioMixedChannelCountAdaptiveness ? 1 : 0);
result = 31 * result + preferredAudioMimeTypes.hashCode();
// General
result = 31 * result + (forceLowestBitrate ? 1 : 0);
result = 31 * result + (forceHighestSupportedBitrate ? 1 : 0);
@ -1297,6 +1376,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
dest.writeInt(viewportWidth);
dest.writeInt(viewportHeight);
Util.writeBoolean(dest, viewportOrientationMayChange);
dest.writeList(preferredVideoMimeTypes);
// Audio
dest.writeInt(maxAudioChannelCount);
dest.writeInt(maxAudioBitrate);
@ -1304,6 +1384,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
Util.writeBoolean(dest, allowAudioMixedMimeTypeAdaptiveness);
Util.writeBoolean(dest, allowAudioMixedSampleRateAdaptiveness);
Util.writeBoolean(dest, allowAudioMixedChannelCountAdaptiveness);
dest.writeList(preferredAudioMimeTypes);
// General
Util.writeBoolean(dest, forceLowestBitrate);
Util.writeBoolean(dest, forceHighestSupportedBitrate);
@ -2576,6 +2657,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private final boolean isWithinRendererCapabilities;
private final int bitrate;
private final int pixelCount;
private final int preferredMimeTypeMatchIndex;
public VideoTrackScore(
Format format,
@ -2603,6 +2685,15 @@ public class DefaultTrackSelector extends MappingTrackSelector {
isSupported(formatSupport, /* allowExceedsCapabilities= */ false);
bitrate = format.bitrate;
pixelCount = format.getPixelCount();
int bestMimeTypeMatchIndex = Integer.MAX_VALUE;
for (int i = 0; i < parameters.preferredVideoMimeTypes.size(); i++) {
if (format.sampleMimeType != null
&& format.sampleMimeType.equals(parameters.preferredVideoMimeTypes.get(i))) {
bestMimeTypeMatchIndex = i;
break;
}
}
preferredMimeTypeMatchIndex = bestMimeTypeMatchIndex;
}
@Override
@ -2623,6 +2714,10 @@ public class DefaultTrackSelector extends MappingTrackSelector {
.compareFalseFirst(this.isWithinRendererCapabilities, other.isWithinRendererCapabilities)
.compareFalseFirst(this.isWithinMaxConstraints, other.isWithinMaxConstraints)
.compareFalseFirst(this.isWithinMinConstraints, other.isWithinMinConstraints)
.compare(
this.preferredMimeTypeMatchIndex,
other.preferredMimeTypeMatchIndex,
Ordering.natural().reverse())
.compare(
this.bitrate,
other.bitrate,
@ -2653,6 +2748,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private final int channelCount;
private final int sampleRate;
private final int bitrate;
private final int preferredMimeTypeMatchIndex;
public AudioTrackScore(Format format, Parameters parameters, @Capabilities int formatSupport) {
this.parameters = parameters;
@ -2684,20 +2780,29 @@ public class DefaultTrackSelector extends MappingTrackSelector {
&& (format.channelCount == Format.NO_VALUE
|| format.channelCount <= parameters.maxAudioChannelCount);
String[] localeLanguages = Util.getSystemLanguageCodes();
int bestMatchIndex = Integer.MAX_VALUE;
int bestMatchScore = 0;
int bestLocaleMatchIndex = Integer.MAX_VALUE;
int bestLocaleMatchScore = 0;
for (int i = 0; i < localeLanguages.length; i++) {
int score =
getFormatLanguageScore(
format, localeLanguages[i], /* allowUndeterminedFormatLanguage= */ false);
if (score > 0) {
bestMatchIndex = i;
bestMatchScore = score;
bestLocaleMatchIndex = i;
bestLocaleMatchScore = score;
break;
}
}
localeLanguageMatchIndex = bestMatchIndex;
localeLanguageScore = bestMatchScore;
localeLanguageMatchIndex = bestLocaleMatchIndex;
localeLanguageScore = bestLocaleMatchScore;
int bestMimeTypeMatchIndex = Integer.MAX_VALUE;
for (int i = 0; i < parameters.preferredAudioMimeTypes.size(); i++) {
if (format.sampleMimeType != null
&& format.sampleMimeType.equals(parameters.preferredAudioMimeTypes.get(i))) {
bestMimeTypeMatchIndex = i;
break;
}
}
preferredMimeTypeMatchIndex = bestMimeTypeMatchIndex;
}
/**
@ -2723,6 +2828,10 @@ public class DefaultTrackSelector extends MappingTrackSelector {
Ordering.natural().reverse())
.compare(this.preferredLanguageScore, other.preferredLanguageScore)
.compareFalseFirst(this.isWithinConstraints, other.isWithinConstraints)
.compare(
this.preferredMimeTypeMatchIndex,
other.preferredMimeTypeMatchIndex,
Ordering.natural().reverse())
.compare(
this.bitrate,
other.bitrate,

View File

@ -1469,6 +1469,98 @@ public final class DefaultTrackSelectorTest {
assertFixedSelection(result.selections.get(1), trackGroups.get(1), /* expectedTrack= */ 1);
}
@Test
public void selectTracks_withPreferredVideoMimeTypes_selectsTrackWithPreferredMimeType()
throws Exception {
Format formatAv1 = new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_AV1).build();
Format formatVp9 = new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_VP9).build();
Format formatH264 = new Format.Builder().setSampleMimeType(MimeTypes.VIDEO_H264).build();
TrackGroupArray trackGroups = wrapFormats(formatAv1, formatVp9, formatH264);
trackSelector.setParameters(
trackSelector.buildUponParameters().setPreferredVideoMimeType(MimeTypes.VIDEO_VP9));
TrackSelectorResult result =
trackSelector.selectTracks(
new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
assertThat(result.length).isEqualTo(1);
assertFixedSelection(result.selections.get(0), trackGroups, formatVp9);
trackSelector.setParameters(
trackSelector
.buildUponParameters()
.setPreferredVideoMimeTypes(MimeTypes.VIDEO_VP9, MimeTypes.VIDEO_AV1));
result =
trackSelector.selectTracks(
new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
assertThat(result.length).isEqualTo(1);
assertFixedSelection(result.selections.get(0), trackGroups, formatVp9);
trackSelector.setParameters(
trackSelector
.buildUponParameters()
.setPreferredVideoMimeTypes(MimeTypes.VIDEO_DIVX, MimeTypes.VIDEO_H264));
result =
trackSelector.selectTracks(
new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
assertThat(result.length).isEqualTo(1);
assertFixedSelection(result.selections.get(0), trackGroups, formatH264);
// Select first in the list if no preference is specified.
trackSelector.setParameters(
trackSelector.buildUponParameters().setPreferredVideoMimeType(null));
result =
trackSelector.selectTracks(
new RendererCapabilities[] {VIDEO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
assertThat(result.length).isEqualTo(1);
assertFixedSelection(result.selections.get(0), trackGroups, formatAv1);
}
@Test
public void selectTracks_withPreferredAudioMimeTypes_selectsTrackWithPreferredMimeType()
throws Exception {
Format formatAac = new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AAC).build();
Format formatAc4 = new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AC4).build();
Format formatEAc3 = new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_E_AC3).build();
TrackGroupArray trackGroups = wrapFormats(formatAac, formatAc4, formatEAc3);
trackSelector.setParameters(
trackSelector.buildUponParameters().setPreferredAudioMimeType(MimeTypes.AUDIO_AC4));
TrackSelectorResult result =
trackSelector.selectTracks(
new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
assertThat(result.length).isEqualTo(1);
assertFixedSelection(result.selections.get(0), trackGroups, formatAc4);
trackSelector.setParameters(
trackSelector
.buildUponParameters()
.setPreferredAudioMimeTypes(MimeTypes.AUDIO_AC4, MimeTypes.AUDIO_AAC));
result =
trackSelector.selectTracks(
new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
assertThat(result.length).isEqualTo(1);
assertFixedSelection(result.selections.get(0), trackGroups, formatAc4);
trackSelector.setParameters(
trackSelector
.buildUponParameters()
.setPreferredAudioMimeTypes(MimeTypes.AUDIO_AMR, MimeTypes.AUDIO_E_AC3));
result =
trackSelector.selectTracks(
new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
assertThat(result.length).isEqualTo(1);
assertFixedSelection(result.selections.get(0), trackGroups, formatEAc3);
// Select first in the list if no preference is specified.
trackSelector.setParameters(
trackSelector.buildUponParameters().setPreferredAudioMimeType(null));
result =
trackSelector.selectTracks(
new RendererCapabilities[] {AUDIO_CAPABILITIES}, trackGroups, periodId, TIMELINE);
assertThat(result.length).isEqualTo(1);
assertFixedSelection(result.selections.get(0), trackGroups, formatAac);
}
private static void assertSelections(TrackSelectorResult result, TrackSelection[] expected) {
assertThat(result.length).isEqualTo(expected.length);
for (int i = 0; i < expected.length; i++) {
@ -1572,6 +1664,7 @@ public final class DefaultTrackSelectorTest {
/* viewportWidth= */ 8,
/* viewportHeight= */ 9,
/* viewportOrientationMayChange= */ true,
/* preferredVideoMimeTypes= */ ImmutableList.of(MimeTypes.VIDEO_AV1, MimeTypes.VIDEO_H264),
// Audio
/* preferredAudioLanguages= */ ImmutableList.of("zh", "jp"),
/* maxAudioChannelCount= */ 10,
@ -1580,6 +1673,7 @@ public final class DefaultTrackSelectorTest {
/* allowAudioMixedMimeTypeAdaptiveness= */ true,
/* allowAudioMixedSampleRateAdaptiveness= */ false,
/* allowAudioMixedChannelCountAdaptiveness= */ true,
/* preferredAudioMimeTypes= */ ImmutableList.of(MimeTypes.AUDIO_AC3, MimeTypes.AUDIO_E_AC3),
// Text
/* preferredTextLanguages= */ ImmutableList.of("de", "en"),
/* preferredTextRoleFlags= */ C.ROLE_FLAG_CAPTION,