MediaController with ExoPlayer: skipToNext() & skipToPrevious() not working - android

I am trying to hook up my Media Player's UI to to ExoPlayer that's running in a MediaBrowserCompatService so that when i touch play/pause, fast forward, rewind etc. will work.
From within my activity i call:
mediaController.transportControls.play() / .pause()
mediaController.transportControls.fastForward() / .rewind()
And this works perfectly.
Calling:
mediaController.transportControls.skipToNext() and
mediaController.transportControls.skipToPrevious()
though don't work at all.
In my MediaBrowserCompatService I have set the appropriate media playback state on the media session but
programmatically calling skip to previous and next won't work.
stateBuilder = PlaybackStateCompat.Builder()
.setActions(PlaybackStateCompat.ACTION_PLAY
or PlaybackStateCompat.ACTION_PLAY_PAUSE
or PlaybackStateCompat.ACTION_SKIP_TO_NEXT
or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS)
setPlaybackState(stateBuilder.build())
The exoplayer is prepared with ConcatenatingMediaSource() and skipping to the next and previous track from the Media Notification is working though.
Anyone any idea what is going wrong here?

It seems like you have to explicitly add a QueueNavigator MediaSessionConnector.
So something like this:
val mediaSessionConnector = MediaSessionConnector(mediaSession)
val queueNavigator = object : TimelineQueueNavigator(mediaSession){
override fun getMediaDescription(player: Player?, windowIndex: Int): MediaDescriptionCompat {
return mediaItems[windowIndex].description
}
}
mediaSessionConnector.setQueueNavigator(queueNavigator)
With the queueu navigator set it isn't necessary to set:
PlaybackStateCompat.ACTION_SKIP_TO_NEXT
or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
on the stateBuilder.

Related

Best way to update seekbar in activity from a MediaBrowserService?

So I have a MediaBrowserServiceCompat() to play some audios with MediaPlayer, and one activity with the UI interface of the current media playing.
On this interface I have a progress bar that I would like to update with the current position of the media player.
I was thinking about having this kind of method in the service, where every second I would send the current position of the MediaPlayer through metadata of the MediaSession :
private fun updateTimerMetadata() {
thread {
Thread.sleep(1000)
if (mp?.isPlaying == true) {
mediaSession.setMetadata(
MediaMetadataCompat.Builder()
.putLong("timerUpdate", mp?.currentPosition!!.toLong())
.build()
)
updateTimerMetadata()
}
}
}
I would receive it in the activity in the onMetadataChanged() and then update the UI.
But then I thought I could just start a timer in the activity and manage it when it's play/pause, and then no need to communicate with the service for that. But maybe it would be a risk for the bar to be unsync with the mediaplayer...
So I would like to know what's the best appraoch to handle this problem?
To communicate between the service and the activity, I use a MediaSession in the service :
mediaSession = MediaSessionCompat(baseContext, SERVICE_TAG).apply {
// Enable callbacks from MediaButtons and TransportControls
setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)
// Set an initial PlaybackState with ACTION_PLAY, so media buttons can start the player
stateBuilder = PlaybackStateCompat.Builder()
.setActions(
PlaybackStateCompat.ACTION_PLAY
or PlaybackStateCompat.ACTION_PLAY_PAUSE
)
setPlaybackState(stateBuilder.build())
setSessionToken(sessionToken)
}
And a MediaController in the activity to call mediaController.transportControls for the actions of the buttons.
I found an other way which I think may be better.
You can save the position of the media player in the playback state
val stateBuilder = PlaybackStateCompat.Builder()
stateBuilder.setState(
playPauseState,
mp?.currentPosition?.toLong() ?: PlaybackStateCompat.PLAYBACK_POSITION_UNKNOWN,
1F
)
mediaSession.setPlaybackState(stateBuilder.build())
And then in the activity, you can get it this way:
val timerPosition = mediaController.playbackState.position

Exoplayer - Plays same audio over and over on reopening the activity

