Dois ou mais apps Android podem reproduzir áudio no 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 o conceito de foco 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 tem foco, pode tocar som. No entanto, depois de adquirir o foco de áudio, talvez não seja possível mantê-lo até que você termine de tocar. Outro app pode solicitar o foco, o que interrompe o foco de áudio. Se isso acontecer, o app precisará pausar a reprodução ou diminuir 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 é gerenciado pelo sistema. Portanto, embora os desenvolvedores de apps sejam incentivados a obedecer às diretrizes de foco de áudio, se um app continuar tocando em volume alto mesmo depois de perder o foco de áudio em um dispositivo com o Android 11 (nível 30 da API) ou versões anteriores, o sistema não poderá impedir isso. No entanto, esse comportamento do app resulta em uma experiência ruim para o usuário e pode fazer com que ele desinstale o app com comportamento inadequado.
Um app de áudio bem projetado precisa gerenciar o foco de áudio de acordo com estas diretrizes gerais:
Chame
requestAudioFocus()imediatamente antes de começar a tocar 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 o foco de áudio, interrompa ou pause a reprodução ou reduza o volume.
Quando a reprodução for interrompida (por exemplo, quando o app não tiver mais nada para reproduzir), abandone o foco de áudio. O app não precisa abandonar o foco de áudio se o usuário pausar a reprodução, mas puder 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.
O foco de áudio é processado de forma diferente, dependendo da versão do Android em execução:
- Android 12 (nível 31 da API) ou mais recente
- O foco de áudio é gerenciado pelo sistema. O sistema força a reprodução de áudio de um app a desaparecer gradualmente quando outro app solicita o foco de áudio. O sistema também silencia a reprodução de áudio quando uma chamada é recebida.
- Do Android 8.0 (nível 26 da API) ao Android 11 (nível 30 da API)
- O foco de áudio não é gerenciado pelo sistema, mas inclui algumas mudanças que foram introduzidas a partir do Android 8.0 (nível 26 da API).
- Android 7.1 (nível 25 da API) e versões anteriores
- O foco de áudio não é gerenciado pelo sistema, e os apps gerenciam o foco de áudio usando
requestAudioFocus()eabandonAudioFocus().
Foco 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 aplica esse comportamento. Quando um app solicita o foco de áudio enquanto outro app tem o foco e está tocando, o sistema força o app que está tocando a diminuir o volume. 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á em execução, atende a todos estes critérios:
- O app tem o atributo de uso
AudioAttributes.USAGE_MEDIAouAudioAttributes.USAGE_GAME. - O app solicitou o foco 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 atributo de uso
Um segundo app solicita o foco de áudio com
AudioManager.AUDIOFOCUS_GAIN.
Quando essas condições são atendidas, o sistema de áudio diminui o volume do primeiro app. No final da diminuição, o sistema notifica o primeiro app sobre a perda de foco. Os players do app permanecem silenciados até que o app solicite o foco 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 deixar que o sistema implemente o ducking, você não precisa fazer isso no seu 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á em reprodução, atende a todos estes critérios:
- O app solicitou o foco 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 o foco de áudio com
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK.
Quando essas condições são atendidas, o sistema de áudio reduz o volume de todos os players ativos do primeiro app enquanto o segundo app está em foco. Quando o segundo app abandona o foco, ele cancela a redução. O primeiro app não é notificado quando perde o foco, então não precisa fazer nada.
A redução automática não é realizada quando o usuário está ouvindo conteúdo de fala, porque ele pode perder parte 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 tocando áudio durante ligações telefônicas. Essa situação força o usuário a encontrar e silenciar ou fechar o app que está causando o problema 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 e um app atende a estas condições:
- O app tem o atributo de uso
AudioAttributes.USAGE_MEDIAouAudioAttributes.USAGE_GAME. - O app solicitou o foco de áudio (qualquer ganho de foco) e está reproduzindo á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 tocar durante a chamada, o player não será silenciado, supondo que o usuário tenha iniciado a reprodução intencionalmente.
Foco em áudio do Android 8.0 ao Android 11
A partir do Android 8.0 (nível 26 da API), ao chamar
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 o ganho e a perda de foco de áudio
automaticamente. Para liberar o foco de áudio, chame o método
abandonAudioFocusRequest(),
que também usa um AudioFocusRequest como argumento. Use a mesma
instância de AudioFocusRequest ao solicitar e abandonar o foco.
Para criar um AudioFocusRequest, use um
AudioFocusRequest.Builder. Como uma solicitação de foco precisa
sempre especificar o tipo, ele é incluído no construtor
do 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 anterior ao Android 8.0 para requestAudioFocus():
AUDIOFOCUS_GAIN, AUDIOFOCUS_GAIN_TRANSIENT,
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK ou AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE.
|
setAudioAttributes()
|
AudioAttributes descreve o caso de uso do app. O
sistema os analisa quando um app ganha e perde o foco de áudio. Os atributos
substituem a noção de tipo de stream. No Android 8.0 (nível 26 da API) e versões mais recentes,
os tipos de stream para qualquer operação que não seja de controle de volume foram descontinuados. Use
os mesmos atributos na solicitação de foco que você usa no player de áudio, conforme
mostrado no exemplo após esta tabela.
Use um
Se não for especificado, |
setWillPauseWhenDucked()
|
Quando outro app solicita o foco com
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, o app que tem o foco geralmente
não recebe um callback
onAudioFocusChange(),
porque o sistema pode fazer a
redução por conta própria. Quando você 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.
|
setAcceptsDelayedFocusGain()
|
Uma solicitação de foco de áudio pode falhar quando o foco está bloqueado por outro app.
Esse método permite o ganho de foco atrasado: a capacidade
de adquirir o foco de forma assíncrona quando ele estiver disponível.
O ganho de foco atrasado só funciona se você também especificar 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 outro sem um argumento de gerenciador. O gerenciador é a sequência em que o listener é executado. Se você
não especificar um gerenciador, o gerenciador associado ao |
O exemplo a seguir mostra como usar um AudioFocusRequest.Builder para criar
um AudioFocusRequest e solicitar e abandonar o foco 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 o foco com
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, o sistema pode reduzir e restaurar o volume
sem invocar o callback onAudioFocusChange() do app.
Embora o ducking automático seja um comportamento aceitável para apps de reprodução de música e vídeo, ele não é útil ao reproduzir conteúdo falado, como em um app de audiolivros. Nesse caso, o app precisa ser pausado.
Se você quiser que o app pause quando solicitado a reduzir o volume, em vez de diminuir o volume, crie um OnAudioFocusChangeListener com
um método de callback onAudioFocusChange() que implemente o comportamento de pausa/retomada desejado.
Chame setOnAudioFocusChangeListener() para registrar o listener e chame
setWillPauseWhenDucked(true)
para instruir o sistema a usar seu callback em vez de realizar a redução automática.
Ganho atrasado de seleção
Às vezes, o sistema não pode conceder uma solicitação de foco de áudio porque o foco está
"bloqueado" por outro app, como durante uma ligação. Nesse caso,
requestAudioFocus() retorna AUDIOFOCUS_REQUEST_FAILED. Quando isso acontece,
o app não pode continuar com a reprodução de áudio porque não recebeu
o foco.
O método setAcceptsDelayedFocusGain(true), que permite que o app processe uma solicitação de foco
de forma assíncrona. Com essa flag definida, uma solicitação feita quando o foco está bloqueado
retorna AUDIOFOCUS_REQUEST_DELAYED. Quando a condição que bloqueou o foco de áudio
não existir mais, como quando uma ligação é encerrada, o sistema
concede a solicitação de foco pendente e chama onAudioFocusChange() para notificar o
app.
Para processar o ganho de foco atrasado, crie um
OnAudioFocusChangeListener com um método de callback onAudioFocusChange() que
implemente o comportamento desejado e registre o listener chamando
setOnAudioFocusChangeListener().
Foco de áudio no Android 7.1 e versões anteriores
Ao chamar
requestAudioFocus(),
é preciso especificar uma dica de duração, que pode
ser respeitada por outro app que esteja em foco e em reprodução:
- Solicite o foco de áudio permanente (
AUDIOFOCUS_GAIN) quando planejar tocar áudio por um período indefinido (por exemplo, ao tocar música) e esperar que o detentor anterior do foco de áudio pare de tocar. - Solicite o foco temporário (
AUDIOFOCUS_GAIN_TRANSIENT) quando você espera tocar áudio por um curto período e espera que o detentor anterior pause a reprodução. - Solicite o foco temporário com ducking
(
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) para indicar que você espera tocar áudio por um curto período e que não há problema se o proprietário do foco anterior continuar tocando se ele "reduzir" (diminuir) a saída de áudio. As duas saídas de áudio são mixadas no stream de áudio. O ducking é particularmente adequado para apps que usam o stream de áudio de forma intermitente, como para rotas de carro audíveis.
O método requestAudioFocus() também requer 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
outro app adquire ou abandona o foco de áudio.
O snippet a seguir solicita o foco de áudio permanente no stream
STREAM_MUSIC e registra um OnAudioFocusChangeListener para processar
as mudanças subsequentes no foco de áudio. O listener de mudança é discutido em
Como responder a uma mudança de foco 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 do foco e cancela o registro do
OnAudioFocusChangeListener associado. Se você solicitou o foco temporário,
isso vai notificar um app que foi pausado ou teve o volume reduzido de que ele pode continuar a reprodução ou
restaurar o volume.
Como responder a uma mudança de seleção de áudio
Quando um app adquire o foco de áudio, ele precisa ser capaz de liberá-lo quando outro app
solicitar o foco de áudio para si. Quando isso acontece, seu app
recebe uma chamada para o método
onAudioFocusChange()
no AudioFocusChangeListener
que você especificou 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á recebendo o foco. Seu app precisa
responder adequadamente.
- Perda transitória de seleção
-
Se a mudança de foco for temporária (
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKouAUDIOFOCUS_LOSS_TRANSIENT), o app precisará reduzir o volume (se você não estiver usando a redução automática) ou pausar a reprodução, mas manter o mesmo estado.Durante uma perda temporária de foco de áudio, continue monitorando as mudanças no foco e se prepare para retomar a reprodução normal quando recuperar o foco. Quando o app que bloqueia o foco o abandona, você recebe um callback (
AUDIOFOCUS_GAIN). Nesse momento, é possível restaurar o volume para o nível normal ou reiniciar a reprodução. - Perda permanente de seleção
-
Se a perda de foco de áudio for permanente (
AUDIOFOCUS_LOSS), outro app está reproduzindo áudio. O app precisa pausar a reprodução imediatamente, já que não vai receber um callbackAUDIOFOCUS_GAIN. Para reiniciar a reprodução, o usuário precisa realizar uma ação explícita, como pressionar o controle de transporte de reprodução em uma notificação ou na IU do app.
O snippet de código abaixo demonstra como implementar o
OnAudioFocusChangeListener e o callback onAudioFocusChange(). Observe o
uso de um Handler para atrasar o callback de parada em uma perda permanente de foco
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 iniciada se o usuário reiniciar a reprodução, chame
mHandler.removeCallbacks(mDelayedStopRunnable) em resposta a qualquer mudança
de estado. Por exemplo, chame removeCallbacks() em onPlay(),
onSkipToNext() etc. do callback. Chame também esse método no callback
onDestroy() do serviço ao limpar os recursos usados por ele.