Riproduzione in background con MediaSessionService

Spesso è consigliabile riprodurre contenuti multimediali quando un'app non è in primo piano. Per Ad esempio, un lettore musicale in genere continua a riprodurre musica anche quando l'utente blocca il suo dispositivo o sta utilizzando un'altra app. La libreria Media3 fornisce una serie che consentono di supportare la riproduzione in background.

Utilizzare MediaSessionService

Per attivare la riproduzione in background, devi contenere Player e MediaSession all'interno di un servizio separato. In questo modo il dispositivo può continuare a pubblicare contenuti multimediali anche quando l'app non è in in primo piano.

MediaSessionService consente di eseguire separatamente la sessione multimediale
  dall'attività dell'app
Figura 1: MediaSessionService consente ai contenuti multimediali eseguita separatamente dall'attività dell'app

Quando ospiti un player all'interno di un servizio, devi usare un MediaSessionService. Per farlo, crea una classe che estenda MediaSessionService" e crea il tuo sessione multimediale al suo interno.

L'utilizzo di MediaSessionService rende possibile per i clienti esterni come Google L'assistente, i controlli multimediali di sistema o i dispositivi associati, come Wear OS, da rilevare il tuo servizio, connetterti allo stesso e controllare la riproduzione, il tutto senza accedere ai tuoi dell'attività UI dell'app. Di fatto, possono essere collegate più app client alla stessa MediaSessionService contemporaneamente, ogni app con MediaController.

Implementare il ciclo di vita del servizio

Devi implementare tre metodi del ciclo di vita del servizio:

  • onCreate() viene chiamato quando il primo controller sta per connettersi e viene creata un'istanza e avviato il servizio. È il posto migliore per creare Player e MediaSession.
  • onTaskRemoved(Intent) viene chiamato quando l'utente chiude l'app dall'app attività recenti. Se la riproduzione è in corso, l'app può scegliere di mantenere il servizio in esecuzione in primo piano. Se il player è in pausa, il servizio non è in in primo piano e deve essere interrotto.
  • onDestroy() viene chiamato quando il servizio viene interrotto. Tutte le risorse inclusi il player e la sessione.
di Gemini Advanced.

Kotlin

class PlaybackService : MediaSessionService() {
  private var mediaSession: MediaSession? = null

  // Create your player and media session in the onCreate lifecycle event
  override fun onCreate() {
    super.onCreate()
    val player = ExoPlayer.Builder(this).build()
    mediaSession = MediaSession.Builder(this, player).build()
  }

  // The user dismissed the app from the recent tasks
  override fun onTaskRemoved(rootIntent: Intent?) {
    val player = mediaSession?.player!!
    if (!player.playWhenReady
        || player.mediaItemCount == 0
        || player.playbackState == Player.STATE_ENDED) {
      // Stop the service if not playing, continue playing in the background
      // otherwise.
      stopSelf()
    }
  }

  // Remember to release the player and media session in onDestroy
  override fun onDestroy() {
    mediaSession?.run {
      player.release()
      release()
      mediaSession = null
    }
    super.onDestroy()
  }
}

Java

public class PlaybackService extends MediaSessionService {
  private MediaSession mediaSession = null;

  // Create your Player and MediaSession in the onCreate lifecycle event
  @Override
  public void onCreate() {
    super.onCreate();
    ExoPlayer player = new ExoPlayer.Builder(this).build();
    mediaSession = new MediaSession.Builder(this, player).build();
  }

  // The user dismissed the app from the recent tasks
  @Override
  public void onTaskRemoved(@Nullable Intent rootIntent) {
    Player player = mediaSession.getPlayer();
    if (!player.getPlayWhenReady()
        || player.getMediaItemCount() == 0
        || player.getPlaybackState() == Player.STATE_ENDED) {
      // Stop the service if not playing, continue playing in the background
      // otherwise.
      stopSelf();
    }
  }

  // Remember to release the player and media session in onDestroy
  @Override
  public void onDestroy() {
    mediaSession.getPlayer().release();
    mediaSession.release();
    mediaSession = null;
    super.onDestroy();
  }
}

Invece di mantenere la riproduzione in background, un'app può interrompere il servizio in ogni caso quando l'utente ignora l'app:

Kotlin

