Spesso è auspicabile riprodurre contenuti multimediali mentre un'app non è in primo piano. Ad esempio, un lettore musicale in genere continua a riprodurre musica quando l'utente ha bloccato il dispositivo o sta utilizzando un'altra app. La libreria Media3 fornisce una serie di interfacce che consentono di supportare la riproduzione in background.
Utilizzare un `MediaSessionService`
Per abilitare la riproduzione in background, devi inserire 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
primo piano.
MediaSessionService consente alla sessione multimediale
di essere eseguita separatamente dall'attività dell'appQuando ospiti un player all'interno di un servizio, devi utilizzare un MediaSessionService.
Per farlo, crea una classe che estenda MediaSessionService e crea la tua
sessione multimediale al suo interno.
L'utilizzo di MediaSessionService consente ai client esterni come l'Assistente Google, i controlli multimediali di sistema, i pulsanti multimediali sui dispositivi periferici o i dispositivi complementari come Wear OS di scoprire il tuo servizio, connettersi e controllare la riproduzione, il tutto senza accedere all'attività della UI dell'app. Infatti,
possono essere collegate più app client allo stesso MediaSessionService contemporaneamente, ognuna con il proprio MediaController.
Implementare il ciclo di vita del servizio
Devi implementare due metodi del ciclo di vita del servizio:
onCreate()viene chiamato quando il primo controller sta per connettersi e il servizio viene creato e avviato. È il posto migliore per crearePlayereMediaSession.onDestroy()viene chiamato quando il servizio viene arrestato. È necessario rilasciare tutte le risorse inclusi il player e la sessione.
Facoltativamente, puoi sostituire onTaskRemoved(Intent) per personalizzare ciò che accade
quando l'utente chiude l'app dalle attività recenti. Per impostazione predefinita, il servizio
viene lasciato in esecuzione se la riproduzione è in corso e viene arrestato in caso contrario.
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() } // 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(); } // Remember to release the player and media session in onDestroy @Override public void onDestroy() { mediaSession.getPlayer().release(); mediaSession.release(); mediaSession = null; super.onDestroy(); } }
In alternativa a mantenere la riproduzione in background, puoi arrestare il servizio in ogni caso quando l'utente chiude l'app:
Kotlin
override fun onTaskRemoved(rootIntent: Intent?) { pauseAllPlayersAndStopSelf() }
Java
@Override public void onTaskRemoved(@Nullable Intent rootIntent) { pauseAllPlayersAndStopSelf(); }
Per qualsiasi altra implementazione manuale di onTaskRemoved, puoi utilizzare
isPlaybackOngoing() per verificare se la riproduzione è considerata in corso e se il
servizio in primo piano è stato avviato.
Fornire l'accesso alla sessione multimediale
Sostituisci il metodo onGetSession() per consentire ad altri client di accedere alla sessione multimediale
creata durante la 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; } }
Dichiarare il servizio nel manifest
Un'app richiede le autorizzazioni FOREGROUND_SERVICE e FOREGROUND_SERVICE_MEDIA_PLAYBACK
per eseguire un servizio in primo piano di riproduzione:
<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 e un foregroundServiceType che includa
mediaPlayback.
<service
android:name=".PlaybackService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
Controllare la riproduzione utilizzando un MediaController
Nell'attività o nel fragment contenente la UI del player, puoi stabilire un collegamento
tra la UI e la sessione multimediale utilizzando un MediaController. La UI utilizza
il controller multimediale per inviare comandi dalla UI al player all'interno della
sessione. Per informazioni dettagliate sulla creazione e sull'utilizzo di un MediaController, consulta la
guida Creare un MediaController.
Gestire i comandi MediaController
Il MediaSession riceve i comandi dal controller tramite il relativo
MediaSession.Callback. L'inizializzazione di un MediaSession crea un'implementazione predefinita
di MediaSession.Callback che gestisce automaticamente tutti i
comandi che un MediaController invia al player.
Notifica
Un MediaSessionService crea automaticamente un MediaNotification che
dovrebbe funzionare nella maggior parte dei casi. Per impostazione predefinita, la notifica pubblicata è una
MediaStyle notifica che rimane aggiornata con le informazioni più recenti
della sessione multimediale e mostra i controlli di riproduzione.
MediaNotification riconosce la sessione e può essere utilizzato per controllare la riproduzione
di qualsiasi altra app connessa alla stessa sessione.
Ad esempio, un'app di streaming musicale che utilizza un MediaSessionService creerebbe un
MediaNotification che mostra il titolo, l'artista e la copertina dell'album dell'
elemento multimediale corrente in riproduzione insieme ai controlli di riproduzione in base alla configurazione di
MediaSession.
I metadati obbligatori possono essere forniti nei contenuti multimediali o dichiarati come parte dell' 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();
Ciclo di vita delle notifiche
La notifica viene creata non appena Player ha istanze MediaItem
nella playlist.
Tutti gli aggiornamenti delle notifiche vengono eseguiti automaticamente in base allo stato di Player e
MediaSession.
La notifica non può essere rimossa durante l'esecuzione del servizio in primo piano. Per
rimuovere immediatamente la notifica, devi chiamare Player.release() o cancellare
la playlist utilizzando Player.clearMediaItems().
Se il player è in pausa, arrestato o non è riuscito a riprodurre per più di 10 minuti senza ulteriori interazioni dell'utente, il servizio passa automaticamente dallo stato del servizio in primo piano, in modo che possa essere eliminato dal sistema. Puoi implementare la ripresa della riproduzione per consentire a un utente di riavviare il ciclo di vita del servizio e riprendere la riproduzione in un secondo momento.
Personalizzazione delle notifiche
I metadati relativi all'elemento attualmente in riproduzione possono essere personalizzati modificando
il MediaItem.MediaMetadata. Se vuoi aggiornare i metadati di un elemento esistente, puoi utilizzare Player.replaceMediaItem per aggiornare i metadati senza
interrompere la riproduzione.
Puoi anche personalizzare alcuni dei pulsanti mostrati nella notifica impostando le preferenze dei pulsanti multimediali personalizzati per i controlli multimediali di Android. Scopri di più sulla personalizzazione dei controlli multimediali di Android.
Per personalizzare ulteriormente la notifica stessa, crea un
MediaNotification.Provider
con DefaultMediaNotificationProvider.Builder
o creando un'implementazione personalizzata dell'interfaccia del provider. Aggiungi il tuo
provider a MediaSessionService con
setMediaNotificationProvider.
Ripresa della riproduzione
Dopo che MediaSessionService è stato terminato e anche dopo il riavvio del dispositivo
, è possibile offrire la ripresa della riproduzione per consentire agli utenti
di riavviare il servizio e riprendere la riproduzione da dove era stata interrotta. Per impostazione predefinita,
la ripresa della riproduzione è disattivata. Ciò significa che l'utente non può riprendere la riproduzione
quando il servizio non è in esecuzione. Per attivare questa funzionalità, devi dichiarare
un ricevitore di pulsanti multimediali e implementare il onPlaybackResumption metodo.
Dichiarare il ricevitore di pulsanti multimediali Media3
Inizia dichiarando MediaButtonReceiver nel manifest:
<receiver android:name="androidx.media3.session.MediaButtonReceiver"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
Implementare il callback di ripresa della riproduzione
Quando la ripresa della riproduzione viene richiesta da un dispositivo Bluetooth o dalla
funzionalità di ripresa della UI di sistema Android ,
viene chiamato il metodo di callback onPlaybackResumption().
Kotlin
override fun onPlaybackResumption( mediaSession: MediaSession, controller: ControllerInfo, isForPlayback: Boolean, ): ListenableFuture<MediaItemsWithStartPosition> { val settable = SettableFuture.create<MediaItemsWithStartPosition>() scope.launch { // Your app is responsible for storing the playlist, metadata (like title // and artwork) of the current item 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, boolean isForPlayback ) { SettableFuture<MediaItemsWithStartPosition> settableFuture = SettableFuture.create(); settableFuture.addListener(() -> { // Your app is responsible for storing the playlist, metadata (like title // and artwork) of the current item 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 la
modalità di riproduzione casuale, onPlaybackResumption() è un buon punto per configurare il player
con questi parametri prima che Media3 prepari il player e avvii la riproduzione al termine
del callback.
Questo metodo viene chiamato durante l'avvio per creare la notifica di ripresa della UI di sistema Android
dopo il riavvio del dispositivo con isForPlayback impostato su
false. Per una notifica avanzata, ti consigliamo di compilare i MediaMetadata
campi come title e artworkData o artworkUri dell'elemento corrente con
valori disponibili localmente, poiché l'accesso alla rete potrebbe non essere ancora disponibile. Puoi
anche aggiungere MediaConstants.EXTRAS_KEY_COMPLETION_STATUS e
MediaConstants.EXTRAS_KEY_COMPLETION_PERCENTAGE a MediaMetadata.extras
per indicare la posizione di riproduzione della ripresa.
Configurazione avanzata del controller e compatibilità con le versioni precedenti
Uno scenario comune è l'utilizzo di un MediaController nella UI dell'app per controllare
la riproduzione e visualizzare la playlist. Allo stesso tempo, la sessione viene esposta
a client esterni come i controlli multimediali di Android e l'Assistente su dispositivi mobili o TV,
Wear OS per smartwatch e Android Auto nelle auto. L'app demo della sessione Media3
è un esempio di app che implementa questo scenario.
Questi client esterni possono utilizzare API come MediaControllerCompat della libreria AndroidX precedente o android.media.session.MediaController della piattaforma Android. Media3 è completamente compatibile con le versioni precedenti della libreria precedente e
fornisce l'interoperabilità con l'API della piattaforma Android.
Identificare i controller attendibili
Qualsiasi app può tentare di connettersi alla tua sessione o libreria multimediale. Se vuoi limitare l'accesso ai controller di sistema, ai controller con l'autorizzazione di controllo dei contenuti multimediali e alla tua app, puoi utilizzare ControllerInfo.isTrusted() per un controllo di accesso di base. In alternativa, puoi identificare controller più specifici
come il controller di notifiche multimediali o i controller Android Auto come descritto
nelle sezioni seguenti.
Utilizzare il controller di notifiche multimediali
È importante capire che questi controller precedenti e della piattaforma condividono
lo stesso stato e la visibilità non può essere personalizzata dal controller (ad esempio,
i valori disponibili PlaybackState.getActions() e PlaybackState.getCustomActions()).
Puoi utilizzare il controller di notifiche multimediali per
configurare lo stato impostato nella sessione multimediale della piattaforma per la compatibilità con questi
controller precedenti e della piattaforma.
Ad esempio, un'app può fornire un'implementazione di
MediaSession.Callback.onConnect() per impostare i comandi disponibili e le
preferenze dei pulsanti multimediali specificamente per la sessione della piattaforma come segue:
Kotlin
override fun onConnect( session: MediaSession, controller: MediaSession.ControllerInfo ): ConnectionResult { if (session.isMediaNotificationController(controller)) { 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 button preferences and commands to configure the platform session. return AcceptedResultBuilder(session) .setMediaButtonPreferences( listOf(seekBackButton, seekForwardButton) ) .setAvailablePlayerCommands(playerCommands) .build() } // Default commands with default button preferences for all other controllers. return AcceptedResultBuilder(session).build() }
Java
@Override public ConnectionResult onConnect( MediaSession session, MediaSession.ControllerInfo controller) { if (session.isMediaNotificationController(controller)) { 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 button preferences and commands to configure the platform session. return new AcceptedResultBuilder(session) .setMediaButtonPreferences( ImmutableList.of(seekBackButton, seekForwardButton)) .setAvailablePlayerCommands(playerCommands) .build(); } // Default commands with default button preferences for all other controllers. return new AcceptedResultBuilder(session).build(); }
Autorizzare Android Auto a inviare comandi personalizzati
Quando utilizzi un MediaLibraryService
e per supportare Android Auto con l'app mobile, il controller Android Auto
richiede i comandi disponibili appropriati, altrimenti Media3 negherebbe
i 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(customCommand) .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 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(customCommand) .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 for all other controllers. return new AcceptedResultBuilder(session).build(); }
L'app demo della sessione ha un modulo per auto, che dimostra il supporto per Automotive OS, che richiede un APK separato.