Gerenciar seleção de áudio

Dois ou mais apps Android podem tocar áudio no mesmo stream de saída simultaneamente, e o sistema mistura tudo. Embora isso seja impressionante tecnicamente, pode ser muito desagradável para o usuário. Para evitar todos de música sendo reproduzido ao mesmo tempo, o Android introduz a ideia de áudio em foco. 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 pode tocar sons. No entanto, depois de adquirir a seleção de áudio, talvez você não seja poderá mantê-lo até terminar de jogar. Outro app pode solicitar foco, interrompe a retenção da seleção de áudio. Se isso acontecer, seu app será pausado reproduzir 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), a seleção de áudio não era gerenciada pelo sistema. Então, enquanto os desenvolvedores de apps são incentivados a seguir as 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 executando o Android 11 (nível 30 da API) ou versões anteriores, o sistema não pode impedir o uso desse recurso. No entanto, esse comportamento do aplicativo causa uma má experiência do usuário e pode levar que os usuários desinstalem o aplicativo com comportamento inadequado.

Um app de áudio bem projetado precisa gerenciar a seleção de áudio de acordo com essas diretrizes diretrizes:

  • Chame requestAudioFocus() imediatamente antes de começar a tocar e verifique se a chamada retorna AUDIOFOCUS_REQUEST_GRANTED. Faça a chamada para requestAudioFocus() no callback onPlay() 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, reduzir) o volume.

  • Quando a reprodução for interrompida (por exemplo, quando o app não tiver mais nada para reproduzir), abandonar a seleção de áudio. Seu app não precisará abandonar a seleção de áudio se o usuário pausa a reprodução, mas pode retomá-la mais tarde.

  • Use AudioAttributes para descrever o tipo de áudio que o app está tocando. Por exemplo, no caso de apps que reproduzem fala, especificar CONTENT_TYPE_SPEECH

A seleção de áudio é tratada de forma diferente dependendo da versão do Android que está 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 a partir de uma esmaeça quando outro app solicita a seleção de áudio. O sistema também silencia a reprodução do áudio quando uma ligação recebida é recebida.
Do 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 alterações que foram Introduzido no Android 8.0 (nível 26 da API) e versões mais recentes.
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() e abandonAudioFocus()

Seleção de áudio no Android 12 e 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 isso do seu modelo. Quando um app solicita a seleção de áudio enquanto outro app está em foco e está sendo reproduzido, o sistema força o app a esmaecer. A adição do elemento o 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:

  1. O primeiro app em execução atende a todos estes critérios:

  2. 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 fim do esmaecimento, o sistema notifica o primeiro app sobre a perda de foco. O Os players 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

Redução temporária do nível de áudio de um app para que outra pode ser ouvida claramente) foi introduzida no Android 8.0 (API de nível 26).

Ao fazer com que o sistema implemente a redução de volume, você não precisa implementar a redução 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:

  1. O primeiro app em execução no momento atende a todos estes critérios:

  2. 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á todos os participantes ativos do para o primeiro app, enquanto o segundo tem o foco. Quando o segundo app é abandonado o foco é imperdível. O primeiro app não é notificado quando perde o foco. então ele não precisa fazer nada.

A redução automática de volume não é realizada quando o usuário está ouvindo conteúdo de fala, porque o usuário pode perder parte do programa. Por exemplo: a orientação por voz das rotas de carro não diminui.

Silenciar a reprodução de áudio atual para chamadas telefônicas recebidas

Alguns apps não se comportam corretamente e continuam a tocar áudio durante chamadas telefônicas. Esta situação força o usuário a encontrar e silenciar ou sair do aplicativo ofensivo em para ouvir a chamada. Para evitar isso, o sistema pode desativar o áudio de outros enquanto recebe uma chamada. O sistema invoca esse recurso quando uma uma chamada telefônica recebida é recebida e um aplicativo atende a estas condições:

  • O app tem a propriedade AudioAttributes.USAGE_MEDIA ou AudioAttributes.USAGE_GAME.
  • O app solicitou 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 aplicativo começar a ser reproduzido durante a chamada, esse player não será silenciado, supondo que o usuário iniciou a reprodução intencionalmente.

Seleção de áudio no Android 8.0 ao 11

A partir do Android 8.0 (nível 26 da API), ao chamar requestAudioFocus() é preciso fornecer um parâmetro AudioFocusRequest. O AudioFocusRequest contém informações sobre o contexto e os recursos de áudio do seu app. A 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 quando você solicita e abandona o foco.

Para criar uma AudioFocusRequest, use uma AudioFocusRequest.Builder Como uma solicitação de foco precisa sempre 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 do solicitação.

O campo FocusGain é obrigatório. Todos os outros são opcionais.

MétodoObservações
setFocusGain() Este campo é obrigatório em todas as solicitações. Ele usa os mesmos valores o durationHint usado na chamada para requestAudioFocus() antes do Android 8.0: AUDIOFOCUS_GAIN, AUDIOFOCUS_GAIN_TRANSIENT, AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK ou AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE.
setAudioAttributes() AudioAttributes descreve o caso de uso do seu app. A sistema olha para eles quando um aplicativo ganha e perde a seleção de áudio. Atributos substitui a noção de tipo de fluxo. 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 os controles de volume foram descontinuados. Usar os mesmos atributos na solicitação de seleção que você usa em seu player de áudio (como como mostrado no exemplo após esta tabela).

Use um AudioAttributes.Builder para especificar atributos e depois use este método para atribuir os atributos ao solicitação.

Se não for especificado, AudioAttributes será definido como AudioAttributes.USAGE_MEDIA por padrão.

