Gestire la messa a fuoco audio

Due o più app Android possono riprodurre l'audio nello stesso stream di output contemporaneamente e il sistema mixa tutto. Sebbene sia tecnicamente impressionante, può essere molto aggravante per un utente. Per evitare che tutte le app di musica vengano riprodotte contemporaneamente, Android introduce l'idea di Messa a fuoco audio. È possibile impostare il focus audio per una sola app alla volta.

Quando l'app deve emettere audio, dovrebbe richiedere il focus audio. Quando è a fuoco, il suono viene riprodotto. Tuttavia, dopo aver acquisito il focus audio, potresti non riuscire a mantenerlo fino al termine della riproduzione. Un'altra app può richiedere lo stato attivo, che anticipa la sospensione per l'audio. In tal caso, l'app dovrebbe mettere in pausa la riproduzione o abbassare il volume per consentire agli utenti di sentire più facilmente la nuova sorgente audio.

Prima di Android 12 (livello API 31), il focus audio non è gestito dal sistema. Pertanto, anche se gli sviluppatori di app sono incoraggiati a rispettare le linee guida relative all'audio, se un'app continua a essere riprodotta ad alto volume anche dopo aver perso l'audio su un dispositivo con Android 11 (livello API 30) o versioni precedenti, il sistema non può impedirlo. Tuttavia, questo comportamento dell'app porta a un'esperienza utente negativa e spesso può portarli a disinstallare l'app che funziona in modo anomalo.

