How to override notification button behavior with logic that resides in my ViewModel?

2 weeks ago 23
ARTICLE AD BOX

I'm new to Android and I'm trying to create an app that uses background playback. But it needs to have full control over what MediaItem is played when. What is played at a given time depends entirely on the state held by my ViewModel.

So currently, upon state change, my ViewModel uses the MediaController.setMediaItem to set what should be currently playing.

I added a listener to the media controller so that when the Playback state changes to STATE_ENDED, the ViewModel selects a new media item and plays it. This can also be forced with a "skip" method of my ViewModel that can be triggered by the UI.

What I want to do now is to make the seek_to_next_media_item command from the MediaSessionNotification trigger this same behavior.

And I can't seem to find how to do this.

I found how to make the button display in the notification by using a forwarding player and overriding getAvailableCommands().

But the forwarding player does not know about my ViewModel and from my understanding, it shouldn't because it resides in the service and a service shouldn't know about the ViewModel. And Player.Listener, which does know about my ViewModel, does not listen for seek_to_next_media_item.

I followed this guide https://github.com/androidx/media/issues/1708, by defining my own listener it seemed to get close to what I wanted.

I wanted to do this:

//In MediaSessionService override fun onCreate() { //Creating player as ExoPlayer val forwardingPlayer = object : ForwardingPlayer(player) { var myCustomListener: MyCustomListener? = null override fun getAvailableCommands(): Player.Commands { val commands = super.getAvailableCommands() return commands.buildUpon().add(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM).build() } override fun addListener(listener: Player.Listener) { if(listener is MyCustomListener) myCustomListener = listener super.addListener(listener) } override fun removeListener(listener: Player.Listener) { myCustomListener = null super.removeListener(listener) } override fun seekToNextMediaItem() { myCustomListener?.onSeekNextMediaItem() super.seekToNextMediaItem() } } // Creating MediaSession with forwardingPlayer } class MyCustomListener( val viewModel: MyViewModel ) : Player.Listener { fun onSeekNextMediaItem() { Log.d("Listener", "Music listener received on seek next") viewModel.skipMusic() } } //In the UI Layer mediaController.addListener(MyCustomListener(myViewModel))

But the listener that I pass is always received in addListener as a ForwardingPlayer.ForwardingListener, which is not extendable. Therefore, my special Listener is never added in the forwarding player and therefore never called for onSeekNextMediaItem.
I tried to cast it, just in case my custom listener was there as a subclass, but it failed.

My last idea is to create a bunch of custom commands following this documentation: https://developer.android.com/media/media3/session/control-playback#available-commands. And have the media controller send those commands to the service so that it holds a copy of the state. This way, it can decide what to play next on its own, but that seems convoluted and error prone regarding the sychronization between the ViewModel state and the service state.

How to override a player's function behavior with logic that resides in my ViewModel? Maybe I'm missing something huge that allows my MediaSessionService to talk back to the ViewModel but throughout my long research, I couldn't find one. Feels like I'm missing the right keyword.

Thank you very much for your help.

Read Entire Article