setWillPauseWhenDucked() Quando outro app pede foco com AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, o app em foco não geralmente recebem uma onAudioFocusChange() porque o sistema pode fazer o a reduzir o volume. Quando você precisar pausar a reprodução em vez do que reduzir o volume, chame setWillPauseWhenDucked(true), crie e defina um OnAudioFocusChangeListener, conforme descrito no artigo automático redução de volume.
setAcceptsDelayedFocusGain() Uma solicitação de seleção de áudio pode falhar quando ela é bloqueada por outro app. Esse método ativa o ganho atrasado da seleção: a capacidade para adquirir o foco de forma assíncrona quando estiver disponível.

O ganho atrasado da seleção só funciona se você também especificar um AudioManager.OnAudioFocusChangeListener na solicitação de áudio, já que seu app precisa receba o callback para saber que o foco foi concedido.

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 manipulador. O gerenciador é a sequência em que o listener é executado. Se você não especifique um manipulador, o gerenciador associado à interface principal Looper é usado.

O exemplo abaixo mostra como usar um AudioFocusRequest.Builder para criar um AudioFocusRequest para 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 aplicativo 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 a reprodução de músicas e vídeos, aplicativos, não é útil ao reproduzir conteúdo falado, como em um aplicativo de audiolivros. Nesse caso, o app precisa ser pausado.

Se você quiser que o app seja pausado quando solicitado a reduzir o volume em vez de diminuir o volume, crie uma OnAudioFocusChangeListener com um método de callback onAudioFocusChange() que implementa o comportamento de pausa/retomada desejado Chame setOnAudioFocusChangeListener() para registrar o listener e chame setWillPauseWhenDucked(true) para instruir o sistema a usar o callback em vez de realizar a redução automática de volume.

Ganho atrasado de seleção

Às vezes, o sistema não pode conceder uma solicitação de seleção de áudio porque ela está "bloqueado" por outro app, como durante uma chamada telefônica. Nesse caso, requestAudioFocus() retorna AUDIOFOCUS_REQUEST_FAILED. Quando isso acontece, seu app não deve continuar com a reprodução de áudio porque não ganhou foco.

O método setAcceptsDelayedFocusGain(true), que permite que o app processe uma solicitação de foco. de forma assíncrona. Com esse flag definido, uma solicitação feita quando o foco está bloqueado retorna AUDIOFOCUS_REQUEST_DELAYED. Quando a condição que bloqueou o áudio não existe mais, por exemplo, quando uma ligação é encerrada, o sistema concede a solicitação de foco pendente e chama onAudioFocusChange() para notificar seu app.

Para lidar com o ganho atrasado de foco, é necessário criar um OnAudioFocusChangeListener por um método de callback onAudioFocusChange() que implementa o comportamento desejado e registra o listener chamando setOnAudioFocusChangeListener().

Seleção de áudio no Android 7.1 e versões anteriores

Quando você ligar requestAudioFocus() você deve especificar uma dica de duração, que pode ser honrado por outro aplicativo que está mantendo o foco e tocando no momento:

  • Solicitar seleção de áudio permanente (AUDIOFOCUS_GAIN) quando planeja tocar áudio em um futuro próximo (por exemplo, ao tocar música) e espera que detentor anterior de seleção de áudio para interromper a reprodução.
  • Solicitar o foco temporário (AUDIOFOCUS_GAIN_TRANSIENT) quando esperar a reprodução o áudio por um curto período e você espera que o detentor anterior pause a reprodução.
  • Solicitar foco temporário com redução de volume (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) para indicar que você espera tocar áudio por um curto período e que não há problema para o proprietário do foco anterior manter tocar se "patos" (diminui) a 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 o stream de áudio de maneira intermitente, por exemplo, para rotas de carro audíveis.

O método requestAudioFocus() também requer um AudioManager.OnAudioFocusChangeListener. Esse listener precisa ser criados na mesma atividade ou serviço que é proprietário da sua sessão de mídia. Ela 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 processar mudanças subsequentes na seleção de áudio. O listener de mudanças é 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 da seleção e cancela o registro do associado OnAudioFocusChangeListener. Se você solicitou a seleção temporária, isso vai notificar um app que pausou ou diminuiu para 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 a seleção de áudio, ele precisa ser capaz de liberá-la quando outro app solicita a seleção de áudio para si. Quando isso acontece, seu app recebe uma chamada onAudioFocusChange() na classe AudioFocusChangeListener que você especificou quando o app chamou requestAudioFocus().

O parâmetro focusChange transmitido para onAudioFocusChange() indica o tipo das mudanças que estão acontecendo. Ele corresponde à dica de duração usada pelo app que está adquirindo 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_DUCK ou AUDIOFOCUS_LOSS_TRANSIENT), o app precisa se retrair (se você não estiver confiando na redução automática de volume) ou pausar a reprodução, mas caso contrário, manteriam o mesmo estado.

Durante uma perda transitória da seleção de áudio, continue monitorando as mudanças. na seleção de áudio e esteja preparado para retomar a reprodução normal quando você recuperar foco. Quando o app de bloqueio abandonar o foco, você vai receber um callback (AUDIOFOCUS_GAIN). Neste ponto, é 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 será a reprodução de áudio. O app precisa pausar a reprodução imediatamente, como nunca receba um callback AUDIOFOCUS_GAIN. Para reiniciar a reprodução, o usuário precisa realizar uma ação explícita, como pressionar o controle de transporte para reprodução; em uma notificação ou na interface do app.

O snippet de código a seguir demonstra como implementar a OnAudioFocusChangeListener e o callback onAudioFocusChange(). Observe que Uso de um Handler para atrasar o callback de parada em uma perda permanente de áudio. foco.

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 estado mudanças. Por exemplo, chame removeCallbacks() no onPlay() do callback. onSkipToNext() etc. Você também deve chamar esse método na classe onDestroy() ao limpar os recursos usados pelo serviço.