Gestire la messa a fuoco audio

Due o più app per Android possono riprodurre audio nello stesso stream di output contemporaneamente e il sistema li mescola tutti. Sebbene tecnicamente sia impressionante, può essere molto irritante per un utente. Per evitare che tutte le app di musica vengano riprodotte contemporaneamente, Android introduce l'idea di audio focus. Solo un'app alla volta può avere il controllo dell'audio.

Quando l'app deve riprodurre audio, deve richiedere l'attenzione audio. Quando è attivo, può riprodurre l'audio. Tuttavia, dopo aver acquisito il controllo audio, potresti non essere in grado di mantenerlo fino al termine della riproduzione. Un'altra app può richiedere l'attenzione, interrompendo il tuo controllo sull'audio. In questo 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), l'audio focus non è gestito dal sistema. Pertanto, anche se gli sviluppatori di app sono invitati a rispettare le linee guida per l'attenzione audio, se un'app continua a riprodurre a volume alto anche dopo aver perso l'attenzione audio su un dispositivo con Android 11 (livello API 30) o versioni precedenti, il sistema non può impedirlo. Tuttavia, questo comportamento dell'app comporta un'esperienza utente negativa e spesso può portare gli utenti a disinstallare l'app con problemi.

Un'app audio ben progettata deve gestire l'attenzione audio in base a queste linee guida generali:

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

  • Quando un'altra app acquisisce l'attenzione audio, interrompi o metti in pausa la riproduzione o abbassa il volume.

  • Quando la riproduzione si interrompe (ad esempio quando non c'è più nulla da riprodurre nell'app), abbandona il controllo audio. La tua app non deve abbandonare il controllo 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.

L'audio in primo piano viene gestito in modo diverso a seconda della versione di Android in uso:

Android 12 (livello API 31) o versioni successive
L'audio in primo piano è gestito dal sistema. Il sistema forza la disattivazione 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.
Da Android 8.0 (livello API 26) ad Android 11 (livello API 30)
L'audio focus 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
L'audio focus non è gestito dal sistema e le app lo gestiscono utilizzando requestAudioFocus() e abandonAudioFocus().

Audio focus in Android 12 e versioni successive

Un'app multimediale o di gioco che utilizza l'audio in primo piano non deve riprodurre l'audio dopo aver perso il primo piano. In Android 12 (livello API 31) e versioni successive, il sistema impone questo comportamento. Quando un'app richiede l'attenzione audio mentre un'altra app è attiva e in riproduzione, il sistema forza l'app in riproduzione a svanire. L'aggiunta dell'estinzione consente una transizione più fluida quando si passa da un'app all'altra.

Questo comportamento di attenuazione si verifica quando si verificano le seguenti condizioni:

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

  2. Una seconda app richiede l'attenzione audio con AudioManager.AUDIOFOCUS_GAIN.

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

Comportamenti esistenti per lo stato attivo dell'audio

Tieni presente anche questi altri casi che comportano un cambio di attenzione audio.

Attenuazione automatica audio

L'attenuazione automatica (riduzione temporanea del livello audio di un'app in modo da poter sentire chiaramente un'altra) è stata introdotta in Android 8.0 (livello API 26).

Se il sistema implementa la funzionalità, non devi farlo nell'app.

Il silenziamento automatico si verifica anche quando una notifica audio acquisisce il controllo da un'app in riproduzione. L'inizio della riproduzione della notifica è sincronizzato con la fine della rampa di silenziamento.

L'attenuazione automatica si verifica quando si verificano le seguenti condizioni:

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

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

Quando queste condizioni sono soddisfatte, il sistema audio mette in sospensione tutti i player attivi della prima app mentre la seconda app ha il focus. Quando la seconda app perde il focus, le estrae. 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 perdersi parte del programma. Ad esempio, le indicazioni stradali vocali non vengono attenuate.

Disattivare l'audio in riproduzione per le chiamate in arrivo

Alcune app non si comportano correttamente e continuano a riprodurre l'audio durante le telefonate. In questa situazione, l'utente deve trovare e disattivare l'audio o uscire dall'app in questione per ascoltare la chiamata. Per evitare questo, il sistema può disattivare l'audio di altre app quando è in arrivo una chiamata. 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_MEDIA o AudioAttributes.USAGE_GAME.
  • L'app ha richiesto correttamente l'attenzione audio (qualsiasi guadagno di attenzione) e sta riproducendo audio.

Se un'app continua a riprodurre contenuti durante la chiamata, la riproduzione viene disattivata fino al termine della chiamata. Tuttavia, se durante la chiamata viene avviata la riproduzione di un'app, il player non viene disattivato poiché si presume che l'utente abbia avviato la riproduzione intenzionalmente.

