Votre application doit déclarer l'MediaBrowserService
avec un filtre d'intent dans son fichier manifeste. Vous pouvez choisir votre propre nom de service, dans l'exemple suivant, il s'agit de "MediaPlaybackService".
<service android:name=".MediaPlaybackService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
Remarque : L'implémentation recommandée de MediaBrowserService
est MediaBrowserServiceCompat
.
qui est défini dans
bibliothèque Support Media-compat.
Sur cette page, le terme "MediaBrowserService" fait référence à une instance
sur MediaBrowserServiceCompat
.
Initialiser la session multimédia
Lorsque le service reçoit la méthode de rappel de cycle de vie onCreate()
, il doit effectuer les étapes suivantes:
- Créez et initialisez la session multimédia.
- Définir le rappel de la session multimédia
- Définir le jeton de session multimédia
Le code onCreate()
ci-dessous illustre ces étapes:
Kotlin
private const val MY_MEDIA_ROOT_ID = "media_root_id" private const val MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id" class MediaPlaybackService : MediaBrowserServiceCompat() { private var mediaSession: MediaSessionCompat? = null private lateinit var stateBuilder: PlaybackStateCompat.Builder override fun onCreate() { super.onCreate() // Create a MediaSessionCompat mediaSession = MediaSessionCompat(baseContext, LOG_TAG).apply { // Enable callbacks from MediaButtons and TransportControls setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS ) // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player stateBuilder = PlaybackStateCompat.Builder() .setActions(PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PLAY_PAUSE ) setPlaybackState(stateBuilder.build()) // MySessionCallback() has methods that handle callbacks from a media controller setCallback(MySessionCallback()) // Set the session's token so that client activities can communicate with it. setSessionToken(sessionToken) } } }
Java
public class MediaPlaybackService extends MediaBrowserServiceCompat { private static final String MY_MEDIA_ROOT_ID = "media_root_id"; private static final String MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id"; private MediaSessionCompat mediaSession; private PlaybackStateCompat.Builder stateBuilder; @Override public void onCreate() { super.onCreate(); // Create a MediaSessionCompat mediaSession = new MediaSessionCompat(context, LOG_TAG); // Enable callbacks from MediaButtons and TransportControls mediaSession.setFlags( MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS); // Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player stateBuilder = new PlaybackStateCompat.Builder() .setActions( PlaybackStateCompat.ACTION_PLAY | PlaybackStateCompat.ACTION_PLAY_PAUSE); mediaSession.setPlaybackState(stateBuilder.build()); // MySessionCallback() has methods that handle callbacks from a media controller mediaSession.setCallback(new MySessionCallback()); // Set the session's token so that client activities can communicate with it. setSessionToken(mediaSession.getSessionToken()); } }
Gérer les connexions client
Un MediaBrowserService
comporte deux méthodes pour gérer les connexions client:
Commandes onGetRoot()
l'accès au service ;
onLoadChildren()
permet à un client de créer et d'afficher un menu de la hiérarchie de contenu de MediaBrowserService
.
Contrôler les connexions client avec onGetRoot()
La méthode onGetRoot()
renvoie le nœud racine de la hiérarchie de contenu. Si le
renvoie une valeur nulle, la connexion est refusée.
Pour permettre aux clients de se connecter à votre service et de parcourir son contenu multimédia, onGetRoot() doit renvoyer un BrowserRoot non nul, c'est-à-dire un identifiant racine qui représente votre hiérarchie de contenu.
Pour permettre aux clients de se connecter à votre MediaSession sans naviguer, onGetRoot() doit quand même renvoyer une valeur BrowserRoot non nulle, mais l'ID racine doit représenter une une hiérarchie de contenu vide.
Une implémentation type de onGetRoot()
peut se présenter comme suit:
Kotlin
override fun onGetRoot( clientPackageName: String, clientUid: Int, rootHints: Bundle? ): MediaBrowserServiceCompat.BrowserRoot { // (Optional) Control the level of access for the specified package name. // You'll need to write your own logic to do this. return if (allowBrowsing(clientPackageName, clientUid)) { // Returns a root ID that clients can use with onLoadChildren() to retrieve // the content hierarchy. MediaBrowserServiceCompat.BrowserRoot(MY_MEDIA_ROOT_ID, null) } else { // Clients can connect, but this BrowserRoot is an empty hierarchy // so onLoadChildren returns nothing. This disables the ability to browse for content. MediaBrowserServiceCompat.BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null) } }
Java
@Override public BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints) { // (Optional) Control the level of access for the specified package name. // You'll need to write your own logic to do this. if (allowBrowsing(clientPackageName, clientUid)) { // Returns a root ID that clients can use with onLoadChildren() to retrieve // the content hierarchy. return new BrowserRoot(MY_MEDIA_ROOT_ID, null); } else { // Clients can connect, but this BrowserRoot is an empty hierarchy // so onLoadChildren returns nothing. This disables the ability to browse for content. return new BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null); } }
Dans certains cas, vous voudrez peut-être
contrôler qui peut se connecter
sur votre MediaBrowserService
. Une méthode consiste à utiliser
une liste de contrôle d'accès (LCA)
spécifiant les connexions autorisées, ou énumérant
les connexions qui doivent être interdites. Pour un exemple de la façon d'implémenter une LCA
qui autorise des connexions spécifiques, consultez
PackageValidator
de la classe Universal Android Music Player (Lecteur de musique universel Android)
application exemple.
Vous devriez envisager de proposer différentes hiérarchies de contenu en fonction
le type de client à l’origine de la requête. En particulier, Android Auto limite
les utilisateurs interagissent avec
les applications audio. Pour en savoir plus, consultez la section Lecture audio pour
Automatique. Toi
peut examiner clientPackageName
au moment de la connexion pour déterminer le client
et renvoient un BrowserRoot
différent en fonction du client (ou rootHints
le cas échéant).
Communication de contenu avec onLoadChildren()
Une fois le client connecté, il peut balayer la hiérarchie de contenu en effectuant des appels répétés à MediaBrowserCompat.subscribe()
pour créer une représentation locale de l'UI. La méthode subscribe()
envoie le rappel onLoadChildren()
au service, qui renvoie une liste d'objets MediaBrowser.MediaItem
.
Chaque MediaItem possède une chaîne d'identifiant unique, qui est un jeton opaque. Lorsqu'un client souhaite ouvrir un sous-menu ou lire un élément, il transmet l'ID. Votre service est chargé d'associer l'ID au nœud de menu ou à l'élément de contenu approprié.
Une implémentation simple de onLoadChildren()
peut se présenter comme suit:
Kotlin
override fun onLoadChildren( parentMediaId: String, result: MediaBrowserServiceCompat.Result<List<MediaBrowserCompat.MediaItem>> ) { // Browsing not allowed if (MY_EMPTY_MEDIA_ROOT_ID == parentMediaId) { result.sendResult(null) return } // Assume for example that the music catalog is already loaded/cached. val mediaItems = emptyList<MediaBrowserCompat.MediaItem>() // Check if this is the root menu: if (MY_MEDIA_ROOT_ID == parentMediaId) { // Build the MediaItem objects for the top level, // and put them in the mediaItems list... } else { // Examine the passed parentMediaId to see which submenu we're at, // and put the children of that menu in the mediaItems list... } result.sendResult(mediaItems) }
Java
@Override public void onLoadChildren(final String parentMediaId, final Result<List<MediaItem>> result) { // Browsing not allowed if (TextUtils.equals(MY_EMPTY_MEDIA_ROOT_ID, parentMediaId)) { result.sendResult(null); return; } // Assume for example that the music catalog is already loaded/cached. List<MediaItem> mediaItems = new ArrayList<>(); // Check if this is the root menu: if (MY_MEDIA_ROOT_ID.equals(parentMediaId)) { // Build the MediaItem objects for the top level, // and put them in the mediaItems list... } else { // Examine the passed parentMediaId to see which submenu we're at, // and put the children of that menu in the mediaItems list... } result.sendResult(mediaItems); }
Remarque : Les objets MediaItem
diffusés par MediaBrowserService
ne doivent pas contenir de bitmaps d'icône. Utilisez plutôt un Uri
en appelant
setIconUri()
lorsque vous créez le MediaDescription
pour chaque élément.
Pour découvrir comment implémenter onLoadChildren()
, consultez l'application exemple Universal Android Music Player.
Cycle de vie du service de navigateur multimédia
Le comportement d'un service Android varie selon qu'il est démarré ou lié à un ou plusieurs clients. Une fois le service créé, il peut être démarré, lié ou les deux. Dans tous ces états, il est entièrement fonctionnel et peut effectuer le travail pour lequel il a été conçu. La différence réside dans la durée d'existence du service. Un service lié n'est pas détruit tant que tous ses clients liés ne sont pas dissociés. Un service démarré peut être explicitement arrêté et détruit (en supposant qu'il n'est plus lié à des clients).
Lorsqu'un MediaBrowser
exécuté dans une autre activité se connecte à un MediaBrowserService
, il lie l'activité au service, ce qui le lie au service (mais n'est pas démarré). Ce comportement par défaut est intégré à la classe MediaBrowserServiceCompat
.
Un service qui est seulement lié (et non démarré) est détruit lorsque tous ses clients sont dissociés. Si l'activité de l'interface utilisateur se déconnecte à ce stade, le service est détruit. Ce n'est pas un problème si vous n'avez pas encore écouté de musique. Cependant, lorsque la lecture commence, l'utilisateur s'attend probablement à continuer d'écouter même après avoir changé d'application. Vous ne devez pas détruire le lecteur lorsque vous dissociez l'interface utilisateur pour qu'elle fonctionne avec une autre application.
Pour cette raison, vous devez vous assurer que le service est démarré lorsqu'il démarre
en appelant startService()
. A
démarré doit être explicitement arrêté, qu'il soit lié ou non. Ce
garantit que votre joueur continue de fonctionner même si l'UI de contrôle
d'activité est dissociée.
Pour arrêter un service démarré, appelez Context.stopService()
ou stopSelf()
. Le système arrête et détruit le service dès que possible. Toutefois, si un ou plusieurs clients sont toujours liés au service, l'appel visant à l'arrêter est retardé jusqu'à ce que tous ses clients soient dissociés.
Le cycle de vie de l'MediaBrowserService
est contrôlé par la façon dont il est créé, par le nombre de clients qui y sont liés et par les appels qu'il reçoit à partir des rappels de session multimédia. En résumé :
- Le service est créé lorsqu'il est démarré en réponse à un bouton multimédia ou lorsqu'une activité s'y associe (après connexion via son
MediaBrowser
). - Le rappel
onPlay()
de la session multimédia doit inclure du code qui appellestartService()
. Cela garantit que le service démarre et continue de s'exécuter, même lorsque toutes les activitésMediaBrowser
de l'UI qui y sont liées sont dissociées. - Le rappel
onStop()
doit appelerstopSelf()
. Si le service a été démarré, il est arrêté. En outre, le service est détruit si aucune activité ne lui est associée. Sinon, le service reste lié jusqu'à ce que toutes ses activités soient dissociées. (Si un appelstartService()
ultérieur est reçu avant que le service ne soit détruit, l'arrêt en attente est annulé.)
L'organigramme suivant illustre la gestion du cycle de vie d'un service. Le compteur de variables effectue le suivi du nombre de clients liés:
Utiliser les notifications MediaStyle avec un service de premier plan
Lorsqu'un service est en cours de lecture, il doit s'exécuter au premier plan. Cela permet au système de savoir que le service exécute une fonction utile et ne doit pas être arrêté si le système manque de mémoire. Un service de premier plan doit afficher une notification pour que l'utilisateur en soit informé et puisse éventuellement le contrôler. Le rappel onPlay()
doit placer le service au premier plan. Notez qu'il s'agit d'une signification particulière du terme "premier plan". Bien qu'Android considère le service au premier plan pour la gestion des processus, pour l'utilisateur, le lecteur lit en arrière-plan tandis qu'une autre application est visible au "premier plan". à l'écran.)
Lorsqu'un service s'exécute au premier plan, il doit afficher une notification, avec idéalement une ou plusieurs commandes de transport. La notification doit également inclure des informations utiles provenant des métadonnées de la session.
Créez et affichez la notification lorsque le joueur commence à jouer. Pour ce faire, nous vous recommandons d'utiliser la méthode MediaSessionCompat.Callback.onPlay()
.
L'exemple ci-dessous utilise la
NotificationCompat.MediaStyle
,
conçu pour les applications multimédias. Il explique comment créer une notification qui affiche les métadonnées et les commandes de transport. La méthode pratique
getController()
vous permet de créer un contrôleur multimédia directement depuis votre session multimédia.
Kotlin
// Given a media session and its context (usually the component containing the session) // Create a NotificationCompat.Builder // Get the session's metadata val controller = mediaSession.controller val mediaMetadata = controller.metadata val description = mediaMetadata.description val builder = NotificationCompat.Builder(context, channelId).apply { // Add the metadata for the currently playing track setContentTitle(description.title) setContentText(description.subtitle) setSubText(description.description) setLargeIcon(description.iconBitmap) // Enable launching the player by clicking the notification setContentIntent(controller.sessionActivity) // Stop the service when the notification is swiped away setDeleteIntent( MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_STOP ) ) // Make the transport controls visible on the lockscreen setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // Add an app icon and set its accent color // Be careful about the color setSmallIcon(R.drawable.notification_icon) color = ContextCompat.getColor(context, R.color.primaryDark) // Add a pause button addAction( NotificationCompat.Action( R.drawable.pause, getString(R.string.pause), MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_PLAY_PAUSE ) ) ) // Take advantage of MediaStyle features setStyle(android.support.v4.media.app.NotificationCompat.MediaStyle() .setMediaSession(mediaSession.sessionToken) .setShowActionsInCompactView(0) // Add a cancel button .setShowCancelButton(true) .setCancelButtonIntent( MediaButtonReceiver.buildMediaButtonPendingIntent( context, PlaybackStateCompat.ACTION_STOP ) ) ) } // Display the notification and place the service in the foreground startForeground(id, builder.build())
Java
// Given a media session and its context (usually the component containing the session) // Create a NotificationCompat.Builder // Get the session's metadata MediaControllerCompat controller = mediaSession.getController(); MediaMetadataCompat mediaMetadata = controller.getMetadata(); MediaDescriptionCompat description = mediaMetadata.getDescription(); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, channelId); builder // Add the metadata for the currently playing track .setContentTitle(description.getTitle()) .setContentText(description.getSubtitle()) .setSubText(description.getDescription()) .setLargeIcon(description.getIconBitmap()) // Enable launching the player by clicking the notification .setContentIntent(controller.getSessionActivity()) // Stop the service when the notification is swiped away .setDeleteIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP)) // Make the transport controls visible on the lockscreen .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) // Add an app icon and set its accent color // Be careful about the color .setSmallIcon(R.drawable.notification_icon) .setColor(ContextCompat.getColor(context, R.color.primaryDark)) // Add a pause button .addAction(new NotificationCompat.Action( R.drawable.pause, getString(R.string.pause), MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_PLAY_PAUSE))) // Take advantage of MediaStyle features .setStyle(new MediaStyle() .setMediaSession(mediaSession.getSessionToken()) .setShowActionsInCompactView(0) // Add a cancel button .setShowCancelButton(true) .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(context, PlaybackStateCompat.ACTION_STOP))); // Display the notification and place the service in the foreground startForeground(id, builder.build());
Tenez compte du comportement de ces notifications lorsque vous utilisez Paramètres NotificationCompat:
- Lorsque vous utilisez
setContentIntent()
, votre service démarre automatiquement lorsque la notification sur laquelle l'utilisateur clique. - Dans un environnement
« digne de confiance » situation
comme l'écran de verrouillage, la visibilité par défaut du contenu des notifications est
VISIBILITY_PRIVATE
. Vous voudrez sans doute voir commandes de transport sur l'écran de verrouillage,VISIBILITY_PUBLIC
est donc la meilleure option. - Soyez prudent lorsque vous définissez la couleur d'arrière-plan. Dans une notification ordinaire Android 5.0 ou version ultérieure, la couleur n'est appliquée qu'à l'arrière-plan petite icône d'application. Toutefois, pour les notifications MediaStyle antérieures à Android 7.0, la couleur est utilisé pour tout l'arrière-plan des notifications. Testez la couleur d'arrière-plan. Accéder doux pour les yeux et évitez les couleurs extrêmement vives ou fluorescentes.
Ces paramètres ne sont disponibles que lorsque vous utilisez NotificationCompat.MediaStyle:
- Utiliser
setMediaSession()
pour associer la notification à votre session. Cela permet aux applications tierces et les appareils associés pour accéder à la session et la contrôler. - Utilisez
setShowActionsInCompactView()
pour ajouter jusqu'à trois actions à afficher dans l'élément contentView en taille standard de la notification. (Ici, le bouton de pause est spécifié.) - Sur Android 5.0 (niveau d'API 21) ou version ultérieure, vous pouvez faire glisser une notification pour l'arrêter
une fois que le service n'est plus exécuté au premier plan. Ce que vous ne pouvez pas faire
dans les versions précédentes. Pour autoriser les utilisateurs à supprimer la notification et à arrêter la lecture
antérieures à Android 5.0 (niveau d'API 21), vous pouvez ajouter un bouton d'annulation dans l'angle supérieur droit
notification en appelant
setShowCancelButton(true)
etsetCancelButtonIntent()
.
Lorsque vous ajoutez les boutons "Pause" et "Cancel", vous avez besoin d'un PendingIntent pour associer
à l'action de lecture. La méthode MediaButtonReceiver.buildMediaButtonPendingIntent()
se charge de convertir
une action PlaybackState en PendingIntent.