diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 38712ca042..8a35bde4c0 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -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 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 3b06239c89..d477bc1fed 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 @@ -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 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 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 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 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 preferredVideoMimeTypes, // Audio ImmutableList preferredAudioLanguages, int maxAudioChannelCount, @@ -1062,6 +1128,7 @@ public class DefaultTrackSelector extends MappingTrackSelector { boolean allowAudioMixedMimeTypeAdaptiveness, boolean allowAudioMixedSampleRateAdaptiveness, boolean allowAudioMixedChannelCountAdaptiveness, + ImmutableList preferredAudioMimeTypes, // Text ImmutableList 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 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 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, diff --git a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java index 6443dc51a4..d30fa384e9 100644 --- a/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java +++ b/library/core/src/test/java/com/google/android/exoplayer2/trackselection/DefaultTrackSelectorTest.java @@ -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,