override fun onTaskRemoved(rootIntent: Intent?) {
  val player = mediaSession.player
  if (player.playWhenReady) {
    // Make sure the service is not in foreground.
    player.pause()
  }
  stopSelf()
}

Java

@Override
public void onTaskRemoved(@Nullable Intent rootIntent) {
  Player player = mediaSession.getPlayer();
  if (player.getPlayWhenReady()) {
    // Make sure the service is not in foreground.
    player.pause();
  }
  stopSelf();
}

Fornire l'accesso alla sessione multimediale

Sostituisci il metodo onGetSession() per concedere ad altri clienti l'accesso ai tuoi contenuti multimediali creata al momento della creazione del servizio.

Kotlin

class PlaybackService : MediaSessionService() {
  private var mediaSession: MediaSession? = null
  // [...] lifecycle methods omitted

  override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
    mediaSession
}

Java

public class PlaybackService extends MediaSessionService {
  private MediaSession mediaSession = null;
  // [...] lifecycle methods omitted

  @Override
  public MediaSession onGetSession(MediaSession.ControllerInfo controllerInfo) {
    return mediaSession;
  }
}

Dichiara il servizio nel file manifest

Un'app richiede l'autorizzazione per eseguire un servizio in primo piano. Aggiungi il parametro FOREGROUND_SERVICE per il manifest e se scegli come target l'API 34 e sopra anche FOREGROUND_SERVICE_MEDIA_PLAYBACK:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />

Devi anche dichiarare la classe Service nel manifest con un filtro per intent di MediaSessionService.

<service
    android:name=".PlaybackService"
    android:foregroundServiceType="mediaPlayback"
    android:exported="true">
    <intent-filter>
        <action android:name="androidx.media3.session.MediaSessionService"/>
    </intent-filter>
</service>

Devi definire un foregroundServiceType che include mediaPlayback quando la tua app è in esecuzione su un dispositivo Android 10 (livello API 29) e successivi.

Controllare la riproduzione usando un MediaController

Nell'attività o nel frammento contenente l'interfaccia utente del player, puoi stabilire un link tra la UI e la tua sessione multimediale utilizzando un MediaController. L'interfaccia utente utilizza il controller multimediale per inviare comandi dall'interfaccia utente al player all'interno durante la sessione. Consulta le Crea un MediaController guida per i dettagli sulla creazione e l'utilizzo di un MediaController.

Gestire i comandi UI

MediaSession riceve i comandi dal controller attraverso la sua MediaSession.Callback. L'inizializzazione di un MediaSession crea un'istanza predefinita implementazione di MediaSession.Callback che gestisce automaticamente tutti i comandi inviati da MediaController al tuo player.

Notifica

Un MediaSessionService crea automaticamente un MediaNotification per te dovrebbe funzionare nella maggior parte dei casi. Per impostazione predefinita, la notifica pubblicata è Notifica MediaStyle che viene sempre aggiornato con le informazioni più recenti dalla sessione multimediale e mostra i controlli di riproduzione. MediaNotification è a conoscenza della tua sessione e può essere utilizzato per controllare la riproduzione di qualsiasi altra app collegate alla stessa sessione.

Ad esempio, un'app di streaming di musica che utilizza un MediaSessionService crea un MediaNotification che mostra il titolo, l'artista e la copertina dell'album l'elemento multimediale corrente riprodotto insieme ai controlli di riproduzione in base ai tuoi Configurazione di MediaSession.

I metadati richiesti possono essere forniti nei contenuti multimediali o dichiarati nell'ambito del elemento multimediale come nello snippet seguente:

Kotlin

val mediaItem =
    MediaItem.Builder()
      .setMediaId("media-1")
      .setUri(mediaUri)
      .setMediaMetadata(
        MediaMetadata.Builder()
          .setArtist("David Bowie")
          .setTitle("Heroes")
          .setArtworkUri(artworkUri)
          .build()
      )
      .build()

mediaController.setMediaItem(mediaItem)
mediaController.prepare()
mediaController.play()

Java

MediaItem mediaItem =
    new MediaItem.Builder()
        .setMediaId("media-1")
        .setUri(mediaUri)
        .setMediaMetadata(
            new MediaMetadata.Builder()
                .setArtist("David Bowie")
                .setTitle("Heroes")
                .setArtworkUri(artworkUri)
                .build())
        .build();

