The MediaSession extension for ExoPlayer

Marc Bächinger
AndroidX Media3
Published in
5 min readAug 22, 2017

The Android MediaSession API provides an abstract model with which clients can interact to monitor and control media playback via MediaController and its TransportControls. Clients like Google Assistant, Android Auto, Android TV and Android Wear use the MediaSession API to control media apps. In addition, UI components can use the same APIs rather than depend on a specific player implementation.

Roughly, a media app publishes supported playback commands, playback state and a playback queue to the media session, which in turn broadcasts these to media controllers. The MediaSessionConnector of the new media session extension for ExoPlayer makes integrating with the MediaSession API much easier and does all the heavy lifting for you.

MediaSession controls and meta data in Android Auto.

Integrating with the MediaSession API made easy

The MediaSession extension mediates between an ExoPlayer instance and a MediaSession. It automatically maps the player state to the playback state of the media session, and handles actions sent by controllers.

Publishing the player state
To publish the state of the player to a media session, we just need to pass the session to the constructor of the MediaSessionConnector and set a player instance:

MediaSessionConnector mediaSessionConnector =
new MediaSessionConnector(mediaSession);
mediaSessionConnector.setPlayer(player);

In addition to publishing the playback state, this MediaSessionConnector automatically handles a basic set of playback actions (ACTION_PLAY_PAUSE, ACTION_PLAY, ACTION_PAUSE, ACTION_SEEK_TO, ACTION_FAST_FORWARD, ACTION_REWIND, ACTION_STOP) by calling corresponding methods on the player.

Receiving media actions
The MediaSession API defines a set of media actions that a media controller can send. The MediaSessionConnector receives these actions and delegates them to components registered by the app. The app can register implementations of the following interfaces with the corresponding setter, depending on which actions it wishes to handle:

For some of these interfaces, the library offers customisable default implementations to be used out of the box, like the TimelineQueueNavigator.

Preparing the player

The MediaSession API declares media actions to initiate playback that require creating a media source and preparing the ExoPlayer for that source. Android Auto, for example, uses these actions to initiate playback when a user chooses an item in the catalogue:

Android Auto browser to initiate playback.

A PlaybackPreparer implementation can define which actions it supports, and handle these actions when they’re received by the connector:

private class MyPlaybackPreparer implements
MediaSessionConnector.PlaybackPreparer {
@Override
public void onPrepare() {
// Prepare.
[...]
}

@Override
public void onPrepareFromMediaId(final String mediaId,
Bundle extras) {
// Prepare player from media id.
[...]
}

[...]

@Override
public long getSupportedPrepareActions() {
return SUPPORTED_PREPARE_ACTIONS;
}

}

A PlaybackPreparer can be registered when calling setPlayer on the connector:

 mediaSessionConnector.setPlayer(player,
new MyPlaybackPreparer());

Using TimelineQueueNavigator

TimelineQueueNavigator is an implementation of the QueueNavigator interface that maps the Timeline of an ExoPlayer to the media session queue. It handles the actions ACTION_SKIP_PREVIOUS, ACTION_SKIP_NEXT and ACTION_SKIP_TO_QUEUE_ITEM to navigate in the timeline. TimelineQueueNavigator is abstract and requires developers to implement the getMediaDescription(int windowIndex) method to provide the MediaDescriptionCompat for a given window index. This enables the connector and the queue navigator to maintain playback state, metadata and queue of the media session.

TimelineQueueNavigator queueNavigator =
new TimelineQueueNavigator(mediaSession) {
@Override
public MediaDescriptionCompat getMediaDescription(
int windowIndex) {
return getMediaDescriptionAtQueuePosition(windowIndex);
}

};
mediaSessionConnector.setQueueNavigator(queueNavigator);

The app interacts with the player like before, for instance to prepare the player for a list of media items:

// Create media sources.
MediaSource[] mediaSources = new MediaSource[queueItems.size()];
for (int i = 0; i < queueItems.size(); i++) {
mediaSources[i] = createMediaSource(queueItems.get(i));
}
// Prepare the player.
player.prepare(new ConcatenatingMediaSource(mediaSources));

TimelineQueueNavigator listens for changes to the player’s state or timeline, and automatically updates the media session’s state, metadata and queue accordingly. In the other direction, the navigator seeks to the requested timeline window when a media controller sends an ACTION_SKIP_* action.

Advertising and handling custom actions

Apps often want to provide additional custom actions to be displayed in a player UI like Android Auto or Android Wear. The MediaSession extension comes with a RepeatModeActionProvider, which provides such a custom action for cycling through the repeat modes of the player.

Custom repeat mode action in Android Auto.

Custom actions can be defined by implementing the MediaSessionConnector.CustomActionProvider interface. The getCustomAction method defines whether and which action should be advertised to media controllers. When a media controller sends a custom action, the connector delegates to the onCustomAction method. The RepeatModeActionProvider illustrates this approach:

public final class RepeatModeActionProvider implements
MediaSessionConnector.CustomActionProvider {
[...] @Override
public void onCustomAction(String action, Bundle extras) {
// implement action
// [...]
}

@Override
public PlaybackStateCompat.CustomAction getCustomAction() {
CharSequence actionLabel;
int iconResourceId;
// evaluate what label and icon is sensible at the current state
// [...]
PlaybackStateCompat.CustomAction.Builder repeatBuilder =
new PlaybackStateCompat.CustomAction.Builder(
ACTION_REPEAT_MODE, actionLabel, iconResourceId);
return repeatBuilder.build();
}

}

Custom action providers can be registered when calling setPlayer on the connector:

mediaSessionConnector.setPlayer(player, playbackPreparer,
CustomActionProvider... customActionProviders);

The order in which the providers are passed determines the order in which the actions are included in the playback state of the media session.

User readable error messages

When streaming media over a network, things can go wrong. The least we can do is provide error messages to appropriately inform our media controller clients. By using a MediaSessionConnector.ErrorMessageProvider you can make sure these messages are human readable:

messageProvider = new MediaSessionConnector.ErrorMessageProvider() {
@Override
public Pair<Integer, String> getErrorMessage(
ExoPlaybackException playbackException) {
return getHumanReadableError(playbackException);
}
}
mediaSessionConnector.setErrorMessageProvider(messageProvider);

With the MediaSessionConnector you can easily integrate your media app with the MediaSession API. The connector does all the heavy lifting for you, whilst still giving you full control over the behaviour of all of the media actions defined by the API.

We are looking forward to seeing the connector in action in your app, and are eager to hear your feedback. File feedback and feature request on our Github Issue tracker. We’re keen to learn more about your requirements, and how we could make integrating with MediaSession even easier!

--

--