Un'app audio ben progettata deve gestire il focus audio secondo queste linee guida generali:

  • Chiama requestAudioFocus() subito prima di iniziare a riprodurre e verifica che la chiamata restituisca AUDIOFOCUS_REQUEST_GRANTED. Chiama requestAudioFocus() nel callback onPlay() della tua sessione multimediale.

  • Quando un'altra app rileva la messa a fuoco audio, interrompi o metti in pausa la riproduzione o abbassa (ovvero, abbassa il volume).

  • Quando la riproduzione si interrompe (ad esempio quando nell'app non è più possibile riprodurre nulla), abbandona la messa a fuoco dell'audio. L'app non deve abbandonare lo stato attivo sull'audio se l'utente mette in pausa la riproduzione, ma potrebbe riprenderla in un secondo momento.

  • Usa AudioAttributes per descrivere il tipo di audio riprodotto dalla tua app. Ad esempio, per le app che riproducono il parlato, specifica CONTENT_TYPE_SPEECH.

Il focus audio viene gestito in modo diverso a seconda della versione di Android installata:

Android 12 (livello API 31) o versioni successive
Il focus audio è gestito dal sistema. Il sistema forza la dissolvenza della riproduzione audio di un'app quando un'altra app richiede l'audio. Il sistema disattiva anche la riproduzione audio quando riceve una chiamata in arrivo.
Da Android 8.0 (livello API 26) ad Android 11 (livello API 30)
Il focus audio non è gestito 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
Il focus audio non è gestito dal sistema e le app gestiscono la messa a fuoco audio utilizzando requestAudioFocus() e abandonAudioFocus().

Focus audio in Android 12 e versioni successive

Un'app di contenuti multimediali o giochi che utilizza focus audio non deve riprodurre audio dopo che è stata messa a fuoco. In Android 12 (livello API 31) e versioni successive, il sistema applica questo comportamento. Quando un'app richiede l'audio attivo mentre un'altra app è attiva e in riproduzione, 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 in uscita si verifica quando sono soddisfatte le seguenti condizioni:

  1. La prima app, attualmente in riproduzione, soddisfa tutti i seguenti criteri:

  2. Una seconda app richiede focus audio con AudioManager.AUDIOFOCUS_GAIN.

Quando queste condizioni sono soddisfatte, il sistema audio attenua la prima app. Al termine della dissolvenza in uscita, il sistema notifica la prima app di perdita di concentrazione. L'audio dei player dell'app rimane disattivato finché l'app non richiede di nuovo lo stato attivo sull'audio.

Comportamenti esistenti per la messa a fuoco audio

Dovresti anche essere consapevoli di questi altri casi che riguardano un cambio di messa a fuoco audio.

Attenuazione automatica automatica

Attenuazione automatica automatica (riduzione temporanea del livello audio di un'app in modo che si possa sentire chiaramente l'altra app) è stata introdotta in Android 8.0 (livello API 26).

Se il sistema implementa l'attenuazione automatica, non è necessario implementarla nell'app.

La funzionalità Attenuazione automatica 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 automatica.

L'attenuazione automatica automatica si verifica quando vengono soddisfatte le seguenti condizioni:

  1. La prima app attualmente in riproduzione soddisfa tutti i seguenti criteri:

  2. Una seconda app richiede l'audio attivo con AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK.

Quando queste condizioni sono soddisfatte, il sistema audio attenua tutti i player attivi della prima app, mentre la seconda è attiva. Quando la seconda app abbandona lo stato attivo, le annulla. La prima app non riceve notifiche se perde lo stato attivo, quindi non deve fare nulla.

Tieni presente che l'attenuazione automatica automatica non viene eseguita quando l'utente è in ascolto dei contenuti vocali, perché potrebbe non vedere parte del programma. Ad esempio, la guida vocale per le indicazioni stradali non viene attenuata.

Disattiva la riproduzione audio corrente per le telefonate in arrivo

Alcune app non funzionano correttamente e continuano a riprodurre audio durante le telefonate. Questa situazione obbliga l'utente a trovare e disattivare l'audio dell'app in questione o a uscire per ascoltare la chiamata. Per evitare che ciò accada, il sistema può disattivare l'audio di altre app quando c'è una chiamata in arrivo. Il sistema richiama questa funzionalità quando riceve una telefonata in arrivo e un'app soddisfa le seguenti condizioni:

  • L'app dispone dell'attributo di utilizzo AudioAttributes.USAGE_MEDIA o AudioAttributes.USAGE_GAME.
  • L'app ha richiesto correttamente lo stato attivo audio (qualsiasi guadagno di stato attivo) e sta riproducendo l'audio.

Se la riproduzione di un'app continua durante la chiamata, l'audio viene disattivato fino al termine della chiamata. Tuttavia, se la riproduzione di un'app inizia durante la chiamata, l'audio del player non viene disattivato presupponendo che l'utente abbia avviato la riproduzione intenzionalmente.

Focus audio in Android da 8.0 ad Android 11

A partire da Android 8.0 (livello API 26), quando chiami requestAudioFocus() devi fornire un parametro AudioFocusRequest. Il AudioFocusRequest contiene informazioni sul contesto audio e sulle funzionalità della tua app. Il sistema utilizza queste informazioni per gestire automaticamente il guadagno e la perdita della messa a fuoco dell'audio. Per rilasciare lo stato attivo sull'audio, chiama il metodo abandonAudioFocusRequest() che utilizza anche AudioFocusRequest come argomento. Utilizza la stessa istanza AudioFocusRequest sia quando richiedi sia quando abbandoni lo stato attivo.

Per creare una AudioFocusRequest, utilizza un AudioFocusRequest.Builder. Poiché una richiesta di stato attivo deve sempre specificare il tipo di richiesta, il tipo è incluso nel costruttore del builder. Utilizza i metodi del generatore per impostare gli altri campi della richiesta.

Il campo FocusGain è obbligatorio; tutti gli altri campi sono facoltativi.

MetodoNotes
setFocusGain() Questo campo è obbligatorio in ogni richiesta. Utilizza gli stessi valori di durationHint utilizzato nella chiamata precedente ad Android 8.0 per 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 perde e perde l'audio. Gli attributi sostituiscono la nozione di tipo di stream. In Android 8.0 (livello API 26) e versioni successive, i tipi di flussi di dati per qualsiasi operazione diversa dai controlli del volume sono deprecati. Nella richiesta di impostazione dello stato attivo, utilizza gli stessi attributi che utilizzi nel lettore audio (come mostrato nell'esempio che segue questa tabella).

