Gestire la messa a fuoco audio

Due o più app per Android possono riprodurre audio nello stesso flusso di output contemporaneamente e il sistema mixa tutto. Sebbene sia tecnicamente impressionante, può essere molto irritante per un utente. Per evitare che tutte le app di musica vengano riprodotte contemporaneamente, Android introduce il concetto di messa a fuoco dell'audio. Solo un'app può mantenere il focus audio alla volta.

Quando la tua app deve riprodurre l'audio, deve richiedere la messa a fuoco audio. Quando è in primo piano, può riprodurre suoni. 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 la messa a fuoco, che precede la tua presa sulla messa a fuoco 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), la messa a fuoco audio non è gestita dal sistema. Pertanto, anche se gli sviluppatori di app sono invitati a rispettare le linee guida per la gestione della messa a fuoco audio, se un'app continua a riprodurre audio ad alto volume anche dopo aver perso la messa a fuoco 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 gli utenti a disinstallare l'app che non funziona correttamente.

Un'app audio ben progettata deve gestire la messa a fuoco audio in base a queste linee guida generali:

  • Chiama il numero requestAudioFocus() immediatamente prima di iniziare a giocare e verifica che la chiamata restituisca AUDIOFOCUS_REQUEST_GRANTED. Chiama requestAudioFocus() nel callback onPlay() della sessione multimediale.

  • Quando un'altra app acquisisce la priorità 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ù nulla da riprodurre), abbandona la messa a fuoco audio. La tua app non deve abbandonare la messa a fuoco audio se l'utente mette in pausa la riproduzione, ma potrebbe riprenderla in un secondo momento.

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

La gestione della messa a fuoco audio varia a seconda della versione di Android in esecuzione:

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

Focus audio in Android 12 e versioni successive

Un'app multimediale o di gioco che utilizza l'audio focus non deve riprodurre audio dopo aver perso il focus. In Android 12 (livello API 31) e versioni successive, il sistema impone questo comportamento. Quando un'app richiede l'audio focus mentre un'altra app ha il focus e sta riproducendo contenuti, il sistema forza la dissolvenza in uscita 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:

  1. La prima app in riproduzione soddisfa tutti questi criteri:

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

Quando queste condizioni sono soddisfatte, il sistema audio attenua l'audio della prima app. Al termine dell'attenuazione, il sistema notifica alla prima app la perdita dello stato attivo. I lettori dell'app rimangono disattivati finché l'app non richiede di nuovo il focus audio.

Comportamenti esistenti della messa a fuoco dell'audio

Devi anche tenere presente questi altri casi che comportano un cambio di messa a fuoco dell'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 il ducking, non devi implementarlo nella tua app.

La riduzione 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 riduzione.

La riduzione automatica si verifica quando sono soddisfatte le seguenti condizioni:

  1. La prima app in riproduzione soddisfa tutti questi criteri:

  2. Una seconda app richiede l'audio 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 è in primo piano. Quando la seconda app perde il focus, viene sganciata. La prima app non riceve una notifica quando perde lo stato attivo, quindi non deve fare nulla.

Tieni presente che la riduzione automatica non viene eseguita quando l'utente ascolta contenuti vocali, perché potrebbe perdere parte del programma. Ad esempio, le indicazioni vocali per le indicazioni stradali non vengono attenuate.

Disattivare la riproduzione audio corrente per le chiamate in arrivo

Alcune app non funzionano correttamente e continuano a riprodurre audio durante le chiamate. Questa situazione costringe l'utente a trovare e disattivare l'audio o chiudere l'app incriminata per sentire la chiamata. Per evitare questo problema, il sistema può disattivare l'audio di altre app quando c'è una chiamata in arrivo. Il sistema richiama questa funzionalità quando viene ricevuta una chiamata in arrivo e un'app soddisfa le seguenti condizioni:

  • L'app ha l'attributo di utilizzo AudioAttributes.USAGE_MEDIA o AudioAttributes.USAGE_GAME.
  • L'app ha richiesto correttamente l'audio focus (qualsiasi acquisizione del focus) e sta riproducendo audio.

Se un'app continua a essere riprodotta durante la chiamata, la riproduzione viene disattivata fino al termine della chiamata. Tuttavia, se un'app inizia la riproduzione durante la chiamata, il player non viene disattivato, in quanto si presume 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 e sulle funzionalità audio della tua app. Il sistema utilizza queste informazioni per gestire automaticamente l'acquisizione e la perdita della messa a fuoco 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 che quando abbandoni la messa a fuoco.

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 per il builder. Utilizza i metodi del builder per impostare gli altri campi della richiesta.

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