Audio focus in Android 8.0 fino ad 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 il guadagno e la perdita dell'attenzione audio. Per rilasciare il focus audio, chiama il metodo abandonAudioFocusRequest() che accetta anche un AudioFocusRequest come argomento. Utilizza la stessa istanzaAudioFocusRequest 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 per il generatore. Utilizza i metodi del generatore 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 del durationHint utilizzato nella chiamata a requestAudioFocus() precedente ad Android 8.0: 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'attenzione audio. Gli attributi superano il concetto 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 gli stessi attributi nella richiesta di messa a fuoco che utilizzi nel tuo player audio (come показано показано nell'esempio che segue questa tabella).

Utilizza un 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 il fuoco con AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK, l'app che ha il fuoco solitamente non riceve un callback onAudioFocusChange() perché il sistema può effettuare il ducking autonomamente. Quando devi mettere in pausa la riproduzione anziché attenuare il volume, chiama setWillPauseWhenDucked(true) e crea e imposta un OnAudioFocusChangeListener, come descritto in Attenuazione automatica.
setAcceptsDelayedFocusGain() Una richiesta di attenzione audio può non andare a buon fine quando l'attenzione è bloccata da un'altra app. Questo metodo consente il raggiungimento ritardato dell'attenzione: la possibilità di acquisire l'attenzione in modo asincrono quando diventa disponibile.

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

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

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

Il seguente esempio mostra come utilizzare un AudioFocusRequest.Builder per creare un AudioFocusRequest e richiedere e abbandonare l'attenzione 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 la riduzione automatica dell'audio sia un comportamento accettabile per le app di riproduzione di musica e video, non è utile per la riproduzione di contenuti parlati, ad esempio in un'app di audiolibri. In questo caso, l'app dovrebbe essere messa in pausa.

Se vuoi che l'app venga messa in pausa quando viene chiesto di abbassare il volume anziché diminuirlo, crea un OnAudioFocusChangeListener con un metodo di callback onAudioFocusChange() che implementi il comportamento di messa in pausa/ripristino desiderato. Chiama setOnAudioFocusChangeListener() per registrare l'ascoltatore e chiama setWillPauseWhenDucked(true) per indicare al sistema di utilizzare il tuo callback anziché eseguire il ducking automatico.

Guadagno di messa a fuoco ritardata

A volte il sistema non può soddisfare una richiesta di attenzione audio perché l'attenzione è "bloccata" 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 la messa a fuoco.

Il metodo setAcceptsDelayedFocusGain(true) che consente alla tua app di gestire una richiesta di messa in primo piano 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'audio in modalità di messa a fuoco non esiste più, ad esempio quando termina una chiamata, il sistema concede la richiesta di messa a fuoco in attesa e chiama onAudioFocusChange() per notificare la tua app.

Per gestire l'acquisizione ritardata dell'attenzione, devi creare un OnAudioFocusChangeListener con un metodo di callback onAudioFocusChange() che implementi il comportamento desiderato e registrare l'ascoltatore chiamando setOnAudioFocusChangeListener().

Audio focus in Android 7.1 e versioni precedenti

Quando chiami requestAudioFocus() devi specificare un suggerimento sulla durata, che può essere seguito da un'altra app che al momento ha il controllo e sta riproducendo:

  • Richiedi il controllo audio permanente (AUDIOFOCUS_GAIN) quando prevedi di riprodurre audio per il futuro prevedibile (ad esempio quando ascolti musica) e prevedi che il precedente detentore del controllo audio interrompa la riproduzione.
  • Richiedi il controllo transitorio (AUDIOFOCUS_GAIN_TRANSIENT) quando prevedi di riprodurre audio solo per poco tempo e prevedi che il proprietario precedente metta in pausa la riproduzione.
  • Richiedi l'attenzione transitoria con il controllo volume automatico (AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK) per indicare che prevedi di riprodurre l'audio solo per poco tempo e che è consentito al proprietario dell'attenzione precedente di continuare la riproduzione se riduce l'output audio. Entrambe le uscite audio vengono mescolate allo 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 ascoltatore deve essere creato nella stessa attività o nello stesso servizio che possiede la sessione multimediale. Implementa il callback onAudioFocusChange() che la tua app riceve quando un'altra app acquisisce o abbandona l'attenzione audio.

Il seguente snippet richiede il controllo audio permanente sullo streamSTREAM_MUSIC e registra un OnAudioFocusChangeListener per gestire le modifiche successive del controllo audio. L'ascoltatore di modifiche è descritto in Rispondi a una modifica dell'attenzione 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 di concentrazione e viene annullata la registrazione del OnAudioFocusChangeListener associato. Se hai richiesto l'attenzione temporanea, l'app messa in pausa o attenuata verrà avvisata che può continuare la riproduzione o ripristinare il volume.

Rispondere a una modifica dell'attenzione audio

Quando un'app acquisisce il controllo audio, deve essere in grado di rilasciarlo quando un'altra app richiede il controllo audio per sé stessa. In questo caso, la tua 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 che acquisisce lo stato attivo. L'app deve rispondere in modo appropriato.

Perdita temporanea della messa a fuoco
Se la modifica dell'attenzione è transitoria (AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK o AUDIOFOCUS_LOSS_TRANSIENT), l'app deve essere messa a basso volume (se non si basa su la disattivazione automatica del volume) o mettere in pausa la riproduzione, ma altrimenti mantenere lo stesso stato.

Durante una perdita temporanea del focus audio, devi continuare a monitorare le modifiche al focus audio ed essere pronto a riprendere la riproduzione normale quando ottieni nuovamente il focus. Quando l'app di blocco perde 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 dell'attenzione audio è permanente (AUDIOFOCUS_LOSS), un'altra app sta riproducendo audio. L'app dovrebbe mettere immediatamente in pausa la riproduzione, 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 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(). Tieni presente l'uso di un Handler per ritardare la chiamata di callback di stop in caso di perdita permanente dell'attenzione 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'arresto ritardato non venga attivato 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 del tuo callback. Devi anche chiamare questo metodo nel callback onDestroy() del tuo servizio quando elimini le risorse utilizzate dal servizio.