Dos o más apps para Android pueden reproducir audio en la misma transmisión de salida al mismo tiempo, y el sistema mezcla todo. Si bien esto es técnicamente impresionante, puede ser muy molesto para el usuario. Para evitar que todas las apps de música reproduzcan contenido al mismo tiempo, Android presenta la idea del 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 está enfocado, puede reproducir sonido. Sin embargo, después de adquirir el foco de audio, es posible que no puedas mantenerlo hasta que termines de reproducir. Otra app puede solicitar el foco, lo que interrumpe tu espera en el foco de audio. Si eso sucede, tu app debería pausar la reproducción o bajar su volumen para que los usuarios escuchen la nueva fuente de audio con mayor facilidad.
En versiones anteriores a Android 12 (nivel de API 31), el sistema no administra el foco de audio. Por lo tanto, si bien se recomienda a los desarrolladores de apps que cumplan con los lineamientos del foco de audio, si una app continúa reproduciéndose a un volumen alto incluso después de perder el foco en un dispositivo con Android 11 (nivel de API 30) o versiones anteriores, el sistema no puede evitarlo. Sin embargo, este comportamiento de la app genera una mala experiencia del usuario y, a menudo, puede llevar a los usuarios a desinstalar la app que funciona mal.
Una app de audio bien diseñada debe administrar el foco de audio de acuerdo con estos lineamientos generales:
Llama a
requestAudioFocus()
inmediatamente antes de comenzar a reproducir y verifica que la llamada muestreAUDIOFOCUS_REQUEST_GRANTED
. Realiza la llamada arequestAudioFocus()
en la devolución de llamadaonPlay()
de tu sesión multimedia.Cuando otra app obtiene el foco de audio, detén o pausa la reproducción, o disminuye (es decir, reduce) el volumen.
Cuando se detiene la reproducción (por ejemplo, cuando la app ya no tiene nada para reproducir), abandona el foco de audio. Tu app no tiene que abandonar el foco de audio si el usuario pausa la reproducción, pero puede reanudarla más tarde.
Usa
AudioAttributes
para describir el tipo de audio que reproduce tu app. Por ejemplo, para apps que reproducen voz, especificaCONTENT_TYPE_SPEECH
.
El foco de audio se maneja de manera diferente según la versión de Android que se ejecute:
- Android 12 (nivel de API 31) o una versión posterior
- El sistema administra el foco de audio. El sistema fuerza la reproducción de audio de una app para que se atenúe cuando otra app solicita el foco de audio. El sistema también silencia la reproducción de audio cuando se recibe una llamada entrante.
- Android 8.0 (nivel de API 26) a Android 11 (nivel de API 30)
- El sistema no administra el foco de audio, que incluye algunos cambios que se introdujeron a partir de Android 8.0 (nivel de API 26).
- Android 7.1 (nivel de API 25) y versiones anteriores
- El sistema no administra el foco de audio, y las apps lo administran mediante
requestAudioFocus()
yabandonAudioFocus()
.
Foco de audio en Android 12 y versiones posteriores
Una app multimedia o de videojuego que usa foco de audio no debe reproducir audio después de que pierde el foco. En Android 12 (nivel de API 31) y versiones posteriores, el sistema aplica de manera forzosa este comportamiento. Cuando una app solicita el foco de audio mientras otra app lo tiene y se está reproduciendo, el sistema aplica un fundido de salida a la app en reproducción. La incorporación del fundido de salida proporciona una transición más fluida cuando se pasa de una app a otra.
Este comportamiento se produce cuando se cumplen las siguientes condiciones:
La primera app que se está reproduciendo actualmente cumple con todos estos criterios:
- La app tiene el atributo de uso
AudioAttributes.USAGE_MEDIA
oAudioAttributes.USAGE_GAME
. - La app solicitó correctamente el foco de audio con
AudioManager.AUDIOFOCUS_GAIN
. - La app no está reproduciendo audio con el tipo de contenido
AudioAttributes.CONTENT_TYPE_SPEECH
.
- La app tiene el atributo de uso
Una segunda app solicita el foco de audio con
AudioManager.AUDIOFOCUS_GAIN
.
Cuando se cumplen estas condiciones, el sistema de audio aplica un fundido de salida en la primera app. Una vez finalizado este fundido, el sistema notifica a la primera app la pérdida de enfoque. Los reproductores de la app permanecerán silenciados hasta que esta vuelva a solicitar el foco de audio.
Comportamientos existentes del foco de audio
También debes tener en cuenta estos otros casos que implican un cambio en el foco de audio.
Disminución automática del volumen
En Android 8.0 (nivel de API 26), se introdujo el autosilenciado automático (que reduce temporalmente el nivel de audio de una app para que otra se pueda escuchar con claridad).
Si el sistema implementa la disminución automática, no necesitas implementarla en tu app.
La disminución automática del volumen también se produce cuando una notificación de audio toma el foco de una app en reproducción. El inicio de la reproducción de notificaciones se sincroniza con el final de la rampa de disminución del volumen.
La disminución automática del volumen se produce cuando se cumplen las siguientes condiciones:
La primera app en reproducción cumple con todos estos criterios:
- La app solicitó correctamente el foco de audio con cualquier tipo de obtención de enfoque.
- La app no está reproduciendo audio con el tipo de contenido
AudioAttributes.CONTENT_TYPE_SPEECH
. - La app no configuró
AudioFocusRequest.Builder.setWillPauseWhenDucked(true)
.
Una segunda app solicita el foco de audio con
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
.
Cuando se cumplen estas condiciones, el sistema de audio disminuye el volumen de todos los reproductores activos de la primera app mientras la segunda tiene el foco. Cuando la segunda app abandona el enfoque, los quita. La primera app no recibe notificaciones cuando pierde el foco, por lo que no tiene que hacer nada.
Ten en cuenta que el autosilenciado de fondo automático no se realiza cuando el usuario escucha contenido de voz, ya que podría perderse parte del programa. Por ejemplo, las indicaciones por voz para las instrucciones sobre cómo llegar en auto no se atenúan.
Silenciar la reproducción del audio actual para las llamadas telefónicas entrantes
Algunas apps no se comportan correctamente y continúan reproduciendo audio durante llamadas telefónicas. Esta situación obliga al usuario a encontrar y silenciar la app ofensiva, o salir de ella, para escuchar su llamada. Para evitarlo, el sistema puede silenciar el audio de otras apps mientras hay una llamada entrante. El sistema invoca esta función cuando se recibe una llamada entrante y una app cumple con las siguientes condiciones:
- La app tiene el atributo de uso
AudioAttributes.USAGE_MEDIA
oAudioAttributes.USAGE_GAME
. - La app solicitó correctamente el foco de audio (cualquier ganancia de foco) y está reproduciendo audio.
Si una app continúa reproduciéndose durante la llamada, se silencia la reproducción hasta que la llamada finaliza. Sin embargo, si una app comienza a reproducir contenido durante la llamada, no se silencia el reproductor porque se presupone que el usuario inició la reproducción de forma intencional.
Foco de audio en Android 8.0 a Android 11
A partir de Android 8.0 (nivel de API 26), cuando llamas a requestAudioFocus()
, debes proporcionar un parámetro AudioFocusRequest
. El AudioFocusRequest
contiene información sobre el contexto de audio y las capacidades de tu app. El sistema usa esta información para administrar automáticamente la obtención y la pérdida del foco de audio. Para liberar el foco de audio, llama al método abandonAudioFocusRequest()
, que también toma un elemento AudioFocusRequest
como argumento. Usa la misma instancia de AudioFocusRequest
cuando solicites y abandones el foco.
Para crear una AudioFocusRequest
, usa un AudioFocusRequest.Builder
. Dado que una solicitud de foco siempre debe especificar el tipo de solicitud, este se incluye en el constructor del 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 durationHint que se usó en la llamada anterior 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 de uso de tu app. El sistema los analiza cuando una app obtiene y pierde el foco de audio. Los atributos reemplazan la noción de tipo de transmisión. En Android 8.0 (nivel de API 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. En la solicitud de foco, usa los mismos atributos 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, |
setWillPauseWhenDucked()
|
Cuando otra app solicita el foco con AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK , la app que lo tiene 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) , crea y establece un OnAudioFocusChangeListener , como se describe en Disminución automática del volumen.
|
setAcceptsDelayedFocusGain()
|
Una solicitud de foco de audio puede fallar cuando otra app bloquea el enfoque. Este método habilita la obtención demorada del foco, es decir, la capacidad de adquirir el foco de forma asíncrona cuando está disponible.
Ten en cuenta que la obtención demorada del foco solo funciona si también especificas un |
setOnAudioFocusChangeListener()
|
Solo se requiere un OnAudioFocusChangeListener si también especificas willPauseWhenDucked(true) o setAcceptsDelayedFocusGain(true) en la solicitud.
Existen 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 asociado al |
En el siguiente ejemplo, se muestra cómo usar un AudioFocusRequest.Builder
para compilar un AudioFocusRequest
y solicitar y abandonar el foco de audio:
Kotlin
// initializing variables for audio focus and playback management 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() } val focusLock = Any() var playbackDelayed = false var playbackNowAuthorized = false // requesting audio focus and processing the response 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 } } // implementing OnAudioFocusChangeListener to react to focus changes 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) { // only resume if playback is being interrupted resumeOnFocusGain = isPlaying() playbackDelayed = false } pausePlayback() } AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> { // ... pausing or ducking depends on your app } } }
Java
// initializing variables for audio focus and playback management 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(); final Object focusLock = new Object(); boolean playbackDelayed = false; boolean playbackNowAuthorized = false; // requesting audio focus and processing the response 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; } } // implementing OnAudioFocusChangeListener to react to focus changes @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) { // only resume if playback is being interrupted resumeOnFocusGain = isPlaying(); 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 (nivel de API 26), cuando otra app solicita el foco con AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
, el sistema puede disminuir el volumen y restablecer el volumen sin invocar la devolución de llamada onAudioFocusChange()
de la app.
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 libros de audio. En este caso, la app debería pausarse.
Si quieres que tu app se detenga cuando se le solicite disminuir el 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 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 otorgar una solicitud de foco de audio porque otra app "bloquea" el foco, como durante una llamada telefónica. 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)
, que permite que tu app controle una solicitud de enfoque de manera asíncrona Con esta marca establecida, una solicitud que se realiza cuando el foco está bloqueado muestra AUDIOFOCUS_REQUEST_DELAYED
. Cuando ya no existe la condición que bloqueó el foco de audio, como cuando finaliza una llamada telefónica, el sistema otorga la solicitud de foco pendiente y llama a onAudioFocusChange()
para notificar a tu app.
Para controlar la obtención demorada del enfoque, 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 Android 7.1 y versiones anteriores
Cuando llames a requestAudioFocus()
, debes especificar una sugerencia de duración, que puede ser respetada por otra app que actualmente mantenga el enfoque y se esté reproduciendo:
- Solicita el foco de audio permanente (
AUDIOFOCUS_GAIN
) cuando planees reproducir audio en el futuro inmediato (por ejemplo, al reproducir música) y esperes que el contenedor anterior del foco de audio deje de reproducir. - Solicita el foco transitorio (
AUDIOFOCUS_GAIN_TRANSIENT
) cuando esperes reproducir audio durante un período breve y esperes que el contenedor anterior pause la reproducción. - Solicita el foco transitorio con el disminución de volumen (
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
) para indicar que esperas reproducir audio durante poco tiempo y que está bien que el propietario anterior del foco siga reproduciendo si disminuye (disminuye) la salida de audio. Ambas salidas se mezclan en la transmisión de audio. El autosilenciado de fondo es particularmente adecuado para las apps que usan la transmisión de audio de forma intermitente, como para las 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 app cuando otra app adquiere o abandona el foco de audio.
En el siguiente fragmento, se solicita el foco de audio permanente en la transmisión STREAM_MUSIC
y se registra un OnAudioFocusChangeListener
para controlar los cambios posteriores en el foco de audio. (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 esta manera, se notifica al sistema que ya no necesitas enfoque y se cancela el registro del OnAudioFocusChangeListener
asociado. Si solicitaste un enfoque transitorio, se notificará a la app que haya pausado o atenuado que puede seguir reproduciendo o restablecer su 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. Cuando esto sucede, tu app recibe una llamada al método onAudioFocusChange()
en el elemento AudioFocusChangeListener
que especificaste cuando la app llamó a requestAudioFocus()
.
El parámetro focusChange
que se pasa 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 enfoque es transitorio (
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
oAUDIOFOCUS_LOSS_TRANSIENT
), tu app debe disminuir el ritmo (si no utiliza la disminución automática de volumen) o pausar la reproducción, pero mantener el mismo estado en los demás casos.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 cuando recuperes el enfoque. Cuando la app que bloquea el foco abandona el foco, recibes una devolución de llamada (
AUDIOFOCUS_GAIN
). En este punto, puedes restablecer 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 app 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 muestra cómo implementar OnAudioFocusChangeListener
y su devolución de llamada onAudioFocusChange()
. Observa el uso de un Handler
para retrasar 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 usa.