Add setPreferredAudioDevice method to ExoPlayer

This allows to access the associated functionality of AudioTrack and
fills a feature gap to MediaPlayer, which has a similar method.

Issue: androidx/media#135
PiperOrigin-RevId: 476398964
This commit is contained in:
tonihei 2022-09-23 17:05:43 +00:00 committed by Marc Baechinger
parent 29cf09316e
commit ccb820dd2f
10 changed files with 138 additions and 0 deletions

View File

@ -20,6 +20,7 @@ import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Assertions.checkState; import static com.google.android.exoplayer2.util.Assertions.checkState;
import android.content.Context; import android.content.Context;
import android.media.AudioDeviceInfo;
import android.media.AudioTrack; import android.media.AudioTrack;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.os.Looper; import android.os.Looper;
@ -29,6 +30,7 @@ import android.view.SurfaceView;
import android.view.TextureView; import android.view.TextureView;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.analytics.AnalyticsListener;
@ -1403,6 +1405,15 @@ public interface ExoPlayer extends Player {
/** Detaches any previously attached auxiliary audio effect from the underlying audio track. */ /** Detaches any previously attached auxiliary audio effect from the underlying audio track. */
void clearAuxEffectInfo(); 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. * Sets whether skipping silences in the audio stream is enabled.
* *

View File

@ -23,6 +23,7 @@ import static com.google.android.exoplayer2.Renderer.MSG_SET_AUDIO_SESSION_ID;
import static com.google.android.exoplayer2.Renderer.MSG_SET_AUX_EFFECT_INFO; 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_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_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_SCALING_MODE;
import static com.google.android.exoplayer2.Renderer.MSG_SET_SKIP_SILENCE_ENABLED; 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; import static com.google.android.exoplayer2.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER;
@ -38,6 +39,7 @@ import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.SurfaceTexture; import android.graphics.SurfaceTexture;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.AudioTrack; import android.media.AudioTrack;
import android.media.MediaFormat; import android.media.MediaFormat;
@ -1431,6 +1433,13 @@ import java.util.concurrent.TimeoutException;
setAuxEffectInfo(new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, /* sendLevel= */ 0f)); 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 @Override
public void setVolume(float volume) { public void setVolume(float volume) {
verifyApplicationThread(); verifyApplicationThread();

View File

@ -194,6 +194,13 @@ public interface Renderer extends PlayerMessage.Target {
* <p>The message payload must be a {@link WakeupListener} instance. * <p>The message payload must be a {@link WakeupListener} instance.
*/ */
int MSG_SET_WAKEUP_LISTENER = 11; 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 * 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. * renderers. These custom constants must be greater than or equal to this value.

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import android.content.Context; import android.content.Context;
import android.media.AudioDeviceInfo;
import android.os.Looper; import android.os.Looper;
import android.view.Surface; import android.view.Surface;
import android.view.SurfaceHolder; import android.view.SurfaceHolder;
@ -23,6 +24,7 @@ import android.view.SurfaceView;
import android.view.TextureView; import android.view.TextureView;
import androidx.annotation.IntRange; import androidx.annotation.IntRange;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting; import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.analytics.AnalyticsCollector; import com.google.android.exoplayer2.analytics.AnalyticsCollector;
import com.google.android.exoplayer2.analytics.AnalyticsListener; import com.google.android.exoplayer2.analytics.AnalyticsListener;
@ -617,6 +619,13 @@ public class SimpleExoPlayer extends BasePlayer
player.clearAuxEffectInfo(); player.clearAuxEffectInfo();
} }
@RequiresApi(23)
@Override
public void setPreferredAudioDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {
blockUntilConstructorFinished();
player.setPreferredAudioDevice(audioDeviceInfo);
}
@Override @Override
public void setVolume(float volume) { public void setVolume(float volume) {
blockUntilConstructorFinished(); blockUntilConstructorFinished();

View File

@ -17,9 +17,11 @@ package com.google.android.exoplayer2.audio;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import android.media.AudioDeviceInfo;
import android.media.AudioTrack; import android.media.AudioTrack;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
@ -416,6 +418,15 @@ public interface AudioSink {
/** Sets the auxiliary effect. */ /** Sets the auxiliary effect. */
void setAuxEffectInfo(AuxEffectInfo auxEffectInfo); 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. * 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 * Enabling tunneling is only possible if the sink is based on a platform {@link AudioTrack}, and

View File

@ -23,11 +23,14 @@ import static com.google.common.base.MoreObjects.firstNonNull;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import android.media.AudioDeviceInfo;
import android.os.Handler; import android.os.Handler;
import android.os.SystemClock; import android.os.SystemClock;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.DoNotInline;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.BaseRenderer; import com.google.android.exoplayer2.BaseRenderer;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
@ -619,6 +622,11 @@ public abstract class DecoderAudioRenderer<
case MSG_SET_AUDIO_SESSION_ID: case MSG_SET_AUDIO_SESSION_ID:
audioSink.setAudioSessionId((Integer) message); audioSink.setAudioSessionId((Integer) message);
break; 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_CAMERA_MOTION_LISTENER:
case MSG_SET_CHANGE_FRAME_RATE_STRATEGY: case MSG_SET_CHANGE_FRAME_RATE_STRATEGY:
case MSG_SET_SCALING_MODE: case MSG_SET_SCALING_MODE:
@ -791,4 +799,16 @@ public abstract class DecoderAudioRenderer<
eventDispatcher.audioSinkError(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);
}
}
} }

