diff --git a/RELEASENOTES.md b/RELEASENOTES.md
index 23d34af5d2..752b15952d 100644
--- a/RELEASENOTES.md
+++ b/RELEASENOTES.md
@@ -66,6 +66,12 @@
* Add Util methods `shouldShowPlayButton` and
`handlePlayPauseButtonAction` to write custom UI elements with a
play/pause button.
+* Track selection:
+ * Add
+ `DefaultTrackSelector.Parameters.allowInvalidateSelectionsForRendererCapabilitiesChange`
+ which is disabled by default. When enabled, the `DefaultTrackSelector`
+ will trigger a new track selection when the renderer capabilities
+ changed.
* Audio:
* Fix bug where some playbacks fail when tunneling is enabled and
`AudioProcessors` are active, e.g. for gapless trimming
@@ -85,6 +91,9 @@
`onRendererCapabilitiesChanged` events.
* Add `ChannelMixingAudioProcessor` for applying scaling/mixing to audio
channels.
+ * Add new int value `DISCARD_REASON_AUDIO_BYPASS_POSSIBLE` to
+ `DecoderDiscardReasons` to discard audio decoder when bypass mode is
+ possible after audio capabilities change.
* Metadata:
* Deprecate `MediaMetadata.folderType` in favor of `isBrowsable` and
`mediaType`.
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DecoderReuseEvaluation.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DecoderReuseEvaluation.java
index 142555e985..e56b172b50 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DecoderReuseEvaluation.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/DecoderReuseEvaluation.java
@@ -80,7 +80,8 @@ public final class DecoderReuseEvaluation {
DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED,
DISCARD_REASON_AUDIO_CHANNEL_COUNT_CHANGED,
DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED,
- DISCARD_REASON_AUDIO_ENCODING_CHANGED
+ DISCARD_REASON_AUDIO_ENCODING_CHANGED,
+ DISCARD_REASON_AUDIO_BYPASS_POSSIBLE
})
public @interface DecoderDiscardReasons {}
@@ -114,6 +115,8 @@ public final class DecoderReuseEvaluation {
public static final int DISCARD_REASON_AUDIO_SAMPLE_RATE_CHANGED = 1 << 13;
/** The audio encoding is changing. */
public static final int DISCARD_REASON_AUDIO_ENCODING_CHANGED = 1 << 14;
+ /** The audio bypass mode is possible. */
+ public static final int DISCARD_REASON_AUDIO_BYPASS_POSSIBLE = 1 << 15;
/** The name of the decoder. */
public final String decoderName;
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java
index 1cfa125c10..30d8206dfb 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/ExoPlayerImplInternal.java
@@ -165,6 +165,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private static final int MSG_SET_PAUSE_AT_END_OF_WINDOW = 23;
private static final int MSG_SET_OFFLOAD_SCHEDULING_ENABLED = 24;
private static final int MSG_ATTEMPT_RENDERER_ERROR_RECOVERY = 25;
+ private static final int MSG_RENDERER_CAPABILITIES_CHANGED = 26;
private static final int ACTIVE_INTERVAL_MS = 10;
private static final int IDLE_INTERVAL_MS = 1000;
@@ -481,6 +482,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
handler.sendEmptyMessage(MSG_TRACK_SELECTION_INVALIDATED);
}
+ @Override
+ public void onRendererCapabilitiesChanged(Renderer renderer) {
+ handler.sendEmptyMessage(MSG_RENDERER_CAPABILITIES_CHANGED);
+ }
+
// DefaultMediaClock.PlaybackParametersListener implementation.
@Override
@@ -576,6 +582,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
case MSG_ATTEMPT_RENDERER_ERROR_RECOVERY:
attemptRendererErrorRecovery();
break;
+ case MSG_RENDERER_CAPABILITIES_CHANGED:
+ reselectTracksInternalAndSeek();
+ break;
case MSG_RELEASE:
releaseInternal();
// Return immediately to not send playback info updates after release.
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java
index 461365629f..4eff06394d 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/audio/MediaCodecAudioRenderer.java
@@ -444,6 +444,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
DecoderReuseEvaluation evaluation = codecInfo.canReuseCodec(oldFormat, newFormat);
@DecoderDiscardReasons int discardReasons = evaluation.discardReasons;
+ if (isBypassPossible(newFormat)) {
+ // We prefer direct audio playback so that for multi-channel tracks the audio is not downmixed
+ // to stereo.
+ discardReasons |= DecoderReuseEvaluation.DISCARD_REASON_AUDIO_BYPASS_POSSIBLE;
+ }
if (getCodecMaxInputSize(codecInfo, newFormat) > codecMaxInputSize) {
discardReasons |= DISCARD_REASON_MAX_INPUT_SIZE_EXCEEDED;
}
diff --git a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java
index 6e79139171..c1ea60c3e9 100644
--- a/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java
+++ b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/mediacodec/MediaCodecRenderer.java
@@ -491,7 +491,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
return;
}
- if (sourceDrmSession == null && shouldUseBypass(inputFormat)) {
+ if (isBypassPossible(inputFormat)) {
initBypass(inputFormat);
return;
}
@@ -546,6 +546,19 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
}
}
+ /**
+ * Returns whether buffers in the input format can be processed without a codec.
+ *
+ *
This method returns the possibility of bypass mode with checking both the renderer
+ * capabilities and DRM protection.
+ *
+ * @param format The input {@link Format}.
+ * @return Whether playback bypassing {@link MediaCodec} is possible.
+ */
+ protected final boolean isBypassPossible(Format format) {
+ return sourceDrmSession == null && shouldUseBypass(inputFormat);
+ }
+
/**
* Returns whether buffers in the input format can be processed without a codec.
*
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 fcb61e3e8c..0e8738b14b 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
@@ -111,7 +111,8 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
* }
*/
@UnstableApi
-public class DefaultTrackSelector extends MappingTrackSelector {
+public class DefaultTrackSelector extends MappingTrackSelector
+ implements RendererCapabilities.Listener {
private static final String TAG = "DefaultTrackSelector";
private static final String AUDIO_CHANNEL_COUNT_CONSTRAINTS_WARN_MESSAGE =
@@ -758,6 +759,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
private boolean exceedRendererCapabilitiesIfNecessary;
private boolean tunnelingEnabled;
private boolean allowMultipleAdaptiveSelections;
+ private boolean allowInvalidateSelectionsOnRendererCapabilitiesChange;
// Overrides
private final SparseArray