Dois ou mais apps para Android podem reproduzir áudio para o mesmo stream de saída simultaneamente, e o sistema mistura tudo. Embora isso seja tecnicamente impressionante, pode ser muito irritante para o usuário. Para evitar que todos os apps de música sejam reproduzidos ao mesmo tempo, o Android apresenta a ideia de seleção de áudio. Somente um app pode manter a seleção de áudio por vez.
Quando seu app precisa da saída de áudio, é necessário solicitar a seleção de áudio. Quando ele está em foco, pode reproduzir som. Porém, depois que você consegue a seleção de áudio, talvez não seja possível mantê-la até que reprodução termine. Outro app pode solicitar a seleção de áudio, o que força a interrupção da sua posse da seleção de áudio. Se isso ocorrer, o app precisará pausar a reprodução ou reduzir o volume para que os usuários ouçam a nova fonte de áudio com mais facilidade.
Antes do Android 12 (nível 31 da API), o foco de áudio não era gerenciado pelo sistema. Portanto, embora os desenvolvedores de apps sejam incentivados a obedecer às diretrizes de seleção de áudio, se um app continuar tocando em volume alto, mesmo depois de perder a seleção de áudio em um dispositivo com o Android 11 (nível da API 30) ou versões anteriores, o sistema não poderá impedir isso. No entanto, esse comportamento do app leva a uma experiência ruim do usuário e, muitas vezes, faz com que os usuários desinstalem o app com problemas.
Um app de áudio bem projetado precisa gerenciar a seleção de áudio de acordo com estas diretrizes gerais:
Chame
requestAudioFocus()imediatamente antes de começar a reproduzir e verifique se a chamada retornaAUDIOFOCUS_REQUEST_GRANTED. Faça a chamada pararequestAudioFocus()no callbackonPlay()da sessão de mídia.Quando outro app receber a seleção de áudio, pare ou pause a reprodução ou reduza o volume.
Quando a reprodução parar (por exemplo, quando o app não tiver mais nada para reproduzir), desative a seleção de áudio. O app não precisa desativar a seleção de áudio se o usuário pausar a reprodução, mas poderá retomá-la mais tarde.
Use
AudioAttributespara descrever o tipo de áudio que o app está reproduzindo. Por exemplo, para apps que reproduzem fala, especifiqueCONTENT_TYPE_SPEECH.
A seleção de áudio é gerenciada de forma diferente dependendo da versão do Android em execução:
- Android 12 (nível 31 da API) ou mais recente
- A seleção de áudio é gerenciada pelo sistema. O sistema força o esmaecimento da reprodução de áudio de um app quando outro solicita a seleção de áudio. O sistema também silencia a reprodução de áudio quando uma chamada recebida é recebida.
- Android 8.0 (nível 26 da API) ao Android 11 (nível 30 da API)
- A seleção de áudio não é gerenciada pelo sistema, mas inclui algumas mudanças introduzidas a partir do Android 8.0 (nível 26 da API).
- Android 7.1 (nível 25 da API) e versões anteriores
- A seleção de áudio não é gerenciada pelo sistema, e os apps gerenciam a seleção de áudio usando
requestAudioFocus()eabandonAudioFocus().
Seleção de áudio no Android 12 e versões mais recentes
Um app de mídia ou jogo que usa a seleção de áudio não precisa reproduzir áudio depois de perder o foco. No Android 12 (nível 31 da API) e versões mais recentes, o sistema impõe esse comportamento. Quando um app solicita a seleção de áudio enquanto outro app está em foco e em reprodução, o sistema força o esmaecimento do app ativo. A adição do esmaecimento oferece uma transição mais suave ao passar de um app para outro.
Esse comportamento de esmaecimento acontece quando as condições a seguir são atendidas:
O primeiro app, que está sendo reproduzido no momento, atende a todos esses critérios:
- O app tem o
AudioAttributes.USAGE_MEDIAouAudioAttributes.USAGE_GAMEatributo de uso. - O app solicitou com sucesso a seleção de áudio com
AudioManager.AUDIOFOCUS_GAIN. - O app não está reproduzindo áudio com o tipo de conteúdo
AudioAttributes.CONTENT_TYPE_SPEECH.
- O app tem o
Um segundo app solicita a seleção de áudio com
AudioManager.AUDIOFOCUS_GAIN.
Quando essas condições são atendidas, o sistema de áudio esmaece o primeiro app. No final do esmaecimento, o sistema notifica o primeiro app da perda do foco. As reproduções do app permanecem silenciadas até que o app solicite a seleção de áudio novamente.
Comportamentos de seleção de áudio existentes
Você também precisa conhecer esses outros casos que envolvem um interruptor na seleção de áudio.
Redução automática de volume
A redução automática (redução temporária do nível de áudio de um app para que outro possa ser ouvido com clareza) foi introduzida no Android 8.0 (nível 26 da API).
Ao usar o sistema para implementar a redução, você não precisa implementá-la no app.
A redução automática também ocorre quando uma notificação de áudio recebe o foco no lugar de um app em reprodução. O início da reprodução da notificação é sincronizado com o final da rampa de redução.
A redução automática ocorre quando as seguintes condições são atendidas:
O primeiro app, que está sendo reproduzido no momento, atende a todos esses critérios:
- O app solicitou com sucesso a seleção de áudio com qualquer tipo de ganho de foco.
- O app não está reproduzindo áudio com o tipo de conteúdo
AudioAttributes.CONTENT_TYPE_SPEECH. - O app não definiu
AudioFocusRequest.Builder.setWillPauseWhenDucked(true).
Um segundo app solicita a seleção de áudio com
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK.
Quando essas condições são atendidas, o sistema de áudio abaixará o volume de todas as reproduções de áudio ativas do primeiro app enquanto o segundo estiver em foco. Quando o segundo app for abandonado, o áudio do primeiro retornará. O primeiro app não é notificado quando perde o foco, então não precisa fazer nada.
A redução de áudio automática não é realizada quando o usuário está ouvindo conteúdo de fala, porque o usuário pode perder alguns aspectos do programa. Por exemplo, a orientação por voz para rotas de carro não é reduzida.
Silenciar a reprodução de áudio atual para chamadas telefônicas recebidas
Alguns apps não se comportam corretamente e continuam reproduzindo áudio durante chamadas telefônicas. Essa situação força o usuário a encontrar e silenciar ou sair do app ofensivo para ouvir a chamada. Para evitar isso, o sistema pode silenciar o áudio de outros apps enquanto houver uma chamada recebida. O sistema invoca esse recurso quando uma chamada telefônica recebida é recebida e um app atende a estas condições:
- O app tem o atributo de uso
AudioAttributes.USAGE_MEDIAouAudioAttributes.USAGE_GAME. - O app solicita com sucesso a seleção de áudio (qualquer ganho de foco) e está tocando áudio.
Se um app continuar sendo reproduzido durante a chamada, a reprodução dele será silenciada até a chamada ser encerrada. No entanto, se um app começar a reproduzir durante a chamada, esse player não será silenciado, presumindo que o usuário iniciou a reprodução intencionalmente.
Seleção de áudio no Android 8.0 ao Android 11
A partir do Android 8.0 (nível 26 da API), quando você chama
requestAudioFocus()
, é necessário fornecer um parâmetro AudioFocusRequest. O AudioFocusRequest contém informações sobre o contexto de áudio e os recursos do app. O sistema usa essas informações para gerenciar automaticamente o ganho e a perda da seleção de áudio. Para liberar a seleção de áudio, chame o método
abandonAudioFocusRequest()
que também usa um AudioFocusRequest como argumento. Use a mesma instância AudioFocusRequest ao solicitar e abandonar a seleção.
Para criar um AudioFocusRequest, use um
AudioFocusRequest.Builder. Como uma solicitação de seleção sempre precisa especificar o tipo, ele é incluído no construtor para o builder. Use os métodos do builder para definir os outros campos da solicitação.
O campo FocusGain é obrigatório. Todos os outros são opcionais.
| Método | Observações |
|---|---|
setFocusGain()
|
Este campo é obrigatório em todas as solicitações. Ele usa os mesmos valores que
o durationHint usado na chamada pré-Android 8.0 para requestAudioFocus():
AUDIOFOCUS_GAIN, AUDIOFOCUS_GAIN_TRANSIENT,
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK ou AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE.
|
setAudioAttributes()
|
AudioAttributes descrevem o caso de uso do app. O sistema olha para eles quando um app ganha e perde a seleção de áudio. Os atributos substituem a noção de tipo de stream. No Android 8.0 (nível 26 da API) ou versões posteriores, os tipos de stream para qualquer operação diferente de controles de volume estão descontinuados. Use os mesmos atributos na solicitação de seleção que você usa no player de áudio (conforme mostrado no exemplo a seguir nesta tabela).
Use
Se não for especificado, |
setWillPauseWhenDucked()
|
Quando outro app solicita a seleção com
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, o app selecionado não
costuma receber um
onAudioFocusChange()
callback porque o sistema pode reduzir o
volume do áudio por si só. Quando precisar pausar a reprodução em vez
de reduzir o volume, chame setWillPauseWhenDucked(true) e crie e defina um
OnAudioFocusChangeListener, conforme descrito em redução automática
de volume.
|
setAcceptsDelayedFocusGain()
|
Uma solicitação de seleção de áudio pode falhar quando bloqueada por outro app.
Esse método permite ganho atrasado de seleção: a capacidade
de adquirir seleção de maneira assíncrona quando ele estiver disponível.
Observe que o ganho atrasado de seleção só funciona se você também especifica um |
setOnAudioFocusChangeListener()
|
Um OnAudioFocusChangeListener só é necessário se você também especificar willPauseWhenDucked(true) ou setAcceptsDelayedFocusGain(true) na solicitação.
Há dois métodos para definir o listener: um com e um sem um argumento de gerenciador. O gerenciador é a sequência em que o listener é executado. Se você não especificar um gerenciador, será usado aquele que está associado ao |
O exemplo a seguir mostra como usar um AudioFocusRequest.Builder para criar
um AudioFocusRequest e solicitar e abandonar a seleção de áudio:
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; } } }
Redução automática de volume
No Android 8.0 (nível 26 da API), quando outro app solicita foco com AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, o sistema pode reduzir e restaurar o volume sem invocar o callback onAudioFocusChange() do app.
Embora a redução automática de volume seja um comportamento aceitável para apps de reprodução de música e vídeo, ela não é útil para reproduzir conteúdo falado, como em um app de audiolivro. Nesse caso, o app precisa pausar.
Se você quiser que seu app pause quando receber uma solicitação, em vez de reduzir o volume, crie um método de callback onAudioFocusChange() com OnAudioFocusChangeListener que implemente o comportamento de pausa/retomada desejado.
Chame setOnAudioFocusChangeListener() para registrar o listener e chame
setWillPauseWhenDucked(true)
para dizer ao sistema para usar o callback em vez de executar a redução automática de volume.
Ganho atrasado de seleção
Às vezes, o sistema não pode conceder uma solicitação de foco de áudio porque ela está "bloqueada" por outro app, por exemplo, durante uma chamada telefônica. Nesse caso, requestAudioFocus() retorna AUDIOFOCUS_REQUEST_FAILED. Quando isso acontece, o app não pode prosseguir com a reprodução de áudio porque não conseguiu a seleção.
O método, setAcceptsDelayedFocusGain(true), permite que o app processe uma solicitação de seleção
de forma assíncrona. Com essa sinalização definida, uma solicitação feita quando a seleção de áudio está bloqueada retorna AUDIOFOCUS_REQUEST_DELAYED. Quando a condição que bloqueou a seleção de áudio não existir mais, por exemplo, quando uma chamada telefônica termina, o sistema concede a solicitação de seleção pendente e chama onAudioFocusChange() para notificar o app.
Para gerenciar o ganho de seleção atrasado, crie um
OnAudioFocusChangeListener com um onAudioFocusChange() método de callback que
implemente o comportamento desejado e registre o listener chamando
setOnAudioFocusChangeListener().
Seleção de áudio no Android 7.1 e versões anteriores
Quando chamar
requestAudioFocus()
especifique uma dica de duração, que pode
ser atendida por outro app que esteja mantendo a seleção e a reprodução no momento:
- Quando você planeja tocar áudio no futuro previsível (por exemplo, ao tocar música) e espera que a seleção anterior de áudio pare a reprodução, solicite a seleção de áudio permanente (
AUDIOFOCUS_GAIN). - Quando você espera tocar áudio por pouco tempo e que a seleção anterior pause a reprodução, solicite a seleção de áudio temporária (
AUDIOFOCUS_GAIN_TRANSIENT). - Solicite a seleção de áudio temporária com redução do volume (
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) para indicar que você espera tocar áudio apenas por pouco tempo e quando o proprietário da seleção anterior pode manter a reprodução se reduzir o volume da saída de áudio. As duas saídas de áudio são misturadas no stream de áudio. A redução de volume é particularmente adequada para apps que usam o stream de áudio de maneira intermitente, como para rotas de carro audíveis.
O método requestAudioFocus() também exige um AudioManager.OnAudioFocusChangeListener. Esse listener precisa ser criado na mesma atividade ou serviço que possui sua sessão de mídia. Ele implementa o callback onAudioFocusChange() que o app recebe quando algum outro app adquire ou abandona a seleção de áudio.
O snippet a seguir solicita a seleção de áudio permanente no stream STREAM_MUSIC e registra um OnAudioFocusChangeListener para gerenciar as alterações subsequentes na seleção de áudio. O listener de mudança é discutido em
Como responder a uma mudança de seleção de áudio.
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 }
Quando terminar a reprodução, chame
abandonAudioFocus().
Kotlin
audioManager.abandonAudioFocus(afChangeListener)
Java
// Abandon audio focus when playback complete audioManager.abandonAudioFocus(afChangeListener);
Isso notifica o sistema de que você não precisa mais de foco e cancela o registro do OnAudioFocusChangeListener associado. Se você solicitou uma seleção temporária, notificará um app que pausou ou reduziu o volume que ele pode continuar a tocar ou restaurar o volume.
Como responder a uma mudança de seleção de áudio
Quando um app adquire a seleção de áudio, ele precisa ser capaz de liberá-la quando outro app a solicita. Quando isso acontece, o app
recebe uma chamada para o
onAudioFocusChange()
método no AudioFocusChangeListener
especificado quando o app chamou requestAudioFocus().
O parâmetro focusChange transmitido para onAudioFocusChange() indica o tipo de mudança que está acontecendo. Ele corresponde à dica de duração usada pelo app que está adquirindo a seleção. O app precisa responder adequadamente.
- Perda transitória de seleção
-
Se a mudança de seleção for temporária (
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKouAUDIOFOCUS_LOSS_TRANSIENT), o app precisa reduzir o volume (se você não depender da redução automática de volume) ou pausar a reprodução, mas manter o mesmo estado.Durante uma perda temporária de seleção de áudio, você precisa continuar a monitorar as mudanças de seleção de áudio e estar preparado para retomar a reprodução normal quando recuperá-la. Quando o app que está bloqueando a seleção a abandonar, você receberá um callback (
AUDIOFOCUS_GAIN). Nesse ponto, você poderá restaurar o volume para o nível normal ou reiniciar a reprodução. - Perda permanente de seleção
-
Se a perda da seleção de áudio é permanente (
AUDIOFOCUS_LOSS), outro app está reproduzindo áudio. Seu app precisa pausar a reprodução imediatamente, porque não receberá um callbackAUDIOFOCUS_GAIN. Para reiniciar a reprodução, o usuário precisa executar uma ação explícita, por exemplo, pressionar o controle de transporte de reprodução em uma notificação ou na IU do app.
O snippet de código a seguir demonstra como implementar o OnAudioFocusChangeListener e seu callback onAudioFocusChange(). Observe o uso de um Handler para atrasar o callback de parada em uma perda permanente de seleção de áudio.
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 } } };
O gerenciador usa um Runnable que tem esta aparência:
Kotlin
private var delayedStopRunnable = Runnable { mediaController.transportControls.stop() }
Java
private Runnable delayedStopRunnable = new Runnable() { @Override public void run() { getMediaController().getTransportControls().stop(); } };
Para garantir que a parada atrasada não seja ativada se o usuário reiniciar a reprodução, chame mHandler.removeCallbacks(mDelayedStopRunnable) em resposta a qualquer mudança de estado. Por exemplo, chame removeCallbacks() no onPlay(), onSkipToNext(), etc. do callback. Você também precisa chamar esse método no callback onDestroy() do serviço ao limpar os recursos usados pelo serviço.