diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java index 381393d8b36..a842e736e16 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayer.java @@ -20,6 +20,7 @@ import static com.google.android.exoplayer2.util.Assertions.checkState; import android.content.Context; +import android.media.AudioDeviceInfo; import android.media.AudioTrack; import android.media.MediaCodec; import android.os.Looper; @@ -29,6 +30,7 @@ import android.view.TextureView; import androidx.annotation.IntRange; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.AnalyticsListener; @@ -1403,6 +1405,15 @@ void setMediaSources( /** Detaches any previously attached auxiliary audio effect from the underlying audio track. */ void clearAuxEffectInfo(); + /** + * Sets the preferred audio device. + * + * @param audioDeviceInfo The preferred {@linkplain AudioDeviceInfo audio device}, or null to + * restore the default. + */ + @RequiresApi(23) + void setPreferredAudioDevice(@Nullable AudioDeviceInfo audioDeviceInfo); + /** * Sets whether skipping silences in the audio stream is enabled. * diff --git a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java index 8709003c017..cca1d552926 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/ExoPlayerImpl.java @@ -23,6 +23,7 @@ import static com.google.android.exoplayer2.Renderer.MSG_SET_AUX_EFFECT_INFO; import static com.google.android.exoplayer2.Renderer.MSG_SET_CAMERA_MOTION_LISTENER; import static com.google.android.exoplayer2.Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY; +import static com.google.android.exoplayer2.Renderer.MSG_SET_PREFERRED_AUDIO_DEVICE; import static com.google.android.exoplayer2.Renderer.MSG_SET_SCALING_MODE; import static com.google.android.exoplayer2.Renderer.MSG_SET_SKIP_SILENCE_ENABLED; import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER; @@ -38,6 +39,7 @@ import android.content.Context; import android.graphics.Rect; import android.graphics.SurfaceTexture; +import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioTrack; import android.media.MediaFormat; @@ -1431,6 +1433,13 @@ public void clearAuxEffectInfo() { setAuxEffectInfo(new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, /* sendLevel= */ 0f)); } + @RequiresApi(23) + @Override + public void setPreferredAudioDevice(@Nullable AudioDeviceInfo audioDeviceInfo) { + verifyApplicationThread(); + sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_PREFERRED_AUDIO_DEVICE, audioDeviceInfo); + } + @Override public void setVolume(float volume) { verifyApplicationThread(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java index 868f2a45790..f8ffe47ef3c 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/Renderer.java @@ -194,6 +194,13 @@ interface WakeupListener { *

The message payload must be a {@link WakeupListener} instance. */ int MSG_SET_WAKEUP_LISTENER = 11; + /** + * The type of a message that can be passed to audio renderers via {@link + * ExoPlayer#createMessage(PlayerMessage.Target)}. The message payload should be an {@link + * android.media.AudioDeviceInfo} instance representing the preferred audio device, or null to + * restore the default. + */ + int MSG_SET_PREFERRED_AUDIO_DEVICE = 12; /** * Applications or extensions may define custom {@code MSG_*} constants that can be passed to * renderers. These custom constants must be greater than or equal to this value. diff --git a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java index a846c7947fb..ddd3df45bc0 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/SimpleExoPlayer.java @@ -16,6 +16,7 @@ package com.google.android.exoplayer2; import android.content.Context; +import android.media.AudioDeviceInfo; import android.os.Looper; import android.view.Surface; import android.view.SurfaceHolder; @@ -23,6 +24,7 @@ import android.view.TextureView; import androidx.annotation.IntRange; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.annotation.VisibleForTesting; import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.AnalyticsListener; @@ -617,6 +619,13 @@ public void clearAuxEffectInfo() { player.clearAuxEffectInfo(); } + @RequiresApi(23) + @Override + public void setPreferredAudioDevice(@Nullable AudioDeviceInfo audioDeviceInfo) { + blockUntilConstructorFinished(); + player.setPreferredAudioDevice(audioDeviceInfo); + } + @Override public void setVolume(float volume) { blockUntilConstructorFinished(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java index 7b326f591bd..192f2dfa92d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/AudioSink.java @@ -17,9 +17,11 @@ import static java.lang.annotation.ElementType.TYPE_USE; +import android.media.AudioDeviceInfo; import android.media.AudioTrack; import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.Format; @@ -416,6 +418,15 @@ boolean handleBuffer(ByteBuffer buffer, long presentationTimeUs, int encodedAcce /** Sets the auxiliary effect. */ void setAuxEffectInfo(AuxEffectInfo auxEffectInfo); + /** + * Sets the preferred audio device. + * + * @param audioDeviceInfo The preferred {@linkplain AudioDeviceInfo audio device}, or null to + * restore the default. + */ + @RequiresApi(23) + default void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {} + /** * Enables tunneling, if possible. The sink is reset if tunneling was previously disabled. * Enabling tunneling is only possible if the sink is based on a platform {@link AudioTrack}, and diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java index 9a13f09019f..5e8960cca3f 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DecoderAudioRenderer.java @@ -23,11 +23,14 @@ import static java.lang.Math.max; import static java.lang.annotation.ElementType.TYPE_USE; +import android.media.AudioDeviceInfo; import android.os.Handler; import android.os.SystemClock; import androidx.annotation.CallSuper; +import androidx.annotation.DoNotInline; import androidx.annotation.IntDef; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; @@ -619,6 +622,11 @@ public void handleMessage(@MessageType int messageType, @Nullable Object message case MSG_SET_AUDIO_SESSION_ID: audioSink.setAudioSessionId((Integer) message); break; + case MSG_SET_PREFERRED_AUDIO_DEVICE: + if (Util.SDK_INT >= 23) { + Api23.setAudioSinkPreferredDevice(audioSink, message); + } + break; case MSG_SET_CAMERA_MOTION_LISTENER: case MSG_SET_CHANGE_FRAME_RATE_STRATEGY: case MSG_SET_SCALING_MODE: @@ -791,4 +799,16 @@ public void onAudioSinkError(Exception audioSinkError) { eventDispatcher.audioSinkError(audioSinkError); } } + + @RequiresApi(23) + private static final class Api23 { + private Api23() {} + + @DoNotInline + public static void setAudioSinkPreferredDevice( + AudioSink audioSink, @Nullable Object messagePayload) { + @Nullable AudioDeviceInfo audioDeviceInfo = (AudioDeviceInfo) messagePayload; + audioSink.setPreferredDevice(audioDeviceInfo); + } + } } diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java index aaf7dcbaffb..0c6920a334d 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/DefaultAudioSink.java @@ -24,6 +24,7 @@ import static java.lang.annotation.ElementType.TYPE_USE; import android.annotation.SuppressLint; +import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.AudioManager; import android.media.AudioTrack; @@ -556,6 +557,7 @@ public DefaultAudioSink build() { private boolean externalAudioSessionIdProvided; private int audioSessionId; private AuxEffectInfo auxEffectInfo; + @Nullable private AudioDeviceInfoApi23 preferredDevice; private boolean tunneling; private long lastFeedElapsedRealtimeMs; private boolean offloadDisabledUntilNextConfiguration; @@ -904,6 +906,9 @@ private boolean initializeAudioTrack() throws InitializationException { audioTrack.attachAuxEffect(auxEffectInfo.effectId); audioTrack.setAuxEffectSendLevel(auxEffectInfo.sendLevel); } + if (preferredDevice != null && Util.SDK_INT >= 23) { + Api23.setPreferredDeviceOnAudioTrack(audioTrack, preferredDevice); + } startMediaTimeUsNeedsInit = true; return true; @@ -1404,6 +1409,16 @@ public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) { this.auxEffectInfo = auxEffectInfo; } + @RequiresApi(23) + @Override + public void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) { + this.preferredDevice = + audioDeviceInfo == null ? null : new AudioDeviceInfoApi23(audioDeviceInfo); + if (audioTrack != null) { + Api23.setPreferredDeviceOnAudioTrack(audioTrack, this.preferredDevice); + } + } + @Override public void enableTunnelingV21() { Assertions.checkState(Util.SDK_INT >= 21); @@ -2300,6 +2315,28 @@ public void clear() { } } + @RequiresApi(23) + private static final class AudioDeviceInfoApi23 { + + public final AudioDeviceInfo audioDeviceInfo; + + public AudioDeviceInfoApi23(AudioDeviceInfo audioDeviceInfo) { + this.audioDeviceInfo = audioDeviceInfo; + } + } + + @RequiresApi(23) + private static final class Api23 { + private Api23() {} + + @DoNotInline + public static void setPreferredDeviceOnAudioTrack( + AudioTrack audioTrack, @Nullable AudioDeviceInfoApi23 audioDeviceInfo) { + audioTrack.setPreferredDevice( + audioDeviceInfo == null ? null : audioDeviceInfo.audioDeviceInfo); + } + } + @RequiresApi(31) private static final class Api31 { private Api31() {} diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java index e8e5edd1cdf..9ce4140d4e2 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/ForwardingAudioSink.java @@ -15,7 +15,9 @@ */ package com.google.android.exoplayer2.audio; +import android.media.AudioDeviceInfo; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.analytics.PlayerId; @@ -134,6 +136,12 @@ public void setAuxEffectInfo(AuxEffectInfo auxEffectInfo) { sink.setAuxEffectInfo(auxEffectInfo); } + @RequiresApi(23) + @Override + public void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) { + sink.setPreferredDevice(audioDeviceInfo); + } + @Override public void enableTunnelingV21() { sink.enableTunnelingV21(); diff --git a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java index 2da4fd87456..62eab7dde7a 100644 --- a/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java +++ b/library/core/src/main/java/com/google/android/exoplayer2/audio/MediaCodecAudioRenderer.java @@ -23,13 +23,16 @@ import android.annotation.SuppressLint; import android.content.Context; +import android.media.AudioDeviceInfo; import android.media.AudioFormat; import android.media.MediaCodec; import android.media.MediaCrypto; import android.media.MediaFormat; import android.os.Handler; import androidx.annotation.CallSuper; +import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlayer; @@ -745,6 +748,11 @@ public void handleMessage(@MessageType int messageType, @Nullable Object message AuxEffectInfo auxEffectInfo = (AuxEffectInfo) message; audioSink.setAuxEffectInfo(auxEffectInfo); break; + case MSG_SET_PREFERRED_AUDIO_DEVICE: + if (Util.SDK_INT >= 23) { + Api23.setAudioSinkPreferredDevice(audioSink, message); + } + break; case MSG_SET_SKIP_SILENCE_ENABLED: audioSink.setSkipSilenceEnabled((Boolean) message); break; @@ -938,4 +946,16 @@ public void onAudioSinkError(Exception audioSinkError) { eventDispatcher.audioSinkError(audioSinkError); } } + + @RequiresApi(23) + private static final class Api23 { + private Api23() {} + + @DoNotInline + public static void setAudioSinkPreferredDevice( + AudioSink audioSink, @Nullable Object messagePayload) { + @Nullable AudioDeviceInfo audioDeviceInfo = (AudioDeviceInfo) messagePayload; + audioSink.setPreferredDevice(audioDeviceInfo); + } + } } diff --git a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java index 0cad49571ae..7dc6c8c1bf9 100644 --- a/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java +++ b/testutils/src/main/java/com/google/android/exoplayer2/testutil/StubExoPlayer.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.testutil; +import android.media.AudioDeviceInfo; import android.os.Looper; import androidx.annotation.Nullable; import com.google.android.exoplayer2.ExoPlaybackException; @@ -234,6 +235,11 @@ public void clearAuxEffectInfo() { throw new UnsupportedOperationException(); } + @Override + public void setPreferredAudioDevice(@Nullable AudioDeviceInfo audioDeviceInfo) { + throw new UnsupportedOperationException(); + } + @Override public void setSkipSilenceEnabled(boolean skipSilenceEnabled) { throw new UnsupportedOperationException();