Cómo administrar el foco de audio

Dos o más apps para Android pueden reproducir audio en la misma transmisión de salida simultáneamente, y el sistema lo mezcla todo. Mientras este sea desde un punto de vista técnico, puede resultar muy agudo para el usuario. Para evitar que todos app de música en vivo al mismo tiempo, Android presenta la idea de audio enfoque. Solo una app por vez puede mantener el foco de audio.

Cuando tu app necesita transmitir audio, debe solicitar el foco de audio. Cuando tenga puede reproducir sonido. Sin embargo, después de adquirir el foco de audio, es posible que no hasta que termines de jugar. Otra aplicación puede solicitar el foco, lo que interrumpe la conservación del foco de audio. Si eso sucede, tu app debería pausarse o baja el volumen para que los usuarios escuchen la nueva fuente de audio con más facilidad.

En versiones anteriores a Android 12 (nivel de API 31), el sistema no administraba el foco de audio. Entonces: mientras que se alienta a los desarrolladores de apps a que cumplan con los lineamientos de foco de audio, Si una app continúa reproduciéndose fuerte incluso después de perder el foco de audio 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, que los usuarios desinstalen la aplicación que funciona mal.

Una app de audio bien diseñada debe administrar el foco de audio de acuerdo con estas lineamientos:

  • Llama a requestAudioFocus() justo antes de comenzar a reproducir y verifica lo siguiente: la llamada muestra AUDIOFOCUS_REQUEST_GRANTED Llamada a requestAudioFocus() en la devolución de llamada onPlay() de tu sesión multimedia.

  • Cuando otra app obtiene el foco de audio, puedes detener o pausar la reproducción, o agacharte (es decir, reducir) el volumen.

  • Cuando se detiene la reproducción (por ejemplo, cuando la app no tiene nada para reproducir) a abandonar 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, en el caso de las apps que reproducen contenido de voz, especificar CONTENT_TYPE_SPEECH

El foco de audio se controla de manera diferente según la versión de Android que se está ejecutando:

Android 12 (nivel de API 31) o versiones posteriores
El sistema administra el foco de audio. El sistema fuerza la reproducción de audio desde un app se atenúe cuando otra app solicite 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) hasta Android 11 (nivel de API 30)
El sistema no administra el foco de audio, pero incluye algunos cambios que se introdujo 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() y abandonAudioFocus()

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 el comportamiento de los usuarios. Cuando una app solicita el foco de audio, mientras que otra lo tiene se está reproduciendo, el sistema fuerza la salida de la app en reproducción. La adición de la El 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:

  1. La primera app que se está reproduciendo actualmente cumple con todos los siguientes criterios:

  2. 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. En el al final del fundido de salida, el sistema notifica a la primera app sobre la pérdida de foco. El los reproductores permanecen silenciados hasta que la app 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

Disminución automática del volumen (reducción temporal del nivel de audio de una aplicación para que otro se puede oír con claridad) se introdujo en Android 8.0 (nivel de API 26).

Si haces que el sistema implemente el autosilenciado, no tienes que hacerlo 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:

  1. La primera app en reproducción cumple con los siguientes criterios:

  2. Una segunda app solicita el foco de audio con AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK

Cuando se cumplen estas condiciones, el sistema de audio disminuye la cantidad de participantes activos de la primera aplicación, mientras que la segunda tiene el foco. Cuando abandona la segunda app no los atenúa. La primera app no recibe notificaciones cuando pierde el foco así que no tiene que hacer nada.

Ten en cuenta que la disminución automática del volumen no se realiza cuando el usuario está escuchando contenido de voz, ya que el usuario podría perderse parte del programa. Por ejemplo: las indicaciones por voz para las indicaciones en automóvil no se atenúan.

Silencia la reproducción de audio actual para las llamadas entrantes

Algunas apps no se comportan correctamente y continúan reproduciendo audio durante las 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 otros apps mientras hay una llamada entrante. El sistema invoca esta función cuando un se recibe una llamada telefónica entrante y una app cumple con las siguientes condiciones:

  • La app tiene el objeto AudioAttributes.USAGE_MEDIA o Atributo de uso de AudioAttributes.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 reproducirse durante la llamada, no se reproduce silenciadas asumiendo que el usuario comenzó la reproducción intencionalmente.