I'm creating an exoplayer instance and adding a stream URL as progressive media source, preparing the player and playing the audio. When I go back to previous activity and open the player activity again, another instance of player is running the same audio (Two instances playing same audio simultaneously). Also I have a mute button. It works as expected when I open the activity for the first time. On reopening the activity, the mute button mutes only the ExoPlayer instance of the current activity
I tried Moving the ExoPlayer code to another class and calling them with public functions, but didn't work
val dataSourceFactory = DefaultDataSourceFactory(this, Util.getUserAgent(this, packageName))
val newMediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(Uri.parse("STREAM URL"));
val exoPlayer = ExoPlayerFactory.newSimpleInstance(this);
exoPlayer.prepare((newMediaSource));
exoPlayer.playWhenReady = true;
muteButton.setOnClickListener {
if(muteButton.tag == "muted")
{
exoPlayer.volume = 1f;
muteButton.tag = "unmuted";
}
else
{
exoPlayer.volume = 0f;
muteButton.tag = "muted";
}
}
I want the same ExoPlayer to run on reopening the activity and also mute that particular instance
I ended up using shared preference to save the service's state. So whenever the service is started, the boolean in shared preference is set to true. So I can start the service only when the boolean is false. Also I am using Broadcast receiver in both activity and service to exchange messages.

Detect pause/resume in ExoPlayer

I searched two days for this question in github but i can't find true answer . I want example for detecting pause / resume in ExoPlayer > 2.x .
Any one can give me an example ? I checked onPlayerStateChanged and problem not solved .
onPlayerStateChanged : STATE_BUFFERING
onPlayerStateChanged : STATE_READY
I just got this log from onPlayerStateChanged and this is not called in all times !
EDIT---
Please refer to the Player.isPlaying() method which provides this as an API.
"Rather than having to check these properties individually, Player.isPlaying can be called."
https://exoplayer.dev/listening-to-player-events.html#playback-state-changes
--- EDIT END
You need to check playWhenReady with a Player.EventListener. The Playback states of ExoPlayer are independent from the player being paused or not:
player.addListener(new Player.DefaultEventListener() {
#Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playWhenReady && playbackState == Player.STATE_READY) {
// media actually playing
} else if (playWhenReady) {
// might be idle (plays after prepare()),
// buffering (plays when data available)
// or ended (plays when seek away from end)
} else {
// player paused in any state
}
}
});
To play/pause the player ExoPlayer provides
player.setPlayWhenReady(boolean)
The sequence of playback states with ExoPlayer with a media file which never stalls to rebuffer is once in each of the four states and does not express play/paused:
Player.STATE_IDLE;
Player.STATE_BUFFERING;
Player.STATE_READY;
Player.STATE_ENDED;
Each time the player needs to buffer it goes:
Player.STATE_READY;
Player.STATE_BUFFERING;
Player.STATE_READY;
Setting playWhenReady does not affect the state.
All together your media is actually playing when
playWhenReady && playbackState == Player.STATE_READY
It plays when ready. :)
You can use this function:
public boolean isPlaying() {
return exoPlayer.getPlaybackState() == Player.STATE_READY && exoPlayer.getPlayWhenReady();
}
It must be that since the other answers were posted, a new method has been provided in Player.EventListener. [EDIT: Now it is Player.Listener, as Player.EventListener has been deprecated]. This works well for me:
override fun onIsPlayingChanged(isPlaying: Boolean) {
// your code here
}
If isPlaying is false, it is paused, otherwise playing.
I had the same requirement to detect the click event of exoplayer play/pause button. Above answers were mainly talking about the state not
about the button click event.
This is what I did to detect the Play/Pause button click, works perfect.
Step 1: Create custom control dispatcher class and override the method dispatchSetPlayWhenReady
class PlayerControlDispatcher : DefaultControlDispatcher() {
override fun dispatchSetPlayWhenReady(player: Player?, playWhenReady: Boolean): Boolean {
if(playWhenReady) {
// Play button clicked
} else {
// Paused button clicked
}
return super.dispatchSetPlayWhenReady(player, playWhenReady)
}
}
Step 2: Set the custom control dispatcher class PlayerControlDispatcher into the the player view.
playerView.setControlDispatcher(PlayerControlDispatcher())
Where playerView is an instance of com.google.android.exoplayer2.ui.PlayerView which we declare in our layout file.
Kotlin 2020 solution approach UPDATE
Events such as changes in state and playback errors are reported to registered Player.EventListener instances.
Player.EventListener has empty default methods, so you only need to implement the methods you’re interested in.
First your class, say your activity, has to conform to the Player.EventListener interface.
Then you override the onIsPlayingChanged method, on the class. Outside onCreate method...
Add the listener to your player instance:
// Adding player listener for tracking events
player?.addListener(this)
You can check if the player is playing (i.e. the position is advancing) with Player.isPlaying:
//Listening to player events
override fun onIsPlayingChanged(isPlaying: Boolean){
if (isPlaying) {
// Active playback.
} else {
// Not playing because playback is paused, ended, suppressed, or the player
// is buffering, stopped or failed. Check player.getPlaybackState,
// player.getPlayWhenReady, player.getPlaybackError and
// player.getPlaybackSuppressionReason for details.
}
}
That's it. Very simple.
You can use exoplayer.getPlayWhenReady() to check whether the player is currently in a pause state or playing state.
I think the callback:
fun onPlayWhenReadyChanged(playWhenReady: Boolean, reason: Int)
From the Player.Listener
With version 2.16.1, I use this code:
exoPlayer.addListener(object : Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) {
if (isPlaying){
videoIsPlaying()
}
}
})
Please try below code snipped. The player listener(onPlayerStateChanged) isn't good to observe play / pause action its called multiple time and also invoke while player configuring.
videoView.setControlDispatcher(object : DefaultControlDispatcher() {
override fun dispatchSetPlayWhenReady(player: Player, playWhenReady: Boolean): Boolean {
if (playWhenReady)
// Tap on Play button
else
// Tap on Pause button
return super.dispatchSetPlayWhenReady(player, playWhenReady)
}
})
for kotlin
private fun ExoPlayer.isPlaying() =
playbackState == Player.STATE_READY && playWhenReady