MetodoNote
setFocusGain() Questo campo è obbligatorio in ogni richiesta. Assume gli stessi valori di durationHint utilizzato 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 lo stato attivo audio. 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 ritirati. Utilizza gli stessi attributi nella richiesta di messa a fuoco che utilizzi nel tuo lettore audio (come mostrato nell'esempio che segue questa tabella).

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

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

setWillPauseWhenDucked() Quando un'altra app richiede il focus con AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, l'app che ha il focus di solito non riceve un callback onAudioFocusChange() perché il sistema può eseguire la riduzione del volume da solo. 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.
setAcceptsDelayedFocusGain() Una richiesta di focus audio può non riuscire quando il focus è bloccato da un'altra app. Questo metodo consente l'acquisizione ritardata del focus: la possibilità di acquisire in modo asincrono il focus quando diventa disponibile.

Tieni presente che l'acquisizione ritardata del focus funziona solo se specifichi anche un AudioManager.OnAudioFocusChangeListener nella richiesta audio, poiché la tua app deve ricevere il callback per sapere che il focus è stato concesso.

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 del gestore. L'handler è il thread su cui viene eseguito il listener. Se non specifichi un gestore, viene utilizzato quello associato al Looper principale.

L'esempio seguente mostra come utilizzare un AudioFocusRequest.Builder per creare un AudioFocusRequest e richiedere e abbandonare la messa a fuoco 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 lo stato attivo con AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK il sistema può abbassare e ripristinare il volume senza richiamare il callback onAudioFocusChange() dell'app.

Sebbene la riduzione automatica sia un comportamento accettabile per le app di riproduzione di musica e video, non è utile quando si riproducono contenuti parlati, ad esempio in un'app di audiolibri. In questo caso, l'app deve mettere in pausa.

Se vuoi che la tua app si metta in pausa quando le viene chiesto di abbassare il volume anziché diminuirlo, crea un OnAudioFocusChangeListener con un metodo di callback onAudioFocusChange() che implementi il comportamento di pausa/riproduzione desiderato. Chiama il numero setOnAudioFocusChangeListener() per registrare l'ascoltatore e chiama il numero setWillPauseWhenDucked(true) per indicare al sistema di utilizzare il richiamo anziché eseguire l'abbassamento automatico.

Acquisizione ritardata della messa a fuoco

A volte il sistema non può concedere una richiesta di focus audio perché il focus è "bloccato" da un'altra app, ad esempio durante una telefonata. In questo caso, requestAudioFocus() restituisce AUDIOFOCUS_REQUEST_FAILED. In questo caso, la tua app non deve procedere con la riproduzione audio perché non ha acquisito il focus.

Il metodo, setAcceptsDelayedFocusGain(true), che consente all'app di gestire una richiesta di messa a fuoco in modo asincrono. Se questo flag è impostato, una richiesta effettuata quando la messa a fuoco è bloccata restituisce AUDIOFOCUS_REQUEST_DELAYED. Quando la condizione che ha bloccato il focus audio non esiste più, ad esempio quando una chiamata termina, il sistema concede la richiesta di focus in attesa e chiama onAudioFocusChange() per notificare la 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 registrare il listener chiamando setOnAudioFocusChangeListener().

Focus audio in Android 7.1 e versioni precedenti

Quando chiami requestAudioFocus() devi specificare un suggerimento sulla durata, che potrebbe essere rispettato da un'altra app attualmente in primo piano e in riproduzione:

  • Richiedi la messa a fuoco audio permanente (AUDIOFOCUS_GAIN) quando prevedi di riprodurre audio per il futuro prevedibile (ad esempio, quando ascolti musica) e ti aspetti che il precedente titolare della messa a fuoco audio interrompa la riproduzione.
  • Richiedi la messa in primo piano temporanea (AUDIOFOCUS_GAIN_TRANSIENT) quando prevedi di riprodurre l'audio solo per un breve periodo di tempo e prevedi che il precedente titolare metta in pausa la riproduzione.
  • Richiedi la messa in primo piano temporanea con attenuazione (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) per indicare che prevedi di riprodurre l'audio solo per un breve periodo di tempo e che il precedente proprietario della messa in primo piano può continuare la riproduzione se "attenua" (abbassa) l'output audio. Entrambi gli output audio vengono mixati nello stream audio. La riduzione è particolarmente adatta alle app che utilizzano il flusso 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 servizio proprietario della sessione multimediale. Implementa il callback onAudioFocusChange() che la tua app riceve quando un'altra app acquisisce o abbandona l'audio focus.

Il seguente snippet richiede la messa a fuoco audio permanente sullo stream STREAM_MUSIC e registra un OnAudioFocusChangeListener per gestire le successive modifiche della messa a fuoco audio. Il listener di modifica è descritto in Rispondere a una modifica dello stato di messa a fuoco 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 avvisato che non è più necessario mettere a fuoco e viene annullata la registrazione del OnAudioFocusChangeListener associato. Se hai richiesto la messa in primo piano temporanea, un'app in pausa o che ha abbassato il volume riceverà una notifica che le comunica che può continuare la riproduzione o ripristinare il volume.

Rispondere a una modifica della messa a fuoco audio

Quando un'app acquisisce lo stato attivo 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() nell'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 acquisisce lo stato attivo. La tua app deve rispondere in modo appropriato.

Perdita temporanea della messa a fuoco
Se il cambio di messa a fuoco è temporaneo (AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK o AUDIOFOCUS_LOSS_TRANSIENT), l'app deve abbassare il volume (se non si basa sull'abbassamento automatico) o mettere in pausa la riproduzione, ma mantenere lo stesso stato.

Durante una perdita temporanea del focus audio, devi continuare a monitorare le modifiche del focus audio ed essere pronto a riprendere la normale riproduzione quando riacquisisci il focus. Quando l'app di blocco abbandona lo stato attivo, 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 di focus audio è permanente (AUDIOFOCUS_LOSS), un'altra app sta riproducendo audio. La riproduzione dell'app deve essere messa in pausa immediatamente, in quanto non riceverà mai un callback AUDIOFOCUS_GAIN. Per riavviare la riproduzione, l'utente deve eseguire un'azione esplicita, ad esempio premere il controllo 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'uso di un Handler per ritardare il callback di interruzione in caso di perdita permanente della messa a fuoco 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 qualsiasi modifica dello stato. Ad esempio, chiama removeCallbacks() nel callback onPlay(), onSkipToNext() e così via. Devi chiamare questo metodo anche nel callback onDestroy() del servizio durante la pulizia delle risorse utilizzate dal servizio.