Foco de audio en Android 8.0 a Android 11

A partir de Android 8.0 (nivel de API 26), cuando llamas requestAudioFocus() debes proporcionar un parámetro AudioFocusRequest. El AudioFocusRequest contiene información sobre el contexto de audio y las capacidades de tu app. El usa esta información para administrar la ganancia y pérdida del foco de audio automáticamente. Para liberar el foco de audio, llama al método abandonAudioFocusRequest() que también toma un objeto AudioFocusRequest como su argumento. Usa la misma AudioFocusRequest cuando solicitas y lo abandonas.

Para crear un AudioFocusRequest, usa un AudioFocusRequest.Builder Dado que una solicitud de foco debe siempre especifica el tipo de solicitud, el tipo se incluye en el constructor para el compilador. Usa los métodos del compilador para establecer los otros campos de la para cada solicitud.

El campo FocusGain es obligatorio; todos los demás son opcionales.

MétodoNotas
setFocusGain() Este campo es obligatorio en todas las solicitudes. Toma los mismos valores que durationHint que se usa en la llamada a requestAudioFocus() anterior a Android 8.0: 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 las analiza cuando una app obtiene o pierde foco de audio. Atributos sustituirá la noción de tipo de transmisión. En Android 8.0 (nivel de API 26) y versiones posteriores, los tipos de transmisión para cualquier operación que no sean los controles de volumen dejaron de estar disponibles. Usa los mismos atributos en la solicitud de foco que usas en tu reproductor de audio (como como se muestra en el ejemplo que sigue a esta tabla).

Usa un AudioAttributes.Builder para especificar atributos primero y, luego, usa este método para asignar los atributos al para cada solicitud.

Si no se especifica, el valor predeterminado de AudioAttributes es AudioAttributes.USAGE_MEDIA.

setWillPauseWhenDucked() Cuando otra app solicita el foco con AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, la app enfocada no generalmente reciben un onAudioFocusChange() porque el sistema puede realizar disminuyendo el volumen por sí solo. Cuando necesitas pausar la reproducción que disminuir el volumen, llama a setWillPauseWhenDucked(true), crea y establece un OnAudioFocusChangeListener, como se describe en la configuración automática autosilenciado de fondo.
setAcceptsDelayedFocusGain() Una solicitud de foco de audio puede fallar cuando otra app bloquea el foco. Este método habilita la ganancia demorada del foco, es decir, la capacidad para que se enfoque de manera asíncrona cuando esté disponible.

Ten en cuenta que la ganancia del foco demorada solo funciona si también especificas un AudioManager.OnAudioFocusChangeListener en la solicitud de audio, ya que tu app necesita y recibirás la devolución de llamada para saber que se otorgó el foco.

setOnAudioFocusChangeListener() Un OnAudioFocusChangeListener solo es obligatorio 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 elemento y otro sin controlador. El controlador es el subproceso en el que se ejecuta el objeto de escucha. Si no especifiques un controlador, el controlador asociado a la instancia Looper está en uso.

En el siguiente ejemplo, se muestra cómo usar un AudioFocusRequest.Builder para compilar un AudioFocusRequest, y solicita y abandona 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 restablecerlo sin invocar la devolución de llamada onAudioFocusChange() de la app.

A pesar de que la disminución automática del volumen es un comportamiento aceptable para la reproducción de música y videos no es útil cuando se reproduce contenido hablado, por ejemplo, en un app de audiolibros. En este caso, la app debería pausarse.

Si quieres que tu app pause la reproducción cuando se le pida que agache en lugar de bajar el volumen, crea una OnAudioFocusChangeListener con Un método de devolución de llamada onAudioFocusChange() que implementa el comportamiento deseado de pausa o reanudación Llama a setOnAudioFocusChangeListener() para registrar el objeto de escucha y llama 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

A veces, el sistema no puede otorgar una solicitud de foco de audio "bloqueado" por otra aplicación, por ejemplo, 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 no te enfocas en eso.

