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.56k stars 373 forks source link

Attach listeners to media controller after building #1598

Open sampengilly opened 1 month ago

sampengilly commented 1 month ago

Use case description

When working with the Player interface, it's simple and effective to attach listeners to be informed of changes to player state. This makes it very easy to update UI in response to player state changes.

One thing that doesn't run through the Player interface is Custom Commands, those have to be sent through the MediaController. There are times where an app may set up custom commands to manipulate some state on the player (such as turning on an autoplay function, or an auto pause timer). If this functionality is residing within the Session service, it would be nice to have the service act as the source of truth for the current state of those functions (i.e. on/off). You can set up a pair of custom commands, one to "get" and one to "set" the state. But that is only an "on-demand" fetch option for getting the current state.

There is a way to set a listener on the MediaController through which the session can send commands to the controller. But this is something which can only be set once and only on the MediaController.Builder.

One example of a challenge caused by this would be having a button on a Player screen, which opens a bottom sheet for selecting an "auto-pause" timeout (e.g. automatically pause after 30 minutes). Upon selecting this option, a custom command is sent to the session to activate the functionality, the bottom sheet is closed, but there is no way for the original button on the Player UI to update to reflect the new state without re-polling via a "getter" custom command, either on an interval or by registering a lifecycle callback to know when the bottom sheet is dismissed.

Proposed solution

A way to set one or more listeners on a MediaController after it has been built, through which custom commands/messages can be sent from the session so that the UI can respond to updates of custom session state.

AND/OR

A shifting of custom commands support into the general purpose Player interface.

Alternatives considered

Current workaround involves setting up a listener when building the media controller, which receives commands from the session and "pushes" those updates to something (class/flow/etc) that sits alongside the media controller instance. UI elements would need to work with the MediaController for most things but would also need to be provided with this additional accessor object to receive updates from the session regarding custom commands

marcbaechinger commented 1 month ago

Thanks for your request.

I think having multiple listeners isn't really an option because some of the callbacks like the mentioned onCustomCommand have a SessionResult that is sent back to the session and resolves the Future that the sender holds. Multiple listeners would add ambiguity regarding which SessionResult to use to resolve the future the session is holding. Similarly, this is a problem regarding adding custom commands to the Player.Listener of which multiple instances can be added to a Player.

Allowing an app to set the listener after the controller being returned from after the buildAsync-Future succeeded looks feasible regarding the point above or threading concerns. The problem here is probably that MediaBrowser extends from MediaController and MediaBrowser.Listener extends from MediaController.Listener. The approach with the builder allows to inject the MediaBrowser.Listener through the constructor of each of the classes without creating strange API quirks bu adding MediaController.setListener(MediaController.Listener) that is inherited by MediaBrowser that would need a MediaBrowser.Listener. That could be dealt with but with a confusing API at best I think.

Like you say, as a solution with the current version an app can initially set the listener and decide whether to dispatch or not according to the availability of consumers (like when setListener(listener) would have been called with a non-null instance or not). If I understand correctly and given multiple listeners aren't an option, I think the ability to set the listener after creation doesn't remove the fact that a UI component would also need to be provided with this additional accessor object.

I leave this enhancement open but given the API difficulties, the availability of an app-side alternative and other priorities of the team, I can't really say whether and when we get around looking into this I'm afraid.