Skip to content

Most visited

Recently visited

navigation

Building a Media Browser Service

Your app must declare the MediaBrowserService with an intent-filter in its manifest. You can choose your own service name; in the following example, it is "MediaPlaybackService."

<service android:name=".MediaPlaybackService">
 <intent-filter>
  <action android:name="android.media.browse.MediaBrowserService" />
 </intent-filter>
</service>

Note: The recommended implementation of MediaBrowserService is MediaBrowserServiceCompat. which is defined in the media-compat support library. Throughout this page the term "MediaBrowserService" refers to an instance of of MediaBrowserServiceCompat.

Initialize the media session

When the service receives the onCreate() lifecycle callback method it should perform these steps:

The onCreate() code below demonstrates these steps:

public class MediaPlaybackService extends MediaBrowserServiceCompat {
  private MediaSessionCompat mMediaSession;
  private PlaybackStateCompat.Builder mStateBuilder;

  @Override
  public void onCreate() {
    super.onCreate();

    // Create a MediaSessionCompat
    mMediaSession = new MediaSessionCompat(context, LOG_TAG);

    // Enable callbacks from MediaButtons and TransportControls
    mMediaSession.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
    mStateBuilder = new PlaybackStateCompat.Builder()
                    .setActions(
                        PlaybackStateCompat.ACTION_PLAY |
                        PlaybackStateCompat.ACTION_PLAY_PAUSE);
    mMediaSession.setPlaybackState(mStateBuilder.build());

    // MySessionCallback() has methods that handle callbacks from a media controller
    mMediaSession.setCallback(new MySessionCallback());

    // Set the session's token so that client activities can communicate with it.
    setSessionToken(mMediaSession.getSessionToken());
  }
}

Manage client connections

A MediaBrowserService has two methods that handle client connections: onGetRoot() controls access to the service, and onLoadChildren() provides the ability for a client to build and display a menu of the MediaBrowserService's content hierarchy.

Controlling client connections with onGetRoot()

The onGetRoot() method returns the root node of the content hierarchy. If the method returns null, the connection is refused.

To allow all clients to connect to your service and browse its media content, onGetRoot() should return a non-null BrowserRoot with a root ID. To allow connections to your service without browsing, return a non-null BrowserRoot with a null root ID.

A typical implementation of onGetRoot() might look like this:

@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, so clients can use onLoadChildren() to retrieve the content hierarchy
      return new BrowserRoot(MY_MEDIA_ROOT_ID, null);
    }
   else {
      // Clients can connect, but since the BrowserRoot is an empty string
      // onLoadChildren will return nothing. This disables the ability to browse for content.
      return new BrowserRoot("", null);
    }
}

In some cases, you might want to implement a white/blacklist scheme to control connections. For an example of whitelisting, see the PackageValidator class in the Universal Android Music Player sample app.

Communicating content with onLoadChildren()

After the client connects, it can traverse the content hierarchy by making repeated calls to MediaBrowserCompat.subscribe() to build a local representation of the UI. The subscribe() method sends the callback onLoadChildren() to the service, which returns a list of MediaBrowser.MediaItem objects.

Each MediaItem has a unique ID string, which is an opaque token. When a client wants to open a submenu or play an item, it passes the ID. Your service is responsible for associating the ID with the appropriate menu node or content item.

A simple implementation of onLoadChildren() might look like this:

