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 5b3efa8ad9
commit a069ebda47
11 changed files with 141 additions and 0 deletions

View File

@ -27,6 +27,8 @@
* Make `AudioTrackBufferSizeProvider` a public interface.
* Add `WrappingMediaSource` to simplify wrapping a single `MediaSource`
([#7279](https://github.com/google/ExoPlayer/issues/7279)).
* Add `ExoPlayer.setPreferredAudioDevice` to set the preferred audio
output device ([#135](https://github.com/androidx/media/issues/135)).
* Metadata:
* `MetadataRenderer` can now be configured to render metadata as soon as
they are available. Create an instance with

View File

@ -20,6 +20,7 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.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.SurfaceView;
import android.view.TextureView;
import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.AuxEffectInfo;
@ -1473,6 +1475,16 @@ public interface ExoPlayer extends Player {
@UnstableApi
void clearAuxEffectInfo();
/**
* Sets the preferred audio device.
*
* @param audioDeviceInfo The preferred {@linkplain AudioDeviceInfo audio device}, or null to
* restore the default.
*/
@UnstableApi
@RequiresApi(23)
void setPreferredAudioDevice(@Nullable AudioDeviceInfo audioDeviceInfo);
/**
* Sets whether skipping silences in the audio stream is enabled.
*

View File

@ -26,6 +26,7 @@ import static androidx.media3.exoplayer.Renderer.MSG_SET_AUDIO_SESSION_ID;
import static androidx.media3.exoplayer.Renderer.MSG_SET_AUX_EFFECT_INFO;
import static androidx.media3.exoplayer.Renderer.MSG_SET_CAMERA_MOTION_LISTENER;
import static androidx.media3.exoplayer.Renderer.MSG_SET_CHANGE_FRAME_RATE_STRATEGY;
import static androidx.media3.exoplayer.Renderer.MSG_SET_PREFERRED_AUDIO_DEVICE;
import static androidx.media3.exoplayer.Renderer.MSG_SET_SCALING_MODE;
import static androidx.media3.exoplayer.Renderer.MSG_SET_SKIP_SILENCE_ENABLED;
import static androidx.media3.exoplayer.Renderer.MSG_SET_VIDEO_FRAME_METADATA_LISTENER;
@ -38,6 +39,7 @@ import android.annotation.SuppressLint;
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;
@ -1442,6 +1444,13 @@ import java.util.concurrent.TimeoutException;
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();

View File

@ -198,6 +198,13 @@ public interface Renderer extends PlayerMessage.Target {
* <p>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.

View File

@ -16,6 +16,7 @@
package androidx.media3.exoplayer;
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.SurfaceView;
import android.view.TextureView;
import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.AuxEffectInfo;
@ -628,6 +630,13 @@ public class SimpleExoPlayer extends BasePlayer
player.clearAuxEffectInfo();
}
@RequiresApi(23)
@Override
public void setPreferredAudioDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {
blockUntilConstructorFinished();
player.setPreferredAudioDevice(audioDeviceInfo);
}
@Override
public void setVolume(float volume) {
blockUntilConstructorFinished();

View File

@ -17,9 +17,11 @@ package androidx.media3.exoplayer.audio;
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 androidx.media3.common.AudioAttributes;
import androidx.media3.common.AuxEffectInfo;
import androidx.media3.common.C;
@ -420,6 +422,15 @@ public interface AudioSink {
/** 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

View File

@ -23,11 +23,14 @@ import static com.google.common.base.MoreObjects.firstNonNull;
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 androidx.media3.common.AudioAttributes;
import androidx.media3.common.AuxEffectInfo;
import androidx.media3.common.C;
@ -623,6 +626,11 @@ public abstract class DecoderAudioRenderer<
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:
@ -795,4 +803,16 @@ public abstract class DecoderAudioRenderer<
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 android.annotation.SuppressLint;
import android.media.AudioDeviceInfo;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
@ -565,6 +566,7 @@ public final class DefaultAudioSink implements AudioSink {
private boolean externalAudioSessionIdProvided;
private int audioSessionId;
private AuxEffectInfo auxEffectInfo;
@Nullable private AudioDeviceInfoApi23 preferredDevice;
private boolean tunneling;
private long lastFeedElapsedRealtimeMs;
private boolean offloadDisabledUntilNextConfiguration;
@ -913,6 +915,9 @@ public final class DefaultAudioSink implements AudioSink {
audioTrack.attachAuxEffect(auxEffectInfo.effectId);
audioTrack.setAuxEffectSendLevel(auxEffectInfo.sendLevel);
}
if (preferredDevice != null && Util.SDK_INT >= 23) {
Api23.setPreferredDeviceOnAudioTrack(audioTrack, preferredDevice);
}
startMediaTimeUsNeedsInit = true;
return true;
@ -1413,6 +1418,16 @@ public final class DefaultAudioSink implements AudioSink {
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);
@ -2309,6 +2324,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)
private static final class Api31 {
private Api31() {}

View File

@ -15,7 +15,9 @@
*/
package androidx.media3.exoplayer.audio;
import android.media.AudioDeviceInfo;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.AuxEffectInfo;
import androidx.media3.common.Format;
@ -138,6 +140,12 @@ public class ForwardingAudioSink implements AudioSink {
sink.setAuxEffectInfo(auxEffectInfo);
}
@RequiresApi(23)
@Override
public void setPreferredDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {
sink.setPreferredDevice(audioDeviceInfo);
}
@Override
public void enableTunnelingV21() {
sink.enableTunnelingV21();

View File

@ -23,13 +23,16 @@ import static java.lang.Math.max;
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 androidx.media3.common.AudioAttributes;
import androidx.media3.common.AuxEffectInfo;
import androidx.media3.common.C;
@ -749,6 +752,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
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;
@ -942,4 +950,16 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
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 androidx.media3.test.utils;
import android.media.AudioDeviceInfo;
import android.os.Looper;
import androidx.annotation.Nullable;
import androidx.media3.common.AudioAttributes;
@ -236,6 +237,11 @@ public class StubExoPlayer extends StubPlayer implements ExoPlayer {
throw new UnsupportedOperationException();
}
@Override
public void setPreferredAudioDevice(@Nullable AudioDeviceInfo audioDeviceInfo) {
throw new UnsupportedOperationException();
}
@Override
public void setSkipSilenceEnabled(boolean skipSilenceEnabled) {
throw new UnsupportedOperationException();