I want to use MediaSession with exoplayer and by searching I found that Exoplayer already have MediaSession extension library(https://github.com/google/ExoPlayer/tree/release-v2/extensions/mediasession), but I am unable to find any good documentation on how to implement that.
I have already read the documentation provided by Google developer on this but it is not clear and hard to understand for me, the documentation link is https://medium.com/google-exoplayer/the-mediasession-extension-for-exoplayer-82b9619deb2d
Can anyone please help me how can I implement MediaSession extension with Exoplayer.
Edited:
Finally I was able to implement this by trying hard using the above link (https://medium.com/google-exoplayer/the-mediasession-extension-for-exoplayer-82b9619deb2d).
Details are given in the answer section below.
To map the playback state of the player to the media session you can do as follows (assuming video playback in an activity):
// onCreate()
mediaSession = new MediaSessionCompat(this, getPackageName());
mediaSessionConnector = new MediaSessionConnector(mediaSession)
// onStart() or onResume() according to API level
initializePlayer();
mediaSessionConnector.setPlayer(player, null, null);
mediaSession.setActive(true);
// onPause() or onStop() according to API level
mediaSessionConnector.setPlayer(null, null, null);
mediaSession.setActive(false);
releasePlayer();
With this media actions like ACTION_PLAY, ACTION_PAUSE and the like are already supported.
You can find some more context in another SOF post.
Here is how I did this.
First initialize the MediaSessionCompat, MediaSessionConnector and MediaControllerCompat as given below.
private void initMediaSession(){
ComponentName mediaButtonReceiver = new ComponentName(getApplicationContext(), MediaButtonReceiver.class);
mMediaSessionCompat = new MediaSessionCompat(getApplicationContext(), "MyMediasession", mediaButtonReceiver, null);
MediaSessionConnector mediaSessionConnector = new MediaSessionConnector(mMediaSessionCompat, mPlaybackController, false);
mediaSessionConnector.setPlayer(mMediaPlayerManager.getPlayer(), null);
mMediaControllerCompat = mMediaSessionCompat.getController();
}
All the callbacks are received in this MediaSessionConnector.PlaybackController.
private MediaSessionConnector.PlaybackController mPlaybackController = new MediaSessionConnector.PlaybackController() {
#Override
public long getSupportedPlaybackActions(#Nullable Player player) {
long ACTIONS = PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PLAY
| PlaybackStateCompat.ACTION_PAUSE | PlaybackStateCompat.ACTION_STOP;
return ACTIONS;
}
#Override
public void onPlay(Player player) {
}
#Override
public void onPause(Player player) {
}
#Override
public void onSeekTo(Player player, long position) {
}
#Override
public void onFastForward(Player player) {
}
#Override
public void onRewind(Player player) {
}
#Override
public void onStop(Player player) {
}
};
Now you can use MediaControllerCompat.TransportControls to send events like play, pause etc. on click of Play/Pause buttons.
mMediaControllerCompat.getTransportControls().play();//For play
mMediaControllerCompat.getTransportControls().pause();//For pause
While using the TransportControls methods corresponding methods of MediaSessionConnector.PlaybackController will also be called simultaneously.
Related
I want to listern to button press on Bluetooth headset and apparently MediaSession is the only way. I created media session using MediaSessionCompat with only one flag MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS and it worked on my Android 9 device. I was able to register a MediaSessionCompat.Callback and onPlay/onPause are called upon pressing Bluetooth headset button. However the same code does not work on another devices running Android 11 and 13.
Media session is created and initialized in activity onCreate
mediaSessionCompat = new MediaSessionCompat(getApplicationContext(), MY_SESSION_TAG);
MediaSessionCompat.Callback mMediaSessionCallback = new MediaSessionCompat.Callback() {
#Override
public void onPlay() {
Log.d("MediaButton", "play");
setMediaPlaybackState(PlaybackStateCompat.STATE_PLAYING);
super.onPlay();
}
#Override
public void onPause() {
Log.d("MediaButton", "pause");
setMediaPlaybackState(PlaybackStateCompat.STATE_PAUSED);
super.onPause();
}
#Override
public void onPlayFromMediaId(String mediaId, Bundle extras) {
super.onPlayFromMediaId(mediaId, extras);
}
};
mediaSessionCompat.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS);
setMediaPlaybackState(PlaybackStateCompat.STATE_PLAYING);
mediaSessionCompat.setCallback(mMediaSessionCallback);
mediaSessionCompat.setActive(true);
Here is setMediaPlaybackState method
private void setMediaPlaybackState(int state) {
if (state == PlaybackStateCompat.STATE_PLAYING) {
playbackStateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PAUSE);
} else {
playbackStateBuilder.setActions(PlaybackStateCompat.ACTION_PLAY_PAUSE | PlaybackStateCompat.ACTION_PLAY);
}
playbackStateBuilder.setState(state, PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN, 1);
mediaSessionCompat.setPlaybackState(playbackStateBuilder.build());
}
Any idea how to get this to work on Android 11+? I don't need to actually play media, just listen to button events.
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?
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.
I have 10 video i need to play, once one is done, the next one starts to play.
I'm using Google's ExoPlayer, I use the example in the DEMO # GitHub.
I can play 1 video but if i try to play the next one, it wont start.
If i try to reInit the player, and the start playing again, it crashes.
private void loadvideo() {
Uri uri = Uri.parse(VIDEO_LIBRARY_URL + currentVideo + ".mp4");
sampleSource = new FrameworkSampleSource(this, uri, null, 2);
// 1. Instantiate the player.
// 2. Construct renderers.
videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING);
audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
// 3. Inject the renderers through prepare.
player.prepare(videoRenderer, audioRenderer);
// 4. Pass the surface to the video renderer.
surface = surfaceView.getHolder().getSurface();
player.sendMessage(videoRenderer, MediaCodecVideoTrackRenderer.MSG_SET_SURFACE, surface);
// 5. Start playback.
player.setPlayWhenReady(true);
player.addListener(new ExoPlayer.Listener() {
#Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
Log.d(TAG, "onPlayerStateChanged + " + playbackState);
if (playbackState == ExoPlayer.STATE_ENDED) {
currentVideo++;
loadNextVideo();
}
}
#Override
public void onPlayWhenReadyCommitted() {
}
#Override
public void onPlayerError(ExoPlaybackException error) {
}
});
}
What am i doing wrong?
How can i play videos continuity?
Thanks.
You can reuse the ExoPlayer up until the point that you call release(), and then it should no longer be used.
To change the media that it is currently playing, you essentially need to perform the following steps:
// ...enable autoplay...
player.stop();
player.seekTo(0L);
player.prepare(renderers);
Creating the renderers is a little bit more involved, but that's the flow you should follow and the player should be able to play back to back videos.
I'm using Exoplayer change mp4 video success. I use the example in the DEMO.
1.DEMO project in DemoPlayer.java:
private final RendererBuilder rendererBuilder;
//remove final,then modify that:
private RendererBuilder rendererBuilder;
//and add the set method:
public void setRendererBuilder(RendererBuilder rendererBuilder){
this.rendererBuilder = rendererBuilder;
}
//finally,add stop method
public void stop(){
player.stop();
}
2.DEMO project in PlayerActivity.java:
add method:
private void changeVideo(){
player.stop();
player.seekTo(0L);
//you must change your contentUri before invoke getRendererBuilder();
player.setRendererBuilder(getRendererBuilder());
player.prepare();
playerNeedsPrepare = false;
}
remember change param contentUri before invoke changeVideo method.
Use ConcatenatingMediaSource to play files in sequence.
For example, for playing 2 media Uris (firstVideoUri and secondVideoUri), use this code:
MediaSource firstSource =
new ExtractorMediaSource.Factory(...).createMediaSource(firstVideoUri);
MediaSource secondSource =
new ExtractorMediaSource.Factory(...).createMediaSource(secondVideoUri);
ConcatenatingMediaSource concatenatedSource =
new ConcatenatingMediaSource(firstSourceTwice, secondSource);
And then use concatenatedSource to play media files sequentially.
OK, Answering my own question.
on the example, google init the ExoPlayer at OnResume().
i had to re-init for every video like that:
player = ExoPlayer.Factory.newInstance(2, 1000, 5000);
if someone has a better idea, please let me know.
There is another solution, you could refer to ConcatenatingMediaSource to achieve auto play next media.
In Demo App example :
1. Launch ExoPlayer
2. Select Playlists
3. Choose Cats->Dogs
I'm using a MediaController and MediaPlayer together, to create a simple audio player in Android, taken from an example I found here on this site.
However, after much search I could not find the solution to my problem: the progress/seek bar doesn't refresh while the music is playing. It just updates itself when something on the MediaController is pressed (Play/Pause, forward, etc).
Any easy fix for this that I'm not getting ?
NOTE: My MediaController is declared in XML, and my MediaController.MediaPlayerControl methods just make use of the MediaPlayer class.
Mediaplayer provides a method getCurrentPosition() you can put this in a thread to continously update the progressbar.
public int getCurrentPositionInt(){
if (player != null)
return player.getCurrentPosition();
else
return 0;
}
Create a Thread or CountDownTimer to continuously update the seekbar :
seekBar.setMax((getCurrentPositionInt() / 1000));
OR
MediaController.MediaPlayerControl mp;
mp.seekTo((getCurrentPositionInt() / 1000))
Im Sorry for my English!
you are showing the controller before the music player is ready.
You need to notify your activity from the controller when it is ready.
public void onPrepared(MediaPlayer mp) {
mp.start();
Intent onPreparedIntent = new Intent("MP_READY");
LocalBroadcastManager.getInstance(activity).sendBroadcast(onPreparedIntent);
}
Then you need to create a BroadcastReceiver in your activity and override his onReceive method to show the controller.
private BroadcastReceiver mpReadyReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context c, Intent i) {
controller.show(0);
}
};
You also need to register the receiver in your activity`s onResume().
protected void onResume() {
super.onResume();
LocalBroadcastManager.getInstance(this).registerReceiver(mpReadyReceiver,
new IntentFilter("MP_READY"));
}
Now try to call controller.show only when it is necesary.
Be careful not creating more than one controller instance
i am using VideoView and seek bar but when i seekTo(..) on desired position through seekBar it play video from starting.
i trying this code:
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
mVideoView.seekTo(progress);
}
As Reno mentioned, you have to wait for the seeking to complete.
VideoView does not have a OnSeekCompleteListener() but you can access the MediaPlayer from the onPrepared method of the VideoView and then set the OnSeekCompleteListener, like this :
mVideoView.setOnPreparedListener(new OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
mp.setOnSeekCompleteListener(new OnSeekCompleteListener() {
#Override
public void onSeekComplete(MediaPlayer mp) {
//TODO: Your code here
}
});
}
});
The call to VideoView.seekTo() is a wrapper around MediaPlayer.seekTo(). This function returns almost immediately even though the actual seeking is still being performed. Therefore you want to wait for seeking to complete via MediaPlayer.OnSeekCompleteListener.
However, as Reno mentioned, the standard VideoView does not support OnSeekCompleteListener.
But you can copy and locally customize the VideoView class to add this support yourself.
First, start with a copy of VideoView.java. Or you can clone the entire frameworks/base repo but warning it is over 1 gig of data to download.
Copy VideoView.java into your Eclipse Android project and it will start building but fail. Here's what I did to get it to compile:
Update the package statement to match your project.
Comment out the references to MetaData. The fix for this is on my todo list. These need to be replaced with calls to MediaMetadataRetriever.
Replace mContext with calls to getBaseContext()
Now you are ready to add the code for OnSeekCompleteListener. The implementation is similar to the other listeners, i.e OnCompletionListener.
public class VideoView extends SurfaceView
implements MediaPlayerControl {
// The client's listener which is the notification callback.
private OnSeekCompleteListener mOnSeekCompleteListener;
// Set up MediaPlayer to forward notifications to client.
private MediaPlayer.OnSeekCompleteListener mSeekCompleteListener =
new MediaPlayer.OnSeekCompleteListener() {
public void onSeekComplete(MediaPlayer mp) {
if (mOnCompletionListener != null) {
mOnCompletionListener.onCompletion(mMediaPlayer);
}
}
};
// API for client to set their listener.
public void setOnSeekCompleteListener(OnSeekCompleteListener l)
{
mOnSeekCompleteListener = l;
}
}
Finally, update your own code:
Update references to android.widget.VideoView to use your customized VideoView.
Implement a listener and set it via by calling setOnSeekCompleteListener().
Your code now receives notifications when the seek has really completed and it can then perform subsequent seeks.
You have to wait for the seeking to complete, unfortunately VideoView does not have a OnSeekCompleteListener() (why Google? -_-)
use my method works like charm
Take mediaPlayer
MediaPlayer mediaPlayer;
init mediaPlayer
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
mediaPlayer = mp;
}
});
use seekTo method like this
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mediaPlayer.seekTo((int) mStartPosition, MediaPlayer.SEEK_CLOSEST);
} else {
mediaPlayer.seekTo((int) mStartPosition);
}
You should use
mVideoView.seekTo(progress, SEEK_CLOSEST)