El método setAcceptsDelayedFocusGain(true), que permite que tu app controle una solicitud de enfoque de forma asíncrona. Con esta marca establecida, se hace una solicitud cuando el foco está bloqueado muestra AUDIOFOCUS_REQUEST_DELAYED. Cuando la condición que bloqueó el audio enfoque ya no existe, por ejemplo, cuando finaliza una llamada telefónica, el sistema otorga la solicitud de enfoque pendiente y llama a onAudioFocusChange() para notificarle a tu .

Para manejar la ganancia demorada del foco, debes crear un OnAudioFocusChangeListener con un método de devolución de llamada onAudioFocusChange() que implementa el comportamiento deseado y registra el objeto de escucha llamando setOnAudioFocusChangeListener()

Foco de audio en Android 7.1 y versiones anteriores

Cuando llames requestAudioFocus() debes especificar una sugerencia de duración, que puede ser respetado por otra aplicación que actualmente mantiene el foco y la reproducción:

  • Solicita el foco de audio permanente (AUDIOFOCUS_GAIN) cuando quieras reproducir audio a corto plazo (por ejemplo, cuando reproduces música) y esperas que el soporte anterior del foco de audio para detener la reproducción.
  • Solicita el enfoque transitorio (AUDIOFOCUS_GAIN_TRANSIENT) cuando quieras jugar audio por poco tiempo y esperas que el elemento anterior se detenga en reproducción.
  • Solicita el enfoque transitorio mediante el disminución de volumen (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) para indicar que esperas reproducir audio por un período breve y que está bien que el propietario anterior del foco se mantenga CANNOT TRANSLATE (disminuye) la salida de audio. Ambas salidas de audio están mezcladas en la reproducción de audio. El autosilenciado de fondo es particularmente adecuado para apps que usan la de forma intermitente, como para indicaciones de conducción audibles.

El método requestAudioFocus() también requiere un objeto AudioManager.OnAudioFocusChangeListener. Este objeto de escucha debe ser crearse en la misma actividad o servicio que posee tu sesión multimedia. Integra implementa la devolución de llamada onAudioFocusChange() que recibe tu app 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 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 abandonAudioFocus()

Kotlin

audioManager.abandonAudioFocus(afChangeListener)

Java

// Abandon audio focus when playback complete
audioManager.abandonAudioFocus(afChangeListener);

De esta forma, se notifica al sistema que ya no necesitas el foco y se cancela el registro de la OnAudioFocusChangeListener asociado. Si solicitaste el enfoque transitorio, se notificará a la app que está en pausa o atenuada que puede seguir reproduciéndose. 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 solicita foco de audio para sí mismo. Cuando esto sucede, tu app recibe una llamada a la onAudioFocusChange() en 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 está produciendo. Le corresponde con la sugerencia de duración que usa la app que adquiere el foco. Tu app debe responder de forma adecuada.

Pérdida transitoria del foco
Si el cambio de enfoque es transitorio (AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK o AUDIOFOCUS_LOSS_TRANSIENT), tu app debe disminuir (si no confías en ti) activar el disminución automática de volumen) o pausar la reproducción, pero de lo contrario, mantendrán el mismo estado.

Durante una pérdida transitoria del foco de audio, debes seguir supervisando los cambios en foco de audio y prepárate para reanudar la reproducción normal cuando recuperes no te enfocas en eso. Cuando la app de bloqueo abandona el enfoque, recibes una devolución de llamada (AUDIOFOCUS_GAIN). En este momento, puedes restablecer el volumen al nivel normal o reinicia la reproducción.

Pérdida permanente del foco
Si la pérdida del foco de audio es permanente (AUDIOFOCUS_LOSS), se aplica otra app reproduciendo audio. Tu app debería pausar la reproducción de inmediato, ya que nunca recibir una devolución de llamada AUDIOFOCUS_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 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 la uso de un Handler para retrasar la devolución de llamada de detención en una pérdida permanente de audio no te enfocas en eso.

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 asegurarte de que la detención retrasada no se active si el usuario reinicia la reproducción, llama mHandler.removeCallbacks(mDelayedStopRunnable) en respuesta a cualquier estado cambios. Por ejemplo, llama a removeCallbacks() en el onPlay() de tu devolución de llamada. onSkipToNext(), etc. Debes llamar a este método en el directorio Es la devolución de llamada onDestroy() cuando limpies los recursos que usa tu servicio.