Utilizza AudioAttributes.Builder per specificare prima gli attributi, quindi utilizza questo metodo per assegnarli alla richiesta.

Se non specificato, il valore predefinito di AudioAttributes è AudioAttributes.USAGE_MEDIA.

setWillPauseWhenDucked() Quando un'altra app richiede lo stato attivo con AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, quest'ultima non riceve di solito un callback di onAudioFocusChange() perché il sistema può eseguire la suddivisione automatica da solo. Quando devi mettere in pausa la riproduzione anziché abbassare il volume, chiama setWillPauseWhenDucked(true) e crea e imposta un OnAudioFocusChangeListener, come descritto nella sezione Attenuazione automatica automatica.
setAcceptsDelayedFocusGain() Una richiesta di focus audio può avere esito negativo quando lo stato attivo è bloccato da un'altra app. Questo metodo consente il guadagno della messa a fuoco ritardata: la capacità di acquisire lo stato attivo in modo asincrono quando diventa disponibile.

Tieni presente che il guadagno della messa a fuoco ritardata funziona solo se specifichi anche un AudioManager.OnAudioFocusChangeListener nella richiesta audio, poiché la tua app deve ricevere il callback per sapere che lo stato attivo è stato concesso.

setOnAudioFocusChangeListener() OnAudioFocusChangeListener è necessario solo se specifichi anche willPauseWhenDucked(true) o setAcceptsDelayedFocusGain(true) nella richiesta.

Esistono due metodi per impostare il listener: uno con e uno senza argomento gestore. Il gestore è il thread su cui viene eseguito il listener. Se non specifichi un gestore, viene utilizzato il gestore associato all'elemento Looper principale.

L'esempio seguente mostra come utilizzare 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 automatica

In Android 8.0 (livello API 26), quando un'altra app richiede lo stato attivo con AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, il sistema può schivare e ripristinare il volume senza richiamare il callback onAudioFocusChange() dell'app.

Sebbene l'attenuazione automatica automatica sia un comportamento accettabile per le app di riproduzione di musica e video, non è utile durante la riproduzione di contenuti vocali, come in un'app di audiolibri. In questo caso, l'app deve essere messa in pausa.

Se vuoi mettere in pausa la tua app quando ti viene chiesto di abbassare il volume anziché ridurne 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 callback anziché eseguire l'attenuazione automatica automatica.

Guadagno di messa a fuoco ritardato

A volte il sistema non riesce a soddisfare una richiesta di messa a fuoco audio perché quest'ultima è "bloccata" da un'altra app, ad esempio durante una telefonata. In questo caso, requestAudioFocus() restituisce AUDIOFOCUS_REQUEST_FAILED. In questo caso, l'app non dovrebbe procedere con la riproduzione audio perché non è stata messa a fuoco.

Il metodo, setAcceptsDelayedFocusGain(true), che consente alla tua app di gestire una richiesta di focus in modo asincrono. Con questo flag impostato, una richiesta effettuata quando lo stato attivo è bloccato restituisce AUDIOFOCUS_REQUEST_DELAYED. Quando la condizione che ha bloccato l'elemento attivo non esiste più, ad esempio al termine di una telefonata, il sistema concede la richiesta di impostazione dello stato attivo in attesa e chiama onAudioFocusChange() per inviare una notifica alla tua app.

Per gestire il guadagno ritardato dello stato attivo, devi creare un elemento OnAudioFocusChangeListener con un metodo di callback onAudioFocusChange() che implementi il comportamento desiderato e registrare il listener chiamando setOnAudioFocusChangeListener().

Focus audio in Android 7.1 e versioni precedenti

