Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Connecting exoplayer with MediaSessionCompat

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?

like image 348
Joel Robinson-Johnson Avatar asked Jan 01 '18 02:01

Joel Robinson-Johnson


People also ask

What is MediaSessionCompat?

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.

What is media session API?

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.


1 Answers

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.

like image 129
marcbaechinger Avatar answered Sep 30 '22 10:09

marcbaechinger