ExoPlayer , Loop Ended/ Started listener for LoopingMediaSource

I am using ExoPlayer for playing video in loop, but before source video is started each loop I need to reset some states of my Layout. I know ExoPlayer is calling onPlayerStateChanged with ExoPlayer.STATE_ENDED paramter when video is ended for usual MediaSources, but it is not called for LoopingMediaSource.
#Override
public void onPlayerStateChanged(boolean playWhenReady, int playbackState) {
if (playbackState == ExoPlayer.STATE_ENDED) {
showControls();
resetLayoutStates(); //I need it here, even in LoopingMediaSource
}
updateButtonVisibilities();
}
Does the Exoplayer have any Callback when Source is restarted or ended in loop ? Or does it have any workaround for my situation ?
You can detect that a video loops when the media item changes. This callback will be fired at the end of the media item and give you the new item to play even if that's the same when repeat mode is ONE. You can detect that with the reason parameter that tells you why the media changed (either naturally, seeking or looping):
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
super.onMediaItemTransition(mediaItem, reason)
if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT) {
// Reset your state
}
}
You may want to assert that it's the right media item with the given mediaItem argument. Especially since it will behave differently whether your repeat mode is ONE or ALL (the media item will be the same for ONE while the first or last item of your timeline for ALL).

Control the playback speed of video in android

I am using a VideoView to play a video file kept in res/raw. I couldnt find a way to control the playback speed of the video. Basically i want to reduce and increase the playback while moving a scroll bar. Is there any work around for implementing this?
you can use this but it works on api 23 and above
mVideo.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
#Override
public void onPrepared(MediaPlayer mp) {
//works only from api 23
PlaybackParams myPlayBackParams = null;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
myPlayBackParams = new PlaybackParams();
myPlayBackParams.setSpeed(0.8f); //you can set speed here
mp.setPlaybackParams(myPlayBackParams);
}
}
});
No, you cannot change the playback rate by simply using VideoView. VideoView and MediaPlayer only provide limited media functions.
You have to use some third party library, e.g., PVPlayer, and implement that yourself.
That's also why good media players on Android are so valuable:)
I want to say than Mk Kamal's solution have an unexpected side effect: calling setPlaybackParams in OnPreparedListener will force VideoView to repeat the latest played video when the app was returned from the background.
I don't know is it a bug or a feature, but I found a way to avoid such behavior:
private float speed = 0.8f;
private final MediaPlayer.OnInfoListener listener = (mp, what, extra) -> {
if (what == MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START) {
mp.setPlaybackParams(mp.getPlaybackParams().setSpeed(speed));
return true;
}
return false;
};
videoView.setOnPreparedListener(
mp -> {
mp.setOnInfoListener(listener);
}
);
MEDIA_INFO_VIDEO_RENDERING_START will be sent only if the palyer was already started.
And I want to emphasize that getPlaybackParams is annotated as #NonNull, so it's not necessary to create a new PlaybackParams object.
Kotlin variant, API above 23
val playerView = itemView.findViewById<VideoView>(R.id.videoview)
playerView.setVideoURI(Uri.parse("android.resource://" + context.packageName + "/" + R.raw.123.mp4))
playerView.setOnPreparedListener { mediaPlayer ->
playerView.seekTo(1) // for video preview
mediaPlayer.playbackParams = mediaPlayer.playbackParams.apply {
speed = 0.6f
}
playerView.start()
}
DicePlayer works perfectly on my Asus Transformer.
It has a speed control onscreen display.
I'm not sure what res/raw is though.

Categories

Resources