Quando chiami requestAudioFocus(), devi specificare un suggerimento relativo alla durata, che può essere rispettato da un'altra app attualmente in riproduzione e con lo stato attivo:

  • Richiedi la messa a fuoco audio permanente (AUDIOFOCUS_GAIN) se prevedi di riprodurre l'audio nell'immediato futuro (ad esempio durante la riproduzione di musica) e prevedi che la riproduzione del precedente proprietario dell'audio smetterà di funzionare.
  • Richiedi lo stato attivo temporaneo (AUDIOFOCUS_GAIN_TRANSIENT) quando prevedi di riprodurre l'audio solo per un breve periodo di tempo e ti aspetti che il titolare precedente metta in pausa la riproduzione.
  • Richiedi lo stato attivo temporaneo con attenuazione automatica (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) per indicare che prevedi di riprodurre l'audio solo per un breve periodo di tempo e che va bene che il proprietario precedente continui a riprodurre se "attenua" (riduce) l'uscita audio. Entrambe le uscite audio vengono mixate nello stream audio. La funzionalità Attenuazione automatica è particolarmente adatta per le app che utilizzano lo stream audio a intermittenza, ad esempio per indicazioni stradali udibili.

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.

Lo snippet seguente richiede lo stato attivo permanente dell'audio sullo stream STREAM_MUSIC e registra un valore OnAudioFocusChangeListener per gestire le successive modifiche all'elemento attivo audio. (Il listener di modifiche è discusso nella sezione Rispondere a un cambiamento dell'elemento attivo nell'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 comunicherai al sistema che non hai più bisogno di impostare lo stato attivo e verrà annullata la registrazione dell'elemento OnAudioFocusChangeListener associato. Se hai richiesto lo stato attivo temporaneo, verrà inviata una notifica all'app che ha messo in pausa o abbassato il livello di dettaglio in modo che possa continuare la riproduzione o ripristinare il volume.

Rispondere a un cambio di messa a fuoco audio

Quando un'app acquisisce il focus audio, deve essere in grado di rilasciarla quando un'altra app richiede per se stessa. In questo caso, l'app riceve una chiamata al metodo onAudioFocusChange() nel 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 su cui viene acquisito lo stato attivo. La tua app deve rispondere in modo appropriato.

Perdita di messa a fuoco temporanea
Se il cambio di impostazione dello stato attivo è temporaneo (AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK o AUDIOFOCUS_LOSS_TRANSIENT), la tua app dovrebbe attenuarsi (se non utilizzi l'attenuazione automatica automatica) o mettere in pausa la riproduzione, ma, in caso contrario, mantenere lo stesso stato.

In caso di perdita temporanea della messa a fuoco audio, devi continuare a monitorare le variazioni di messa a fuoco audio e prepararti a riprendere la normale riproduzione quando riprendi la messa a fuoco. Quando l'app di blocco non è più attiva, ricevi un callback (AUDIOFOCUS_GAIN). A questo punto, puoi ripristinare il volume al livello normale o riavviare la riproduzione.

Perdita permanente di concentrazione
Se la perdita di focus audio è permanente (AUDIOFOCUS_LOSS), un'altra app sta riproducendo audio. L'app dovrebbe mettere in pausa immediatamente la riproduzione perché non riceverà mai un callback AUDIOFOCUS_GAIN. Per riavviare la riproduzione, l'utente deve eseguire un'azione esplicita, ad esempio premere il controllo di trasporto della riproduzione in una notifica o nell'interfaccia utente di un'app.

Il seguente snippet di codice mostra come implementare OnAudioFocusChangeListener e il relativo callback onAudioFocusChange(). Nota l'utilizzo di Handler per ritardare il callback di interruzione in caso di perdita permanente dell'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 che ha il seguente aspetto:

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() in onPlay(), onSkipToNext() e così via di callback. Dovresti chiamare questo metodo anche nel callback onDestroy() del tuo servizio quando ripulisci le risorse utilizzate dal servizio.