Dos o más apps para Android pueden reproducir audio en la misma transmisión de salida de forma simultánea y el sistema se ocupa de mezclar todo. Si bien esta función es técnicamente impresionante, puede resultar muy molesta para el usuario. Para evitar que todas las apps de música reproduzcan contenido al mismo tiempo, Android presenta el concepto de foco de audio. Solo una app por vez puede mantener el foco de audio.
Cuando tu app necesita transmitir audio, debe solicitar el foco de audio. Cuando lo recibe, puede reproducir sonido. Sin embargo, después de adquirir el foco de audio, es posible que no pueda mantenerlo hasta que termine la reproducción. Si otra app solicita el foco de audio, tu app dejará de tenerlo. Si eso ocurre, debería pausar la reproducción o bajar el volumen para que los usuarios escuchen la nueva fuente de audio con más facilidad.
El foco de audio es cooperativo. Si bien te alentamos a que programes tu app de modo que cumpla con los lineamientos del foco de audio, el sistema no impone las reglas. Si una aplicación quiere seguir reproduciendo contenido a un volumen alto, incluso luego de haber perdido el foco de audio, no hay nada que se lo impida. Esa es una mala experiencia y es muy probable que los usuarios desinstalen una app que se comporte de ese modo incorrecto.
Una app de audio que tiene un buen comportamiento debe administrar el foco de audio de acuerdo con estos lineamientos generales:
- Debe llamar a
requestAudioFocus()
justo antes de comenzar a reproducir contenido y verificar que la llamada muestreAUDIOFOCUS_REQUEST_GRANTED
. Si diseñas tu app como se describe en esta guía, la llamada arequestAudioFocus()
se debe realizar en la devolución de llamadaonPlay()
de tu sesión multimedia. - Cuando otra app obtiene el foco de audio, la tuya debe detener o pausar la reproducción, o bajar el volumen.
- Cuando la reproducción se detiene, debe abandonar el foco de audio.
El foco de audio se maneja de manera diferente según la versión de Android que se ejecute:
- A partir de Android 2.2 (API nivel 8), las apps llaman a
requestAudioFocus()
y aabandonAudioFocus()
para administrar el foco de audio. También deben registrar unAudioManager.OnAudioFocusChangeListener
con ambas llamadas para recibir devoluciones de llamada y administrar su propio nivel de audio. - En el caso de las apps orientadas a Android 5.0 (API nivel 21) y versiones posteriores, las apps de audio deben usar
AudioAttributes
para describir el tipo de audio que reproduce tu app. Por ejemplo, las aplicaciones que reproducen contenido de voz deben especificarCONTENT_TYPE_SPEECH
. - Las apps que ejecutan Android 8.0 (API nivel 26) o versiones posteriores deben usar el método
requestAudioFocus()
, que toma un parámetroAudioFocusRequest
.AudioFocusRequest
contiene información sobre el contexto de audio y las funciones de tu app. El sistema usa esta información para administrar automáticamente la obtención y la pérdida del foco de audio.
Foco de audio en Android 8.0 y versiones posteriores
A partir de Android 8.0 (API nivel 26), cuando llamas a requestAudioFocus()
, debes proporcionar un parámetro AudioFocusRequest
.
Para liberar el foco de audio, llama al método abandonAudioFocusRequest()
, que también toma un AudioFocusRequest
como argumento. Se debe usar la misma instancia de AudioFocusRequest
al solicitar y abandonar el foco.
Para crear un AudioFocusRequest
, usa un AudioFocusRequest.Builder
. Dado que una solicitud de foco siempre debe especificar de qué tipo es, este se incluye en el constructor para el compilador. Usa los métodos del compilador para establecer los otros campos de la solicitud.
El campo FocusGain
es obligatorio; todos los demás son opcionales.
Método | Notas |
---|---|
setFocusGain()
|
Este campo es obligatorio en todas las solicitudes. Toma los mismos valores que el objeto durationHint utilizado en la llamada de versiones previas a Android 8.0 a requestAudioFocus() : AUDIOFOCUS_GAIN , AUDIOFOCUS_GAIN_TRANSIENT , AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK o AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE .
|
setAudioAttributes()
|
AudioAttributes describe el caso práctico de tu app. El sistema observa esos atributos cuando una aplicación obtiene y pierde el foco de audio. Los atributos reemplazan la noción de tipo de transmisión. En Android 8.0 (API nivel 26) y versiones posteriores, dejaron de estar disponibles los tipos de transmisión para cualquier operación que no sea la de los controles de volumen. Usa los mismos atributos en la solicitud del foco que usas en tu reproductor de audio (como se muestra en el ejemplo que sigue a esta tabla).
Primero usa un
Si no se especifica, el valor predeterminado de |
setWillPauseWhenDucked()
|
Cuando otra aplicación solicita el foco con AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK , la app que lo tiene en ese momento no suele recibir una devolución de llamada onAudioFocusChange() porque el sistema puede disminuir el volumen por sí mismo. Cuando necesites pausar la reproducción en lugar de bajar el volumen, llama a setWillPauseWhenDucked(true) y crea y configura un OnAudioFocusChangeListener , como se describe en Disminución automática del volumen.
|
setAcceptsDelayedFocusGain()
|
Una solicitud de foco de audio puede fallar cuando otra aplicación bloquea el foco. Este método permite una obtención demorada del foco: la capacidad de adquirir el foco de manera asíncrona cuando esté disponible.
Ten en cuenta que la obtención demorada del foco solo funciona si también especificas |
setOnAudioFocusChangeListener()
|
Un OnAudioFocusChangeListener solo es necesario si también especificas willPauseWhenDucked(true) o setAcceptsDelayedFocusGain(true) en la solicitud.
Hay dos métodos para configurar el objeto de escucha: uno con un argumento de controlador y otro sin él. El controlador es el subproceso en el que se ejecuta el objeto de escucha. Si no especificas un controlador, se usa el que está asociado con el |
En el siguiente ejemplo, se muestra cómo usar un AudioFocusRequest.Builder
para crear una AudioFocusRequest
y solicitar y abandonar el foco de audio:
Kotlin
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager focusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run { setAudioAttributes(AudioAttributes.Builder().run { setUsage(AudioAttributes.USAGE_GAME) setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) build() }) setAcceptsDelayedFocusGain(true) setOnAudioFocusChangeListener(afChangeListener, handler) build() } mediaPlayer = MediaPlayer() val focusLock = Any() var playbackDelayed = false var playbackNowAuthorized = false // ... val res = audioManager.requestAudioFocus(focusRequest) synchronized(focusLock) { playbackNowAuthorized = when (res) { AudioManager.AUDIOFOCUS_REQUEST_FAILED -> false AudioManager.AUDIOFOCUS_REQUEST_GRANTED -> { playbackNow() true } AudioManager.AUDIOFOCUS_REQUEST_DELAYED -> { playbackDelayed = true false } else -> false } } // ... override fun onAudioFocusChange(focusChange: Int) { when (focusChange) { AudioManager.AUDIOFOCUS_GAIN -> if (playbackDelayed || resumeOnFocusGain) { synchronized(focusLock) { playbackDelayed = false resumeOnFocusGain = false } playbackNow() } AudioManager.AUDIOFOCUS_LOSS -> { synchronized(focusLock) { resumeOnFocusGain = false playbackDelayed = false } pausePlayback() } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { synchronized(focusLock) { resumeOnFocusGain = true playbackDelayed = false } pausePlayback() } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { // ... pausing or ducking depends on your app } } }
Java
audioManager = (AudioManager) Context.getSystemService(Context.AUDIO_SERVICE); playbackAttributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_GAME) .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC) .build(); focusRequest = new AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN) .setAudioAttributes(playbackAttributes) .setAcceptsDelayedFocusGain(true) .setOnAudioFocusChangeListener(afChangeListener, handler) .build(); mediaPlayer = new MediaPlayer(); final Object focusLock = new Object(); boolean playbackDelayed = false; boolean playbackNowAuthorized = false; // ... int res = audioManager.requestAudioFocus(focusRequest); synchronized(focusLock) { if (res == AudioManager.AUDIOFOCUS_REQUEST_FAILED) { playbackNowAuthorized = false; } else if (res == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { playbackNowAuthorized = true; playbackNow(); } else if (res == AudioManager.AUDIOFOCUS_REQUEST_DELAYED) { playbackDelayed = true; playbackNowAuthorized = false; } } // ... @Override public void onAudioFocusChange(int focusChange) { switch (focusChange) { case AudioManager.AUDIOFOCUS_GAIN: if (playbackDelayed || resumeOnFocusGain) { synchronized(focusLock) { playbackDelayed = false; resumeOnFocusGain = false; } playbackNow(); } break; case AudioManager.AUDIOFOCUS_LOSS: synchronized(focusLock) { resumeOnFocusGain = false; playbackDelayed = false; } pausePlayback(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT: synchronized(focusLock) { resumeOnFocusGain = true; playbackDelayed = false; } pausePlayback(); break; case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK: // ... pausing or ducking depends on your app break; } } }
Disminución automática del volumen
En Android 8.0 (API nivel 26), cuando otra app solicita el foco con AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
, el sistema puede disminuir y restaurar el volumen sin invocar la devolución de llamada onAudioFocusChange()
de la aplicación.
Si bien la disminución automática del volumen es un comportamiento aceptable para las apps de reproducción de música y video, no es útil cuando se reproduce contenido de voz, como en una app de audiolibros. En este caso, la aplicación debería pausar la reproducción.
Si deseas que tu app pause la reproducción cuando se le pida que disminuya volumen, crea un OnAudioFocusChangeListener
con un método de devolución de llamada onAudioFocusChange()
que implemente el comportamiento de pausa/reanudación deseado.
Llama a setOnAudioFocusChangeListener()
para registrar el objeto de escucha y llama a setWillPauseWhenDucked(true)
para indicarle al sistema que use tu devolución de llamada en lugar de aplicar la disminución automática del volumen.
Obtención demorada del foco
En ocasiones, el sistema no puede aceptar una solicitud de foco de audio porque otra app lo está "bloqueando"; por ejemplo, cuando hay una llamada telefónica en curso. En este caso, requestAudioFocus()
muestra AUDIOFOCUS_REQUEST_FAILED
. Cuando esto sucede, tu app no debe continuar con la reproducción de audio porque no obtuvo el foco.
El método setAcceptsDelayedFocusGain(true)
le permite a tu app manejar una solicitud de foco de manera asíncrona. Con esta marca establecida, una solicitud que se realiza cuando el foco está bloqueado muestra AUDIOFOCUS_REQUEST_DELAYED
. Cuando la condición que bloqueó el foco de audio deja de existir, como cuando termina una llamada telefónica, el sistema acepta la solicitud de foco pendiente y llama a onAudioFocusChange()
para informárselo a tu app.
A fin de manejar la obtención demorada del foco, debes crear un OnAudioFocusChangeListener
con un método de devolución de llamada onAudioFocusChange()
que implemente el comportamiento deseado y llamar a setOnAudioFocusChangeListener()
para registrar el objeto de escucha.
Foco de audio en versiones previas a Android 8.0
Cuando llamas a requestAudioFocus()
, debes especificar una sugerencia de duración, que puede respetar otra app que tenga el foco en ese momento y esté reproduciendo contenido:
- Solicita el foco de audio permanente (
AUDIOFOCUS_GAIN
) cuando planees reproducir audio en el futuro próximo (por ejemplo, para reproducir música) y esperes que el elemento que tiene el foco de audio en ese momento deje de reproducir su contenido. - Solicita el foco transitorio (
AUDIOFOCUS_GAIN_TRANSIENT
) cuando planees reproducir audio durante un período breve y esperes que el elemento anterior que tenía el foco pause la reproducción. - Solicita el foco transitorio con disminución de volumen (
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
) para indicar que esperas reproducir audio solo durante un período breve y que está bien que el elemento anterior que tenía el foco siga reproduciendo si disminuye el volumen de su salida de audio. Ambas salidas se mezclan en la transmisión de audio. La disminución del volumen es adecuada en especial para las apps que usan la transmisión de audio de forma intermitente, como las que brindan rutas en auto audibles.
El método requestAudioFocus()
también requiere un AudioManager.OnAudioFocusChangeListener
. Este objeto de escucha debe crearse en la misma actividad o servicio que posee tu sesión multimedia. Implementa la devolución de llamada onAudioFocusChange()
que recibe tu aplicación cuando alguna otra app adquiere o abandona el foco de audio.
El siguiente fragmento solicita el foco de audio permanente en la transmisión STREAM_MUSIC
y registra un OnAudioFocusChangeListener
para manejar los cambios posteriores en el foco. (El objeto de escucha de cambios se analiza en Cómo responder a un cambio de foco de audio).
Kotlin
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager lateinit var afChangeListener AudioManager.OnAudioFocusChangeListener ... // Request audio focus for playback val result: Int = audioManager.requestAudioFocus( afChangeListener, // Use the music stream. AudioManager.STREAM_MUSIC, // Request permanent focus. AudioManager.AUDIOFOCUS_GAIN ) if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { // Start playback }
Java
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); AudioManager.OnAudioFocusChangeListener afChangeListener; ... // Request audio focus for playback int result = audioManager.requestAudioFocus(afChangeListener, // Use the music stream. AudioManager.STREAM_MUSIC, // Request permanent focus. AudioManager.AUDIOFOCUS_GAIN); if (result == AudioManager.AUDIOFOCUS_REQUEST_GRANTED) { // Start playback }
Cuando termines la reproducción, llama a abandonAudioFocus()
.
Kotlin
audioManager.abandonAudioFocus(afChangeListener)
Java
// Abandon audio focus when playback complete audioManager.abandonAudioFocus(afChangeListener);
De este modo, se informa al sistema que ya no necesitas el foco y se cancela el registro del OnAudioFocusChangeListener
asociado. Si solicitaste un foco transitorio, se informará a la app que haya pausado la reproducción o disminuido su volumen que puede reanudarla o restaurar el volumen.
Cómo responder a un cambio de foco de audio
Cuando una app adquiere el foco de audio, debe poder liberarlo cuando otra app lo solicite para sí misma. En ese momento, tu app recibe una llamada al método onAudioFocusChange()
en el AudioFocusChangeListener
que especificaste cuando la app llamó a requestAudioFocus()
.
El parámetro focusChange
pasado a onAudioFocusChange()
indica el tipo de cambio que se produce. Corresponde a la sugerencia de duración que usa la app que está adquiriendo el foco. Tu app debería responder de manera adecuada.
- Pérdida transitoria del foco
-
Si el cambio de foco es transitorio (
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
oAUDIOFOCUS_LOSS_TRANSIENT
), tu app debe disminuir el volumen (si no usa la disminución automática de volumen) o pausar la reproducción, y no alterar su estado más allá de eso.Durante una pérdida transitoria del foco de audio, debes seguir supervisando los cambios del foco y estar preparado para reanudar la reproducción normal al recuperar el foco. Cuando la app que bloqueaba el foco lo abandona, recibes una devolución de llamada (
AUDIOFOCUS_GAIN
). En este punto, puedes restaurar el volumen al nivel normal o reiniciar la reproducción. - Pérdida permanente del foco
- Si la pérdida del foco de audio es permanente (
AUDIOFOCUS_LOSS
), otra app está reproduciendo audio. Tu aplicación debe pausar la reproducción de inmediato, ya que no recibirá una devolución de llamadaAUDIOFOCUS_GAIN
. Para reiniciar la reproducción, el usuario debe realizar una acción explícita, como presionar el control de transporte de reproducción en una notificación o en la IU de la app.
En el siguiente fragmento de código, se demuestra cómo implementar el OnAudioFocusChangeListener
y su devolución de llamada onAudioFocusChange()
. Observa el uso de un Handler
para demorar la devolución de llamada de detención en una pérdida permanente del foco de audio.
Kotlin
private val handler = Handler() private val afChangeListener = AudioManager.OnAudioFocusChangeListener { focusChange -> when (focusChange) { AudioManager.AUDIOFOCUS_LOSS -> { // Permanent loss of audio focus // Pause playback immediately mediaController.transportControls.pause() // Wait 30 seconds before stopping playback handler.postDelayed(delayedStopRunnable, TimeUnit.SECONDS.toMillis(30)) } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> { // Pause playback } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { // Lower the volume, keep playing } AudioManager.AUDIOFOCUS_GAIN -> { // Your app has been granted audio focus again // Raise volume to normal, restart playback if necessary } } }
Java
private Handler handler = new Handler(); AudioManager.OnAudioFocusChangeListener afChangeListener = new AudioManager.OnAudioFocusChangeListener() { public void onAudioFocusChange(int focusChange) { if (focusChange == AudioManager.AUDIOFOCUS_LOSS) { // Permanent loss of audio focus // Pause playback immediately mediaController.getTransportControls().pause(); // Wait 30 seconds before stopping playback handler.postDelayed(delayedStopRunnable, TimeUnit.SECONDS.toMillis(30)); } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT) { // Pause playback } else if (focusChange == AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK) { // Lower the volume, keep playing } else if (focusChange == AudioManager.AUDIOFOCUS_GAIN) { // Your app has been granted audio focus again // Raise volume to normal, restart playback if necessary } } };
El controlador usa un Runnable
que tiene el siguiente aspecto:
Kotlin
private var delayedStopRunnable = Runnable { mediaController.transportControls.stop() }
Java
private Runnable delayedStopRunnable = new Runnable() { @Override public void run() { getMediaController().getTransportControls().stop(); } };
Para garantizar que la detención demorada no se active si el usuario reinicia la reproducción, llama a mHandler.removeCallbacks(mDelayedStopRunnable)
en respuesta a cualquier cambio de estado. Por ejemplo, llama a removeCallbacks()
en onPlay()
, onSkipToNext()
, etc., de tu devolución de llamada. También debes llamar a este método en la devolución de llamada onDestroy()
de tu servicio cuando limpies los recursos que utiliza.