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 includere Player
e
MediaSession
in un servizio separato.
In questo modo il dispositivo può continuare a pubblicare contenuti multimediali anche quando l'app non è in
in primo piano.
Quando ospiti un player all'interno di un servizio, devi utilizzare un MediaSessionService
.
Per farlo, crea una classe che estenda MediaSessionService
e crea la sessione media al suo interno.
L'utilizzo di MediaSessionService
consente ai client esterni come l'Assistente Google, i controlli multimediali di sistema o i dispositivi companion come Wear OS di rilevare il tuo servizio, connettersi e controllare la riproduzione, il tutto senza accedere all'attività dell'interfaccia utente della tua app. Infatti, possono essere collegate contemporaneamente più app client allo stesso MediaSessionService
, ciascuna con il proprio MediaSessionService
.
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 il servizio viene creato e avviato. È il posto migliore per crearePlayer
eMediaSession
.onTaskRemoved(Intent)
viene chiamato quando l'utente chiude l'app dalle attività recenti. Se la riproduzione è in corso, l'app può scegliere di mantenere attivo il servizio in primo piano. Se il player è in pausa, il servizio non è in primo piano e deve essere interrotto.onDestroy()
viene chiamato quando il servizio viene interrotto. Tutte le risorse, inclusi il player e la sessione, devono essere rilasciate.
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 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 includa mediaPlayback
quando la tua app è in esecuzione su un dispositivo con Android
10 (livello API 29) e versioni successive.
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 sull'utilizzo di un MediaController
.
Gestire i comandi dell'interfaccia utente
Il MediaSession
riceve i comandi dal controller tramite il suo
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
che dovrebbe funzionare nella maggior parte dei casi. Per impostazione predefinita, la notifica pubblicata è
Notifica MediaStyle
che viene sempre aggiornata 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
collegata alla stessa sessione.
Ad esempio, un'app di streaming musicale che utilizza un MediaSessionService
crea un
MediaNotification
che mostra il titolo, l'artista e la copertina dell'elemento media corrente in riproduzione insieme ai controlli di riproduzione in base alla configurazione del
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 a 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 al termine di un'app e anche dopo il riavvio del dispositivo. 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>
Implementare 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 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.
Configurazione avanzata del controller e compatibilità con le versioni precedenti
Uno scenario comune è l'utilizzo di un MediaController
nell'interfaccia utente dell'app per controllare la riproduzione e visualizzare la playlist. Allo stesso tempo, la sessione è esposta a client esterni come i controlli multimediali e l'assistente Android su dispositivi mobili o TV, Wear OS per gli 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 libreria AndroidX precedente o android.media.session.MediaController
del framework Android. 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 precedenti o del framework leggono gli stessi valori dal framework PlaybackState.getActions()
e PlaybackState.getCustomActions()
. Per determinare le azioni e le azioni personalizzate della sessione del framework, un'app può utilizzare il controller di notifica multimediale e impostare i comandi e il layout personalizzato disponibili. 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 ha un modulo Automotive, che dimostra il supporto per il sistema operativo Automotive che richiede un APK separato.