View File

@ -24,6 +24,7 @@ import static java.lang.Math.min;
import static java.lang.annotation.ElementType.TYPE_USE; import static java.lang.annotation.ElementType.TYPE_USE;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.AudioTrack; import android.media.AudioTrack;
@ -556,6 +557,7 @@ public final class DefaultAudioSink implements AudioSink {
private boolean externalAudioSessionIdProvided; private boolean externalAudioSessionIdProvided;
private int audioSessionId; private int audioSessionId;
private AuxEffectInfo auxEffectInfo; private AuxEffectInfo auxEffectInfo;
@Nullable private AudioDeviceInfoApi23 preferredDevice;
private boolean tunneling; private boolean tunneling;
private long lastFeedElapsedRealtimeMs; private long lastFeedElapsedRealtimeMs;
private boolean offloadDisabledUntilNextConfiguration; private boolean offloadDisabledUntilNextConfiguration;
@ -904,6 +906,9 @@ public final class DefaultAudioSink implements AudioSink {
audioTrack.attachAuxEffect(auxEffectInfo.effectId); audioTrack.attachAuxEffect(auxEffectInfo.effectId);
audioTrack.setAuxEffectSendLevel(auxEffectInfo.sendLevel); audioTrack.setAuxEffectSendLevel(auxEffectInfo.sendLevel);
} }
if (preferredDevice != null && Util.SDK_INT >= 23) {
Api23.setPreferredDeviceOnAudioTrack(audioTrack, preferredDevice);
}
startMediaTimeUsNeedsInit = true; startMediaTimeUsNeedsInit = true;
return true; return true;
@ -1404,6 +1409,16 @@ public final class DefaultAudioSink implements AudioSink {
this.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 @Override
public void enableTunnelingV21() { public void enableTunnelingV21() {
Assertions.checkState(Util.SDK_INT >= 21); Assertions.checkState(Util.SDK_INT >= 21);
@ -2300,6 +2315,28 @@ public final class DefaultAudioSink implements AudioSink {
} }
} }
@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) @RequiresApi(31)
private static final class Api31 { private static final class Api31 {
private Api31() {} private Api31() {}

View File

@ -15,7 +15,9 @@
*/ */
package com.google.android.exoplayer2.audio; package com.google.android.exoplayer2.audio;
import android.media.AudioDeviceInfo;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.exoplayer2.PlaybackParameters;
import com.google.android.exoplayer2.analytics.PlayerId; import com.google.android.exoplayer2.analytics.PlayerId;
@ -134,6 +136,12 @@ public class ForwardingAudioSink implements AudioSink {
sink.setAuxEffectInfo(auxEffectInfo); sink.setAuxEffectInfo(auxEffectInfo);
} }
@RequiresApi(23)
@Override
public void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {
sink.setPreferredDevice(audioDeviceInfo);
}
@Override @Override
public void enableTunnelingV21() { public void enableTunnelingV21() {
sink.enableTunnelingV21(); sink.enableTunnelingV21();

View File

@ -23,13 +23,16 @@ import static java.lang.Math.max;
import android.annotation.SuppressLint; import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCrypto; import android.media.MediaCrypto;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.os.Handler; import android.os.Handler;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.DoNotInline;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.ExoPlayer; import com.google.android.exoplayer2.ExoPlayer;
@ -745,6 +748,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
AuxEffectInfo auxEffectInfo = (AuxEffectInfo) message; AuxEffectInfo auxEffectInfo = (AuxEffectInfo) message;
audioSink.setAuxEffectInfo(auxEffectInfo); audioSink.setAuxEffectInfo(auxEffectInfo);
break; break;
case MSG_SET_PREFERRED_AUDIO_DEVICE:
if (Util.SDK_INT >= 23) {
Api23.setAudioSinkPreferredDevice(audioSink, message);
}
break;
case MSG_SET_SKIP_SILENCE_ENABLED: case MSG_SET_SKIP_SILENCE_ENABLED:
audioSink.setSkipSilenceEnabled((Boolean) message); audioSink.setSkipSilenceEnabled((Boolean) message);
break; break;
@ -938,4 +946,16 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
eventDispatcher.audioSinkError(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);
}
}
} }

View File

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.testutil; package com.google.android.exoplayer2.testutil;
import android.media.AudioDeviceInfo;
import android.os.Looper; import android.os.Looper;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
@ -234,6 +235,11 @@ public class StubExoPlayer extends StubPlayer implements ExoPlayer {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
@Override
public void setPreferredAudioDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {
throw new UnsupportedOperationException();
}
@Override @Override
public void setSkipSilenceEnabled(boolean skipSilenceEnabled) { public void setSkipSilenceEnabled(boolean skipSilenceEnabled) {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();