mediaController.setMediaItem(mediaItem);
mediaController.prepare();
mediaController.play();

Le app possono personalizzare i pulsanti di comando dei controlli di Android Media. Scopri di più sulla personalizzazione dei contenuti multimediali Android Google Cloud.

Personalizzazione delle notifiche

Per personalizzare la notifica, crea un MediaNotification.Provider con DefaultMediaNotificationProvider.Builder oppure creando un'implementazione personalizzata dell'interfaccia del provider. Aggiungi il tuo fornitore al tuo MediaSessionService con setMediaNotificationProvider

Ripresa della riproduzione

I pulsanti multimediali sono pulsanti hardware presenti su dispositivi Android e altre periferiche dispositivi, ad esempio il pulsante di riproduzione o pausa delle cuffie Bluetooth. Contenuti multimediali 3 gestisce gli input dei pulsanti multimediali quando il servizio è in esecuzione.

Dichiara il ricevitore del pulsante multimediale Media3

Media3 include un'API per consentire agli utenti di riprendere la riproduzione dopo l'interruzione di un'app e anche dopo che il dispositivo è stata riavviata. La ripresa della riproduzione è disattivata per impostazione predefinita. Ciò significa che l'utente non puoi riprendere la riproduzione quando il servizio non è in esecuzione. Per attivare la funzionalità, inizia con dichiarando MediaButtonReceiver nel file manifest:

<receiver android:name="androidx.media3.session.MediaButtonReceiver"
  android:exported="true">
  <intent-filter>
    <action android:name="android.intent.action.MEDIA_BUTTON" />
  </intent-filter>
</receiver>

Implementa il callback di ripresa della riproduzione

Quando la ripresa della riproduzione è richiesta da un dispositivo Bluetooth o dal Funzionalità di ripresa della UI di sistema Android, onPlaybackResumption() viene chiamato il metodo di callback.

Kotlin

override fun onPlaybackResumption(
    mediaSession: MediaSession,
    controller: ControllerInfo
): ListenableFuture<MediaItemsWithStartPosition> {
  val settable = SettableFuture.create<MediaItemsWithStartPosition>()
  scope.launch {
    // Your app is responsible for storing the playlist and the start position
    // to use here
    val resumptionPlaylist = restorePlaylist()
    settable.set(resumptionPlaylist)
  }
  return settable
}

Java

@Override
public ListenableFuture<MediaItemsWithStartPosition> onPlaybackResumption(
    MediaSession mediaSession,
    ControllerInfo controller
) {
  SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create();
  settableFuture.addListener(() -> {
    // Your app is responsible for storing the playlist and the start position
    // to use here
    MediaItemsWithStartPosition resumptionPlaylist = restorePlaylist();
    settableFuture.set(resumptionPlaylist);
  }, MoreExecutors.directExecutor());
  return settableFuture;
}

Se hai memorizzato altri parametri, come la velocità di riproduzione, la modalità di ripetizione o riproduzione casuale, onPlaybackResumption() è il posto giusto per configurare il player con questi parametri prima che Media3 prepari il player e inizi la riproduzione quando viene completato il callback.

Configurazione avanzata del controller e compatibilità con le versioni precedenti

Uno scenario comune è l'utilizzo di un MediaController nell'UI dell'app per controllare la riproduzione e la visualizzazione della playlist. Allo stesso tempo, la sessione è esposta per i client esterni come i controlli multimediali di Android e l'assistente su dispositivi mobili o TV, Wear OS per orologi e Android Auto nelle auto. L'app demo sessione Media3 è un esempio di app che implementa questo scenario.

Questi client esterni potrebbero utilizzare API come MediaControllerCompat della versione precedente nella libreria AndroidX o nella versione android.media.session.MediaController di Android il modello di machine learning. Media3 è completamente compatibile con le versioni precedenti della libreria Fornisce l'interoperabilità con l'API Android Framework.

Utilizzare il controller delle notifiche multimediali

È importante capire che questi controller legacy o framework leggono gli stessi valori del framework PlaybackState.getActions() e PlaybackState.getCustomActions(). Per determinare le azioni e le azioni personalizzate di sessione del framework, un'app può usare il controller di notifica multimediale e impostare i comandi disponibili e il layout personalizzato. Il servizio connette i contenuti multimediali il controller di notifica alla tua sessione, e la sessione utilizza ConnectionResult restituito dal onConnect() del callback per configurare azioni e azioni personalizzate della sessione del framework.