@Override
public void onLoadChildren(final String parentMediaId,
    final Result<List<MediaItem>> result) {

  //  Browsing not allowed
  if (TextUtils.isEmpty(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);
}

Note: MediaItem objects delivered by the MediaBrowserService should not contain icon bitmaps. Use a Uri instead by calling setIconUri() when you build the MediaDescription for each item.

For examples of how to implement onLoadChildren(), see the MediaBrowserService and Universal Android Music Player sample apps.

The media browser service lifecycle

The behavior of an Android service depends on whether it is started or bound to one or more clients. After a service is created, it can be started, bound, or both. In all of these states, it is fully functional and can perform the work it's designed to do. The difference is how long the service will exist. A bound service is not destroyed until all its bound clients unbind. A started service can be explicitly stopped and destroyed (assuming it is no longer bound to any clients).

When a MediaBrowser running in another activity connects to a MediaBrowserService, it binds the activity to the service, making the service bound (but not started). This default behavior is built into the MediaBrowserServiceCompat class.

A service that is only bound (and not started) is destroyed when all of its clients unbind. If your UI activity disconnects at this point, the service is destroyed. This isn't a problem if you haven't played any music yet. However, when playback starts, the user probably expects to continue listening even after switching apps. You don't want to destroy the player when you unbind the UI to work with another app.

For this reason, you need to be sure that the service is started when it begins to play by calling startService(). A started service must be explicitly stopped, whether or not it's bound. This ensures that your player continues to perform even if the controlling UI activity unbinds.

To stop a started service, call Context.stopService() or stopSelf(). The system stops and destroys the service as soon as possible. However, if one or more clients are still bound to the service, the call to stop the service is delayed until all its clients unbind.

The lifecycle of the MediaBrowserService is controlled by the way it is created, the number of clients that have are to it, and the calls it receives from media session callbacks. To summarize:

The following flowchart demonstrates how the lifecycle of a service is managed. The variable counter tracks the number of bound clients:

Service Lifecycle

Using MediaStyle notifications with a foreground service

When a service is playing, it should be running in the foreground. This lets the system know that the service is performing a useful function and should not be killed if the system is low on memory. A foreground service must display a notification so the user knows about it and can optionally control it. The onPlay() callback should put the service in the foreground. (Note that this is a special meaning of "foreground." While Android considers the service in the foreground for purposes of process management, to the user the player is playing in the background while some other app is visible in the "foreground" on the screen.)

When a service runs in the foreground, it must display a notification, ideally with one or more transport controls. The notification should also include useful information from the session's metadata.

Build and display the notification when the player starts playing. The best place to do this is inside the MediaSessionCompat.Callback.onPlay() method.

The example below uses the NotificationCompat.MediaStyle, which is designed for media apps. It shows how to build a notification that displays metadata and transport controls. The convenience method getController() allows you to create a media controller directly from your media session.

// 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);

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(this,
       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(this, R.color.primaryDark))

// Add a pause button
      .addAction(new NotificationCompat.Action(
          R.drawable.pause, getString(R.string.pause),
          MediaButtonReceiver.buildMediaButtonPendingIntent(this,
              PlaybackStateCompat.ACTION_PLAY_PAUSE)))

// Take advantage of MediaStyle features
    .setStyle(new NotificationCompat.MediaStyle()
      .setMediaSession(mediaSession.getSessionToken())
      .setShowActionsInCompactView(0)
// Add a cancel button
      .setShowCancelButton(true)
      .setCancelButtonIntent(MediaButtonReceiver.buildMediaButtonPendingIntent(this,
          PlaybackStateCompat.ACTION_STOP));

// Display the notification and place the service in the foreground
startForeground(id, builder.build());

When using MediaStyle notifications, be aware of the behavior of these NotificationCompat settings:

These settings are available only when you are using NotificationCompat.MediaStyle:

When you add the pause and cancel buttons, you'll need a PendingIntent to attach to the playback action. The method MediaButtonReceiver.buildMediaButtonPendingIntent() does the job of converting a PlaybackState action into a PendingIntent.

This site uses cookies to store your preferences for site-specific language and display options.

Get the latest Android developer news and tips that will help you find success on Google Play.

* Required Fields

Hooray!

Browse this site in ?

You requested a page in , but your language preference for this site is .

Would you like to change your language preference and browse this site in ? If you want to change your language preference later, use the language menu at the bottom of each page.

This class requires API level or higher

This doc is hidden because your selected API level for the documentation is . You can change the documentation API level with the selector above the left navigation.

For more information about specifying the API level your app requires, read Supporting Different Platform Versions.

Take a one-minute survey?
Help us improve Android tools and documentation.