Due o più app per Android possono riprodurre l'audio nello stesso stream di output contemporaneamente e il sistema combina tutto. Sebbene questo sia tecnicamente impressionante, può essere molto fastidioso per un utente. Per evitare che tutte le app musicali vengano riprodotte contemporaneamente, Android introduce il concetto di focus audio. Solo un'app alla volta può avere il controllo dell'audio.
Quando l'app deve riprodurre l'audio, deve richiedere l'audio focus. Quando ha lo stato attivo, può riprodurre l'audio. Tuttavia, dopo aver acquisito lo stato attivo dell'audio, potresti non essere in grado di mantenerlo fino al termine della riproduzione. Un'altra app può richiedere il focus, il che preclude il tuo controllo sul focus audio. In questo caso, l'app deve mettere in pausa la riproduzione o abbassare il volume per consentire agli utenti di ascoltare più facilmente la nuova sorgente audio.
Prima di Android 12 (livello API 31), l'audio focus non è gestito dal sistema. Pertanto, sebbene gli sviluppatori di app siano incoraggiati a rispettare le linee guida per l'audio focus, se un'app continua a riprodurre l'audio ad alto volume anche dopo aver perso l'audio focus su un dispositivo con Android 11 (livello API 30) o versioni precedenti, il sistema non può impedirlo. Tuttavia, questo comportamento dell'app genera un'esperienza utente negativa e spesso può indurre gli utenti a disinstallare l'app che si comporta in modo anomalo.
Un'app audio ben progettata deve gestire l'audio focus in base a queste linee guida generali:
Chiama
requestAudioFocus()immediatamente prima di iniziare la riproduzione e verifica che la chiamata restituiscaAUDIOFOCUS_REQUEST_GRANTED. Effettua la chiamata arequestAudioFocus()nel callbackonPlay()della sessione multimediale.Quando un'altra app ottiene il focus audio, interrompi o metti in pausa la riproduzione oppure abbassa il volume.
Quando la riproduzione si interrompe (ad esempio, quando l'app non ha più contenuti da riprodurre), abbandona la priorità audio. La tua app non deve abbandonare il livello di priorità audio se l'utente mette in pausa la riproduzione, ma potrebbe riprenderla in un secondo momento.
Utilizza
AudioAttributesper descrivere il tipo di audio riprodotto dalla tua app. Ad esempio, per le app che riproducono contenuti vocali, specificaCONTENT_TYPE_SPEECH.
La priorità audio viene gestita in modo diverso a seconda della versione di Android in esecuzione:
- Android 12 (livello API 31) o versioni successive
- La priorità audio è gestita dal sistema. Il sistema forza la dissolvenza della riproduzione audio di un'app quando un'altra app richiede l'audio focus. Il sistema disattiva anche la riproduzione audio quando viene ricevuta una chiamata in arrivo.
- Da Android 8.0 (livello API 26) ad Android 11 (livello API 30)
- La selezione audio non è gestita dal sistema, ma include alcune modifiche introdotte a partire da Android 8.0 (livello API 26).
- Android 7.1 (livello API 25) e versioni precedenti
- La selezione audio non è gestita dal sistema e le app la gestiscono utilizzando
requestAudioFocus()eabandonAudioFocus().
Focalizzazione audio in Android 12 e versioni successive
Un'app di contenuti multimediali o di gioco che utilizza il focus audio non deve riprodurre audio dopo aver perso il focus. In Android 12 (livello API 31) e versioni successive, il sistema applica questo comportamento. Quando un'app richiede l'audio focus mentre un'altra app ha il focus e sta riproducendo, il sistema forza la dissolvenza dell'app in riproduzione. L'aggiunta della dissolvenza in uscita offre una transizione più fluida quando si passa da un'app all'altra.
Questo comportamento di dissolvenza si verifica quando sono soddisfatte le seguenti condizioni:
La prima app, quella attualmente in riproduzione, soddisfa tutti i seguenti criteri:
- L'app ha l'attributo di utilizzo
AudioAttributes.USAGE_MEDIAoAudioAttributes.USAGE_GAME. - L'app ha richiesto correttamente il focus audio con
AudioManager.AUDIOFOCUS_GAIN. - L'app non riproduce audio con tipo di contenuti
AudioAttributes.CONTENT_TYPE_SPEECH.
- L'app ha l'attributo di utilizzo
Una seconda app richiede l'audio focus con
AudioManager.AUDIOFOCUS_GAIN.
Quando queste condizioni sono soddisfatte, l'impianto audio applica la dissolvenza in uscita alla prima app. Al termine della dissolvenza in uscita, il sistema comunica alla prima app la perdita di focus. I player dell'app rimangono disattivati finché l'app non richiede nuovamente l'audio focus.
Comportamenti di focus audio esistenti
Devi anche tenere presente questi altri casi che comportano un cambio di focus audio.
Attenuazione automatica audio
L'attenuazione automatica (riduzione temporanea del livello audio di un'app in modo che un'altra possa essere ascoltata chiaramente) è stata introdotta in Android 8.0 (livello API 26).
Se il sistema implementa l'attenuazione, non è necessario implementarla nella tua app.
L'attenuazione automatica si verifica anche quando una notifica audio acquisisce lo stato attivo da un'app in riproduzione. L'inizio della riproduzione della notifica è sincronizzato con la fine della rampa di attenuazione.
L'attenuazione automatica si verifica quando sono soddisfatte le seguenti condizioni:
La prima app, attualmente in riproduzione, soddisfa tutti i seguenti criteri:
- L'app ha richiesto correttamente l'audio focus con qualsiasi tipo di guadagno di focus.
- L'app non riproduce audio con tipo di contenuti
AudioAttributes.CONTENT_TYPE_SPEECH. - L'app non ha impostato
AudioFocusRequest.Builder.setWillPauseWhenDucked(true).
Una seconda app richiede l'audio focus con
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK.
Quando queste condizioni sono soddisfatte, il sistema audio abbassa il volume di tutti i player attivi della prima app mentre la seconda app è attiva. Quando la seconda app abbandona il focus, il volume viene ripristinato. La prima app non riceve una notifica quando perde lo stato attivo, quindi non deve fare nulla.
Tieni presente che l'attenuazione automatica non viene eseguita quando l'utente ascolta contenuti vocali, perché potrebbe perdersi parte del programma. Ad esempio, le indicazioni stradali della guida vocale non vengono attenuate.
Disattiva l'audio della riproduzione corrente per le chiamate in arrivo
Alcune app non funzionano correttamente e continuano a riprodurre l'audio durante le telefonate. Questa situazione costringe l'utente a trovare e disattivare l'audio o chiudere l'app che causa il problema per poter ascoltare la chiamata. Per evitare questo problema, il sistema può disattivare l'audio di altre app durante una chiamata in arrivo. Il sistema richiama questa funzionalità quando viene ricevuta una chiamata in arrivo e un'app soddisfa queste condizioni:
- L'app ha l'attributo di utilizzo
AudioAttributes.USAGE_MEDIAoAudioAttributes.USAGE_GAME. - L'app ha richiesto correttamente l'audio focus (qualsiasi guadagno di focus) e sta riproducendo audio.
Se un'app continua la riproduzione durante la chiamata, l'audio viene disattivato fino al termine della chiamata. Tuttavia, se un'app avvia la riproduzione durante la chiamata, l'audio del player non viene disattivato, presupponendo che l'utente abbia avviato la riproduzione intenzionalmente.
Focus audio in Android 8.0-Android 11
A partire da Android 8.0 (livello API 26), quando chiami
requestAudioFocus()
devi fornire un parametro AudioFocusRequest. AudioFocusRequest contiene informazioni sul contesto audio e sulle funzionalità della tua app. Il sistema utilizza queste informazioni per gestire automaticamente l'acquisizione e la perdita del focus audio. Per rilasciare il focus audio, chiama il metodo
abandonAudioFocusRequest()
che accetta anche un AudioFocusRequest come argomento. Utilizza la stessa istanza AudioFocusRequest sia quando richiedi sia quando abbandoni lo stato attivo.
Per creare un AudioFocusRequest, utilizza un
AudioFocusRequest.Builder. Poiché una richiesta di messa a fuoco deve sempre specificare il tipo di richiesta, il tipo è incluso nel costruttore del builder. Utilizza i metodi del builder per impostare gli altri campi della richiesta.
Il campo FocusGain è obbligatorio, mentre tutti gli altri campi sono facoltativi.
| Metodo | Note |
|---|---|
setFocusGain()
|
Questo campo è obbligatorio in ogni richiesta. Assume gli stessi valori di durationHint utilizzati nella chiamata precedente ad Android 8.0 a requestAudioFocus(): AUDIOFOCUS_GAIN, AUDIOFOCUS_GAIN_TRANSIENT, AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK o AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE.
|
setAudioAttributes()
|
AudioAttributes descrive il caso d'uso della tua app. Il sistema li esamina quando un'app acquisisce e perde l'audio focus. Gli attributi sostituiscono la nozione di tipo di stream. In Android 8.0 (livello API 26) e versioni successive,
i tipi di stream per qualsiasi operazione diversa dai controlli del volume sono deprecati. Utilizza nella richiesta di selezione gli stessi attributi che utilizzi nel tuo lettore audio (come mostrato nell'esempio che segue questa tabella).
Utilizza un
Se non specificato, il valore predefinito di |
setWillPauseWhenDucked()
|
Quando un'altra app richiede lo stato attivo con
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, l'app che ha lo stato attivo non
riceve in genere un
callback onAudioFocusChange()
perché il sistema può eseguire il
ducking autonomamente. Quando devi mettere in pausa la riproduzione anziché abbassare il volume, chiama setWillPauseWhenDucked(true) e crea e imposta un OnAudioFocusChangeListener, come descritto in Abbassamento automatico del volume.
|
setAcceptsDelayedFocusGain()
|
Una richiesta di selezione audio può non riuscire quando la selezione è bloccata da un'altra app.
Questo metodo consente di ritardare l'acquisizione della selezione: la possibilità
di acquisire la selezione in modo asincrono quando diventa disponibile.
Tieni presente che l'acquisizione ritardata del focus funziona solo se specifichi anche un
|
setOnAudioFocusChangeListener()
|
Un OnAudioFocusChangeListener è obbligatorio solo se specifichi anche willPauseWhenDucked(true) o setAcceptsDelayedFocusGain(true) nella richiesta.
Esistono due metodi per impostare il listener: uno con e uno senza un argomento handler. Il gestore è il thread su cui viene eseguito il listener. Se non specifichi un gestore, viene utilizzato quello associato al |
L'esempio seguente mostra come utilizzare un AudioFocusRequest.Builder per creare un AudioFocusRequest e richiedere e abbandonare il focus 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; } } }
Attenuazione automatica audio
In Android 8.0 (livello API 26), quando un'altra app richiede l'attenzione con
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, il sistema può abbassare e ripristinare il volume
senza richiamare il callback onAudioFocusChange() dell'app.
Sebbene l'attenuazione automatica sia un comportamento accettabile per le app di riproduzione di musica e video, non è utile durante la riproduzione di contenuti parlati, ad esempio in un'app di audiolibri. In questo caso, l'app dovrebbe invece essere messa in pausa.
Se vuoi che la tua app venga messa in pausa quando viene richiesto l'abbassamento del volume anziché diminuire il volume, crea un OnAudioFocusChangeListener con un metodo di callback onAudioFocusChange() che implementi il comportamento di pausa/ripresa desiderato.
Chiama setOnAudioFocusChangeListener() per registrare il listener e chiama
setWillPauseWhenDucked(true)
per indicare al sistema di utilizzare il tuo callback anziché eseguire l'attenuazione automatica.
Guadagno di messa a fuoco ritardato
A volte il sistema non può concedere una richiesta di selezione dell'audio perché la selezione è "bloccata" da un'altra app, ad esempio durante una telefonata. In questo caso, requestAudioFocus() restituisce AUDIOFOCUS_REQUEST_FAILED. In questo caso, l'app non deve procedere con la riproduzione audio perché non ha ottenuto lo stato attivo.
Il metodo setAcceptsDelayedFocusGain(true), che consente alla tua app di gestire una richiesta di selezione in modo asincrono. Con questo flag impostato, una richiesta effettuata quando la messa a fuoco è bloccata restituisce AUDIOFOCUS_REQUEST_DELAYED. Quando la condizione che ha bloccato l'audio focus non esiste più, ad esempio quando termina una telefonata, il sistema concede la richiesta di focus in attesa e chiama onAudioFocusChange() per inviare una notifica alla tua app.
Per gestire l'acquisizione ritardata dello stato attivo, devi creare un
OnAudioFocusChangeListener con un metodo di callback onAudioFocusChange() che
implementi il comportamento desiderato e registri il listener chiamando
setOnAudioFocusChangeListener().
Focus audio in Android 7.1 e versioni precedenti
Quando chiami
requestAudioFocus()
devi specificare un suggerimento di durata, che potrebbe
essere rispettato da un'altra app che attualmente ha lo stato attivo e sta riproducendo contenuti:
- Richiedi l'audio focus permanente (
AUDIOFOCUS_GAIN) se prevedi di riprodurre l'audio per un periodo di tempo prevedibile (ad esempio, quando riproduci musica) e ti aspetti che il precedente titolare dell'audio focus interrompa la riproduzione. - Richiedi il focus temporaneo (
AUDIOFOCUS_GAIN_TRANSIENT) quando prevedi di riprodurre l'audio solo per un breve periodo di tempo e prevedi che il titolare precedente metta in pausa la riproduzione. - Richiedi il focus temporaneo con ducking
(
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) per indicare che prevedi di riprodurre l'audio solo per un breve periodo di tempo e che il proprietario del focus precedente può continuare a riprodurre se "abbassa" (riduce) l'output audio. Entrambe le uscite audio vengono mixate nello stream audio. Il ducking è particolarmente adatto per le app che utilizzano lo stream audio in modo intermittente, ad esempio per le indicazioni stradali vocali.
Il metodo requestAudioFocus() richiede anche un AudioManager.OnAudioFocusChangeListener. Questo listener deve essere creato nella stessa attività o nello stesso servizio proprietario della sessione multimediale. Implementa il callback onAudioFocusChange() che la tua app riceve quando un'altra app acquisisce o abbandona il focus audio.
Il seguente snippet richiede il focus audio permanente sullo stream
STREAM_MUSIC e registra un OnAudioFocusChangeListener per gestire
le successive modifiche al focus audio. (Il listener di modifica è descritto in Risposta a una modifica della priorità 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 }
Al termine della riproduzione, chiama
abandonAudioFocus().
Kotlin
audioManager.abandonAudioFocus(afChangeListener)
Java
// Abandon audio focus when playback complete audioManager.abandonAudioFocus(afChangeListener);
In questo modo, il sistema viene informato che non hai più bisogno dello stato attivo e annulla la registrazione del OnAudioFocusChangeListener associato. Se hai richiesto lo stato attivo temporaneo,
un'app che è stata messa in pausa o il cui volume è stato abbassato riceverà una notifica che la informa che può continuare la riproduzione o
ripristinare il volume.
Risposta a una modifica della priorità audio
Quando un'app acquisisce il focus audio, deve essere in grado di rilasciarlo quando un'altra app
lo richiede per sé. In questo caso, la tua app riceve una chiamata al metodo onAudioFocusChange() in AudioFocusChangeListener che hai specificato quando l'app ha chiamato requestAudioFocus().
Il parametro focusChange passato a onAudioFocusChange() indica il tipo di modifica in corso. Corrisponde al suggerimento sulla durata utilizzato dall'app che sta acquisendo il focus. L'app deve rispondere in modo appropriato.
- Perdita temporanea di concentrazione
-
Se la variazione di stato attivo è temporanea (
AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCKoAUDIOFOCUS_LOSS_TRANSIENT), l'app deve abbassare il volume (se non utilizzi l'abbassamento automatico del volume) o mettere in pausa la riproduzione, ma mantenere lo stesso stato.Durante una perdita temporanea della selezione audio, devi continuare a monitorare le modifiche alla selezione audio ed essere pronto a riprendere la normale riproduzione quando recuperi la selezione. Quando l'app di blocco abbandona la selezione, ricevi un callback (
AUDIOFOCUS_GAIN). A questo punto, puoi ripristinare il volume al livello normale o riavviare la riproduzione. - Perdita permanente della messa a fuoco
-
Se la perdita del focus audio è permanente (
AUDIOFOCUS_LOSS), un'altra app sta riproducendo l'audio. L'app deve mettere immediatamente in pausa la riproduzione, poiché non riceverà mai un callbackAUDIOFOCUS_GAIN. Per riavviare la riproduzione, l'utente deve eseguire un'azione esplicita, ad esempio premere il controllo di trasporto di riproduzione in una notifica o nell'interfaccia utente dell'app.
Il seguente snippet di codice mostra come implementare
OnAudioFocusChangeListener e il relativo callback onAudioFocusChange(). Nota l'utilizzo di un Handler per ritardare il callback di interruzione in caso di perdita permanente della priorità audio.
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 } } };
Il gestore utilizza un Runnable simile al seguente:
Kotlin
private var delayedStopRunnable = Runnable { mediaController.transportControls.stop() }
Java
private Runnable delayedStopRunnable = new Runnable() { @Override public void run() { getMediaController().getTransportControls().stop(); } };
Per assicurarti che l'interruzione ritardata non venga attivata se l'utente riavvia la riproduzione, chiama mHandler.removeCallbacks(mDelayedStopRunnable) in risposta a eventuali modifiche dello stato. Ad esempio, chiama removeCallbacks() nel onPlay(), onSkipToNext() e così via del tuo callback. Devi anche chiamare questo metodo nel callback onDestroy() del tuo servizio durante la pulizia delle risorse utilizzate dal servizio.