Dois ou mais apps 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 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 fica em foco, ele pode tocar som. No entanto, depois de adquirir a seleção de áudio, pode não ser possível mantê-la até que a reprodução termine. Outro app pode solicitar a seleção, que encerra a suspensão da seleção de áudio. Se isso acontecer, o app precisará pausar a reprodução ou diminuir o volume para permitir que os usuários ouçam a nova fonte de áudio com mais facilidade.
Antes do Android 12 (nível 31 da API), a seleção de áudio não era gerenciada pelo sistema. 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 (API de nível 30) ou versões anteriores, o sistema não poderá evitar isso. No entanto, esse comportamento do app leva a uma má experiência do usuário e pode levar os usuários a desinstalar o app que está apresentando um comportamento inadequado.
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 reprodução 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 diminua o volume (ou seja, reduza) o volume.
Quando a reprodução for interrompida (por exemplo, quando o app não tiver mais nada para tocar), abandone a seleção de áudio. Seu app não precisa abandonar a seleção de áudio se o usuário pausar a reprodução, mas puder retomá-la mais tarde.
Use
AudioAttributes
para descrever o tipo de áudio que seu app está tocando. Por exemplo, para apps que reproduzem fala, especifiqueCONTENT_TYPE_SPEECH
.
A seleção de áudio é processada de maneira 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 a reprodução de áudio de um app a esmaecer quando outro app solicita a seleção de áudio. O sistema também silencia a reprodução de áudio quando uma ligação é 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 no Android 8.0 (nível 26 da API).
- Android 7.1 (nível 25 da API) e anteriores
- A seleção de áudio não é gerenciada pelo sistema, e os apps gerenciam a seleção 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 aplica esse comportamento. Quando um app solicita seleção de áudio enquanto outro app está em foco e em reprodução, o sistema força o esmaecimento do app em reprodução. O acréscimo do esmaecimento proporciona 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 em uso atende a todos estes critérios:
- O app tem o atributo de uso
AudioAttributes.USAGE_MEDIA
ouAudioAttributes.USAGE_GAME
. - O app solicitou a seleção de áudio com
AudioManager.AUDIOFOCUS_GAIN
. - O app não está tocando áudio com o tipo de conteúdo
AudioAttributes.CONTENT_TYPE_SPEECH
.
- O app tem o atributo de uso
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 sobre a perda de seleção. Os players do app permanecem silenciados 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 do nível de áudio (redução temporária do nível de áudio de um app para que outro possa ser ouvido claramente) foi introduzida no Android 8.0 (nível 26 da API).
Ao fazer com que o sistema implemente a redução, você não vai precisar 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 em uso atende a todos estes critérios:
- O app solicitou a seleção de áudio com qualquer tipo de ganho de foco.
- O app não está tocando á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 abaixa todos os players ativos do primeiro app enquanto o segundo está em foco. Quando o segundo app abandona o foco, ele o tira de novo. O primeiro app não é notificado quando perde o foco, portanto, ele 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, orientações por voz para rotas de carro não são reduzidas.
Silenciar a reprodução de áudio atual para chamadas telefônicas recebidas
Alguns apps não se comportam da forma adequada e continuam reproduzindo áudio durante ligações. 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 há 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_MEDIA
ouAudioAttributes.USAGE_GAME
. - O app solicitou com sucesso a seleção de áudio (qualquer ganho de seleção) 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 ser reproduzido durante a chamada, esse player não será silenciado, supondo que o usuário a tenha iniciado intencionalmente.
Seleção de áudio do Android 8.0 ao Android 11
A partir do Android 8.0 (nível 26 da API), ao chamar
requestAudioFocus()
,
você precisa 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 da seleção de áudio
automaticamente. 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 de AudioFocusRequest
ao solicitar e abandonar a seleção.
Para criar uma AudioFocusRequest
, use uma
AudioFocusRequest.Builder
. Como uma solicitação de foco
sempre precisa especificar o tipo da solicitação, o tipo é 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 | Notes |
---|---|
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()
|
O AudioAttributes descreve o caso de uso do seu app. O
sistema analisa essas ocorrências 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 (API de nível 26) e versões mais recentes,
os tipos de stream para qualquer operação que não sejam controles de volume foram descontinuados. Use
os mesmos atributos na solicitação de seleção que você usa no player de áudio, conforme
mostrado no exemplo após esta tabela.
Use
Se não for especificado, |
setWillPauseWhenDucked()
|
Quando outro app solicita a seleção com
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK , o app em foco não
geralmente recebe um callback
onAudioFocusChange() ,
porque o sistema pode realizar a
redução por conta própria. Quando você precisar pausar a reprodução em
vez de diminuir o volume, chame setWillPauseWhenDucked(true) , crie e defina um
OnAudioFocusChangeListener , conforme descrito em redução
automática.
|
setAcceptsDelayedFocusGain()
|
Uma solicitação de seleção de áudio pode falhar quando a seleção é bloqueada por outro app.
Esse método permite o ganho atrasado de seleção: a capacidade
de adquirir seleção de maneira assíncrona quando ela está disponível.
O ganho atrasado de seleção só funciona se você também especificar um
|
setOnAudioFocusChangeListener()
|
Um OnAudioFocusChangeListener só será 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 gerenciador. O gerenciador é a sequência em que o listener é executado. Se você
não especificar um gerenciador, o associado ao |
O exemplo abaixo 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 (API de nível 26), quando outro app solicita a seleção com
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
, o sistema pode reduzir e restaurar o volume
sem invocar o callback onAudioFocusChange()
.
Embora a redução automática seja um comportamento aceitável para apps de reprodução de música e vídeo, ela não é útil ao 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 para diminuir 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
setWillPauseWhenDucked(true)
para dizer ao sistema para usar o callback, em vez de executar a redução automática.
Ganho atrasado de seleção
Às vezes, o sistema não pode conceder uma solicitação de seleção de áudio porque ela está
"bloqueada" por outro app, como durante uma chamada telefônica. Nesse caso,
requestAudioFocus()
retorna AUDIOFOCUS_REQUEST_FAILED
. Quando isso acontece,
seu app não pode continuar com a reprodução de áudio porque não recebeu
foco.
O método setAcceptsDelayedFocusGain(true)
, que 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 o foco está bloqueado
retorna AUDIOFOCUS_REQUEST_DELAYED
. Quando a condição que bloqueou a seleção
de áudio não existe mais, como quando uma chamada telefônica termina, o sistema
concede a solicitação de seleção pendente e chama onAudioFocusChange()
para notificar seu
app.
Para processar o ganho de foco atrasado, você precisa criar um
OnAudioFocusChangeListener
com um método de callback onAudioFocusChange()
que
implemente o comportamento desejado e registre o listener chamando
setOnAudioFocusChangeListener()
.
Seleção de áudio no Android 7.1 e versões anteriores
Ao chamar
requestAudioFocus()
,
você precisa especificar uma dica de duração, que pode
ser respeitada por outro app que esteja mantendo o foco e sendo reproduzido:
- Solicite a seleção de áudio permanente (
AUDIOFOCUS_GAIN
) quando você planeja tocar áudio em um futuro próximo (por exemplo, ao tocar música) e espera que o proprietário anterior da seleção de áudio pare de tocar. - Solicite a seleção temporária (
AUDIOFOCUS_GAIN_TRANSIENT
) quando esperar tocar áudio por um curto período e esperar que o detentor anterior pause a reprodução. - Solicite a seleção temporária com redução de volume
(
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK
) para indicar que você espera tocar áudio apenas por um curto período e que não há problema se o proprietário anterior do foco continuar a tocar se ele "diminuir" (diminuir) a saída de áudio. As duas saídas de áudio são misturadas no stream de áudio. A redução é particularmente adequada para apps que usam o fluxo de áudio de maneira 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 proprietário da sua sessão de mídia. Ela
implementa o callback onAudioFocusChange()
que seu app recebe quando
algum outro recebe ou abandona a seleção de áudio.
O snippet a seguir solicita seleção de áudio permanente no stream
STREAM_MUSIC
e registra um OnAudioFocusChangeListener
para processar
mudanças 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ê tiver solicitado a seleção temporária,
isso vai notificar um app que pausou ou reduziu o volume que ele pode continuar a reproduzir ou
restaurar o volume.
Como responder a uma mudança de seleção de áudio
Quando um app recebe seleção de áudio, ele precisa conseguir liberá-la quando outro app
solicitar seleção de áudio para si mesmo. Quando isso acontece, o app
recebe uma chamada para o método
onAudioFocusChange()
no AudioFocusChangeListener
especificado quando o app chamou requestAudioFocus()
.
O parâmetro focusChange
transmitido para onAudioFocusChange()
indica o tipo
de mudança que está acontecendo. Ela corresponde
à dica de duração usada pelo app que está recebendo foco. Seu app precisa
responder da maneira adequada.
- Perda transitória de seleção
-
Se a mudança de foco for temporária (
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK
ouAUDIOFOCUS_LOSS_TRANSIENT
), seu app precisará reduzir o volume (se você não depender da redução automática) ou pausar a reprodução, mas manter o mesmo estado.Durante uma perda temporária de seleção de áudio, continue monitorando as mudanças na seleção de áudio e esteja preparado para retomar a reprodução normal quando recuperar o foco. Quando o app de bloqueio abandonar o foco, você receberá 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 da seleção de áudio for permanente (
AUDIOFOCUS_LOSS
), outro app vai estar tocando áudio. Seu app precisa pausar a reprodução imediatamente, porque nunca 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 interface 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 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 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()
no callback onPlay()
,
onSkipToNext()
etc. Você também precisa chamar esse método no callback
onDestroy()
do serviço ao limpar os recursos usados pelo serviço.