androidx / media

Jetpack Media3 support libraries for media use cases, including ExoPlayer, an extensible media player for Android
https://developer.android.com/media/media3
Apache License 2.0
1.63k stars 386 forks source link

Using PlaybackState to update the current playback state #789

Open okmand opened 11 months ago

okmand commented 11 months ago

Hello,

I have a project that uses androidx.media library, and I'm going to upgrade the project and use the androidx.media3 library. In the project, I use a separate service to get information about the tracks being played. So when music is playing, I receive some callback from my service. In this callback I have all the track information I need to display. Every time I get the callback, I update the music playing on the device with the following code:

mediaSession.setMetadata(
            MediaMetadataCompat.Builder()
                .putString(METADATA_KEY_TITLE, title)
                .putString(METADATA_KEY_DISPLAY_TITLE, title)
                .putLong(METADATA_KEY_DURATION, duration)
                .build()
        )

Documentation of media3 library says "The Media3 library automatically updates the media session using the player's state. As such, you don't need to manually handle the mapping from player to session". As I understand, in media3 library I can't update the tracks being played in my own way, I can only provide URI for the track and the library itself will decide how to play the music . But I don't have any URI or path to music and all the information which I need to display, I get using callbacks from my service. Please let me know, how I can provide playable information about tracks in my situation?

Thank you in advance

oceanjules commented 11 months ago

Hi @okmand,

Please take a look at https://github.com/androidx/media/issues/778, I think you might be having a similar question.

okmand commented 11 months ago

Hi @oceanjules, Unfortunately not, I have another issue. I will try to explain it in a different way. When I select a track on the device or do something on the device, I send information about user behavior to the service. For example, if a user clicks the "Go to the next track" button, I send information about this action to my service. After that, I get a callback with information about which track I should include next. I get information about the title, duration, track cover and so on. After that I provide this information to MediaSession with the following code:

mediaSession.setMetadata(
            MediaMetadataCompat.Builder()
                .putString(METADATA_KEY_TITLE, title)
                .putString(METADATA_KEY_DISPLAY_TITLE, title)
                .putLong(METADATA_KEY_DURATION, duration)
                .build()
        )

After that, when the track is played, every second I get a callback from my service with an update of information. I get the playback progress, the status of the music being played (is it played or paused, for example), and other useful information about the track being played. Every time I get this information, I update the playback state with the following code:

mediaSession.setPlaybackState(
            PlaybackStateCompat.Builder()
                .setState(/*PlaybackStateCompat = */ playbackState, /*Long = */ playbackProgressbarPos, 0.0f)
                .setActions(
                    PlaybackStateCompat.ACTION_PLAY or PlaybackStateCompat.ACTION_PAUSE or PlaybackStateCompat.ACTION_PLAY_PAUSE or
                            PlaybackStateCompat.ACTION_SKIP_TO_NEXT or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or
                            PlaybackStateCompat.ACTION_FAST_FORWARD or PlaybackStateCompat.ACTION_SET_RATING
                )
                .build()
        )

So, I don't have any URI for the track being played. I use MediaID to understand which track to send to my service, and the service itself has to figure out what kind of music to play to it. In the course of playing music by my service, I receive callbacks from it with information that I need to display on the screen and transmit it either through MediaSession.setPlaybackState or through MediaSession.setMetadata.

That's how I've been working since the first version of media library. My question is, how can I do the same with the third version of the library?

marcbaechinger commented 10 months ago

Sorry for my late response.

I understand your intention is to use a MediaSessionService or MediaLibraryService only to maintain the media session but not for hosting a player that actually does playback.

Option 1 Obviously, migrating to use the MediaSessionService only would be most convenient for you once you are there. I think you have your reasons to go with your own service. You have to provide what MediaSessionService does on your own in such a case (see the following options).

Option 2 Another thought was that you should just use a standalone MediaSession within your service instead of running two services. It sounds a bit cumbersome to have the session in another service and then delegate each command received by the session to your service that actually does playback.

If you are using ExoPlayer in your own service then you can just pass ExoPlayer when building the MediaSession and this should just work. You still have to make sure to do a notification if you want/need that. You can do your own, or look whether PlayerNotificationManager is helpful. If you are not using ExoPlayer you would need a Player implementation to build a service. You can base this implementation on SimpleBasePlayer. This would allow you to do what you describe: receive the playback status from your player, set it to SimpleBasePlayer which then triggers all the listener to inform the session about the state change. Playback commands sent to the SimpleBasePlayer then would need to be delegated to your own player.

Option 3 Is what you are currently do. You would run a MediaSessionService with a player that actually doesn't do playback but just holds the state of the other player. You would build the session with a SimpleBasePlayer on that you set the status that you receive from your playback service. Then if a controller sends a command to the session, you would have to receive this, delegate to your service that applies this and then sends back a state object to the MediaSessionService that is set to the fake player implemented with SimpleBasePlayer.

I think these are the options that I see.

Generally, I think in the long run it would be worthwhile for you to think about migrating to the MediaSessionService, that makes life easier for you as you can benefit from the features provided by it and it would greatly simplify the design of your app. If that's a goal, now would probably be a good moment to start your journey.

If for any reason you don't want to migrate, then I would go for option 2 I think. You still have to reimplement things that is in MediaSessionService like the notification. But from what I understand I think the design with two services is the most complicated approach. Specifically if your service should play when the app is in the background, you'd have to maintain two foreground services I think? Assuming you only support playback when your activity is in the foreground makes option 2. You wouldn't even need a notification and you wouldn't need to care about foreground services. So option 2 would probably become easier.