In uno scenario esclusivamente mobile, un'app può fornire un'implementazione MediaSession.Callback.onConnect() per impostare i comandi disponibili e layout personalizzato appositamente per la sessione del framework:

Kotlin

override fun onConnect(
  session: MediaSession,
  controller: MediaSession.ControllerInfo
): ConnectionResult {
  if (session.isMediaNotificationController(controller)) {
    val sessionCommands =
      ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon()
        .add(customCommandSeekBackward)
        .add(customCommandSeekForward)
        .build()
    val playerCommands =
      ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
        .remove(COMMAND_SEEK_TO_PREVIOUS)
        .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
        .remove(COMMAND_SEEK_TO_NEXT)
        .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
        .build()
    // Custom layout and available commands to configure the legacy/framework session.
    return AcceptedResultBuilder(session)
      .setCustomLayout(
        ImmutableList.of(
          createSeekBackwardButton(customCommandSeekBackward),
          createSeekForwardButton(customCommandSeekForward))
      )
      .setAvailablePlayerCommands(playerCommands)
      .setAvailableSessionCommands(sessionCommands)
      .build()
  }
  // Default commands with default custom layout for all other controllers.
  return AcceptedResultBuilder(session).build()
}

Java

@Override
public ConnectionResult onConnect(
    MediaSession session, MediaSession.ControllerInfo controller) {
  if (session.isMediaNotificationController(controller)) {
    SessionCommands sessionCommands =
        ConnectionResult.DEFAULT_SESSION_COMMANDS
            .buildUpon()
            .add(customCommandSeekBackward)
            .add(customCommandSeekForward)
            .build();
    Player.Commands playerCommands =
        ConnectionResult.DEFAULT_PLAYER_COMMANDS
            .buildUpon()
            .remove(COMMAND_SEEK_TO_PREVIOUS)
            .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
            .remove(COMMAND_SEEK_TO_NEXT)
            .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
            .build();
    // Custom layout and available commands to configure the legacy/framework session.
    return new AcceptedResultBuilder(session)
        .setCustomLayout(
            ImmutableList.of(
                createSeekBackwardButton(customCommandSeekBackward),
                createSeekForwardButton(customCommandSeekForward)))
        .setAvailablePlayerCommands(playerCommands)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
  // Default commands without default custom layout for all other controllers.
  return new AcceptedResultBuilder(session).build();
}

Autorizza Android Auto a inviare comandi personalizzati

Se utilizzi una MediaLibraryService e per supportare Android Auto con l'app mobile, il controller Android Auto richiede comandi disponibili appropriati, altrimenti Media3 nega comandi personalizzati in entrata da quel controller:

Kotlin

override fun onConnect(
  session: MediaSession,
  controller: MediaSession.ControllerInfo
): ConnectionResult {
  val sessionCommands =
    ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
      .add(customCommandSeekBackward)
      .add(customCommandSeekForward)
      .build()
  if (session.isMediaNotificationController(controller)) {
    // [...] See above.
  } else if (session.isAutoCompanionController(controller)) {
    // Available session commands to accept incoming custom commands from Auto.
    return AcceptedResultBuilder(session)
      .setAvailableSessionCommands(sessionCommands)
      .build()
  }
  // Default commands with default custom layout for all other controllers.
  return AcceptedResultBuilder(session).build()
}

Java

@Override
public ConnectionResult onConnect(
    MediaSession session, MediaSession.ControllerInfo controller) {
  SessionCommands sessionCommands =
      ConnectionResult.DEFAULT_SESSION_COMMANDS
          .buildUpon()
          .add(customCommandSeekBackward)
          .add(customCommandSeekForward)
          .build();
  if (session.isMediaNotificationController(controller)) {
    // [...] See above.
  } else if (session.isAutoCompanionController(controller)) {
    // Available commands to accept incoming custom commands from Auto.
    return new AcceptedResultBuilder(session)
        .setAvailableSessionCommands(sessionCommands)
        .build();
  }
  // Default commands without default custom layout for all other controllers.
  return new AcceptedResultBuilder(session).build();
}

L'app demo della sessione include automotive, che dimostra il supporto di Automotive OS che richiede un APK separato.