I want to connect my implementation of exoplayer with the media session object. I set up a SimpleExoPlayerView to show a video. Every time a button is clicked, I want the media session callbacks to fire. I can only get the callbacks to fire when something like a pair of headphones is used. The code used in the app is written below
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void setUp(LifecycleOwner lifecycleOwner){
// Create a MediaSessionCompat
Log.i("Hoe8", "lco setup called");
mMediaSession = new MediaSessionCompat(activity, "this");
// Enable callbacks from MediaButtons and TransportControls
mMediaSession.setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
// Do not let MediaButtons restart the player when the app is not visible
mMediaSession.setMediaButtonReceiver(null);
// 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 MediaSessionCompat.Callback() {
@Override
public void onPlay() {
super.onPlay();
Log.i("Hoe8", "MediaSession callback play called");
mMediaSession.setActive(true);
((JokesAdapter.VideoPostViewHolder) rv).setIsPlaying(true);
((JokesAdapter.VideoPostViewHolder) rv).setHasStarted(true);
}
@Override
public void onPause() {
super.onPause();
((JokesAdapter.VideoPostViewHolder) rv).setIsPlaying(false);
}
@Override
public void onStop() {
super.onStop();
mMediaSession.setActive(false);
((JokesAdapter.VideoPostViewHolder) rv).setIsPlaying(false);
((JokesAdapter.VideoPostViewHolder) rv).setHasStarted(false);
}
});
// Create a MediaControllerCompat
MediaControllerCompat mediaController =
new MediaControllerCompat(activity, mMediaSession);
MediaControllerCompat.setMediaController(activity, mediaController);
//Handler mainHandler = new Handler();
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector =
new DefaultTrackSelector(videoTrackSelectionFactory);
// 2. Create the player
player = ExoPlayerFactory.newSimpleInstance(activity, trackSelector);
playerView.setPlayer(player);
MediaSessionConnector mediaSessionConnector =
new MediaSessionConnector(mMediaSession);
mediaSessionConnector.setPlayer(player, null,null );
}
Made some changes to the code
public class VideoLifeCyclerObserver implements LifecycleObserver {
MediaSessionCompat mMediaSession;
PlaybackStateCompat.Builder mStateBuilder;
AppCompatActivity activity;
SimpleExoPlayerView playerView;
SimpleExoPlayer player;
ExoPlayer.ExoPlayerComponent rv;
MediaSessionConnector mediaSessionConnector;
public VideoLifeCyclerObserver(AppCompatActivity activity, SimpleExoPlayerView playerView, ExoPlayer.ExoPlayerComponent rv){
this.activity = activity;
this.playerView = playerView;
this.activity.getLifecycle().addObserver(this);
this.rv = rv;
Log.i("Hoe8","video lco created");
}
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void setUp(LifecycleOwner lifecycleOwner){
// Create a MediaSessionCompat
Log.i("Hoe8", "lco setup called");
mMediaSession = new MediaSessionCompat(activity, "this");
// Create a MediaControllerCompat
MediaControllerCompat mediaController =
new MediaControllerCompat(activity, mMediaSession);
MediaControllerCompat.setMediaController(activity, mediaController);
mediaSessionConnector =
new MediaSessionConnector(mMediaSession, new PlayBackController());
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void startPlayer(LifecycleOwner lifecycleOwner){
BandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
TrackSelection.Factory videoTrackSelectionFactory =
new AdaptiveTrackSelection.Factory(bandwidthMeter);
TrackSelector trackSelector =
new DefaultTrackSelector(videoTrackSelectionFactory);
player = ExoPlayerFactory.newSimpleInstance(activity, trackSelector);
playerView.setPlayer(player);
mediaSessionConnector.setPlayer(player, null,null );
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void tearDown(LifecycleOwner lifecycleOwner){
player.stop();
player.release();
player.sendMessages(new ExoPlayer.ExoPlayerMessage(rv,1,player.getContentPosition()));
}
public class PlayBackController extends DefaultPlaybackController{
@Override
public void onPause(Player player) {
Log.i("Hoe8", "onPause called");
((JokesAdapter.VideoPostViewHolder) rv).setIsPlaying(false);
super.onPause(player);
}
@Override
public void onPlay(Player player) {
Log.i("Hoe8", "MediaSession callback play called 2");
mMediaSession.setActive(true);
((JokesAdapter.VideoPostViewHolder) rv).setIsPlaying(true);
((JokesAdapter.VideoPostViewHolder) rv).setHasStarted(true);
super.onPlay(player);
}
@Override
public void onStop(Player player) {
Log.i("Hoe8", "onStop called");
mMediaSession.setActive(false);
((JokesAdapter.VideoPostViewHolder) rv).setIsPlaying(false);
((JokesAdapter.VideoPostViewHolder) rv).setHasStarted(false);
super.onStop(player);
}
}
}
How can I get the buttons that show in the SimpleExoPlayerView to fire the media session callbacks?
class MediaSessionCompat. Allows interaction with media controllers, volume keys, media buttons, and transport controls. A MediaSession should be created when an app wants to publish media playback information or handle media keys.
Media sessions provide a universal way of interacting with an audio or video player. By informing Android that media is playing in an app, playback controls can be delegated to the app.
In short:
delete all code in your onCreate starting from (inclusive)
// Enable callbacks from MediaButtons and TransportControls
to (exclusive)
// Create a MediaControllerCompat
:)
More lengthy:
I recommend firing media session callbacks by listening to state transitions of the player instead of by click on buttons. This saves you from doing this for each UI element interacting with the player. That's actually what the MediaSessionConnector does for you.
With the MediaSessionConnector you do not need to manipulate the MediaSession yourself. The connector mediates between the player instance and the media session. This means the connector listens to state transitions of the player and maps the player state to the media session state. The connector also listens for media actions sent by transport controls and delegates them to the player or your app. Note: Your app does not need to provide a MediaSessionCompat.Callback, the connector registers its own (and overrides yours as there can be only one per session).
In general: your app does only interact with the SimpleExoPlayer instance while the connector maps the player state to the session.
Let's start with the basic approach which maps the state of the player to the session which triggers appropriate MediaControllerCompat.Callback methods:
// code running in a activity or service where (this instanceof Context)
mediaSession = new MediaSessionCompat(this, getPackageName());
mediaSessionConnector = new MediaSessionConnector(mediaSession)
mediaSessionConnector.setPlayer(player, null, null);
mediaSession.setActive(true);
You can now prepare and use the player like before, like call setPlayWhenReady(true|false), seekTo(t) and the connector maintains the PlaybackStateCompat which is broadcast to controllers of the session.
The connector does receive and implement a couple of media actions at this level (no need for your own MediaSession.Callback):
PlaybackStateCompat.ACTION_PLAY_PAUSE |
PlaybackStateCompat.ACTION_PLAY |
PlaybackStateCompat.ACTION_PAUSE |
PlaybackStateCompat.ACTION_STOP |
PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE |
PlaybackStateCompat.ACTION_SET_REPEAT_MODE;
PlayFromXYZ actions
You may want to support additional media actions like ACTION_PLAY_FROM_MEDIA_ID. You can do so by providing your PlaybackPreparer:
playbackPreparer = new YourPlaybackPreparer();
mediaSessionConnector.setPlayer(player, playbackPreparer, null);
The connector now delegates actions like ACTION_PLAY_FROM_MEDIA_ID or ACTION_PREPARE_FROM_MEDIA_ID to your playback preparer which creates a MediaSource for the given media ID to prepare the player.
Metadata and Queue management
Also interesting is the ability to map the Timeline of the player directly to queue and metadata of the media session. To do this you can provide a QueueNavigator. There is an abstract TimelineQueueNavigator provided by the extension:
QueueNavigator queueNavigator = new TimelineQueueNavigator(mediaSession) {
@Override
public MediaDescriptionCompat getMediaDescription(int windowIndex) {
// implement this method and read from your backing data:
getMediaDescriptionAtQueuePosition(windowIndex):
return mediaDescription;
}
}
mediaSessionConnector.setQueueNavigator(queueNavigator);
With this media controllers can now read metadata and queue of the session. The queue represents the current Timeline of the player and the metadata of the session describes the window in the Timeline which is currently playing. (more about playlists).
Provided a TimelineQueueNavigator, the connector listens for ACTION_SKIP_TO_NEXT, ACTION_SKIP_TO_PREVIOUS and ACTION_SKIP_TO_QUEUE_ITEM sent by transport controls and navigates along the timeline accordingly.
Lifecycle integration
Please note that you must create the player instance onStart/onResume and release it onPause/onStop. This makes sure codec resources you share with other apps are freed when you are in background. Your code sample does it only once onCreate which is not good citizenship :). See how the ExoPlayer demo app does it.
Please also consider the Medium blog about the MediaSessionConnector.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With