media
media copied to clipboard
Handling long-running required loading task before playback with a MediaLibraryService
I've been attempting a full architecture of my music app's background playback service to use media3's MediaLibraryService. However, my app requires an extremely time-intensive loading task that must be completed before any playback can start, which leads to a large amount of issues with attempting to integrate the current architecture which is seemingly designed for only streaming use-cases and doesn't have any utilities to accommodate this type of process.
My ideas:
- I would try to couple the loading logic with the new service, but this requires low-level notification control I am unsure is feasible with the API given by MediaLibraryService. MediaNotification.Provider I assume is just called whenever the MediaSession itself changes, and is otherwise basically impossible to trigger or control in any other circumstance.
- I could try to spin off some long-running task with WorkManager, but I have no idea how feasible that even could be since it would mean attempting to start a foreground service from another foreground service. Additionally, I would still need to have a placeholder foreground notification within the MediaLibraryService, which is both ugly and still requires low-level notification control.
Would either of those work at all? Is there any part of the library that would allow me to handle this easier?
My current idea to work around it is this:
class MyService() : MediaLibraryService(), LongRunningLoadTaskListener {
// ... Initialization stuff
override fun onUpdateNotification(session: MediaSession, startInForegroundRequired: Boolean) {
super.onUpdateNotification(session, startInForegroundRequired);
onLongRunningLoadStateChanged()
}
override fun onLongRunningLoadStateChanged() {
if (mediaNotificationManager.isStartedInForeground) { // Assumes these are patched to be visible
return; // Already showing playback
}
// ... Show loading foreground state
// ... Player is started by loading eventually
}
}
Do you think this would work @marcbaechinger?
Any help here @marcbaechinger? I talked about this in #1249, but while most of the things blocking me from using the standard service lifecycle (i.e binding) can be resolved, the inability to integrate this loading task without risking huge lifecycle issues prevents me from going ahead with it.
This doesn't have to be an official part of the Media3 API, I just want a safe way to integrate this into the service lifecycle.
Please, any help? I don't want to end up shipping my own broken hacky lifecycle override that completely violates android specs here @marcbaechinger
Could you provide some additional insights into what you would like to do with the notification while this long-running prepare task is ongoing? Do you like to display no notification, a media notification or something else? The recent Android versions became very strict with regards to foreground service management, and you generally need to be able to set up your service with a working notification quickly to avoid exceptions like ForegroundServiceDidNotStartInTimeException.
Could you provide some additional insights into what you would like to do with the notification while this long-running prepare task is ongoing? Do you like to display no notification, a media notification or something else? The recent Android versions became very strict with regards to foreground service management, and you generally need to be able to set up your service with a working notification quickly to avoid exceptions like ForegroundServiceDidNotStartInTimeException.
I'm aware of this limitation and already have a non-media notification of my own that indicates loading progess @tonihei. I'm really open to anything as long as it works and slots neatly into Media3.
I just don't know how to handle managing the loading process and it's foreground state considering:
- The very transient nature of media foreground states (i.e automatically ending the foreground state after some time idle)
- Playback resumption (No clue what will happen if I suddenly decide to display a non-media notification, or if I need a long time to do loading)
- "Downtime" between when loading foreground state ends and playback foreground state begins (might cause foreground-start-from-background crashes if the service is being ran without any bound activities)
What I want is a way I could neatly slot this loading process and it's foreground notification into the Media3 service lifecycle and still have it all work and be within "best-practice".
That's definitely an interesting use case. So if I understand correctly, you are currently trying to show a non-media notification (presumably something to the effect of "Player loading..."?).
How do you set up the MediaSession while this loading is in progress? On a logical level, this session should expose your Player as being in STATE_BUFFERING until all the loading is resolved. I assume this is already the case, but please correct me if you are doing something else. This leaves two questions: how to make sure the session sees this state before all the required information for a proper setup are available, and how to convert this state into a suitable notification to tell the use what's happening.
Creating a session with STATE_BUFFERING: I'm trying to list some options and would be curious if any of those resonate with your setup and would fit into what you are looking for.
- Allow to create a
MediaSessionwith a nullPlayerthat is automatically translated to such a state. You can set your real player once ready. - Create the
MediaSessionwith a placeholderPlayerthat is always buffering and then replace it with your real player once ready - Create the
MediaSessionwith a forwardingDelayedPreparePlayerthat either returns placeholder states or forwards to the realPlayeronce available. This might be beneficial to the two variants above if you need to store state or pending change requests while you are in the initial loading state. - Create the
MediaSessionwith your realExoPlayer, but add a placeholderMediaItemto the player that never finishes preparing. You can update theMediaItemto the real one once you are ready.
Creating the special notification can probably be done by using setMediaNotificationProvider and either calling the DefaultMediaNotificationProvider once your player is up and running or manually creating your placeholder notification instead.
- Side question: Could you also create a media notification with just the placeholder text? This makes sure the system still sees your session associated to the notification. This may also become a requirement for media playback services in future Android versions.
How do you set up the MediaSession while this loading is in progress? On a logical level, this session should expose your Player as being in STATE_BUFFERING until all the loading is resolved. I assume this is already the case, but please correct me if you are doing something else.
Currently the player is actually left idle until loading is complete. Forcing a buffering state would be best, but it would also need to have all controls disabled alongside it obviously, but isn't necessarily the case from when my player is normally in a buffered
Allow to create a MediaSession with a null Player that is automatically translated to such a state. You can set your real player once ready.
Create the MediaSession with a placeholder Player that is always buffering and then replace it with your real player once ready
Create the MediaSession with a forwarding DelayedPreparePlayer that either returns placeholder states or forwards to the real Player once available. This might be beneficial to the two variants above if you need to store state or pending change requests while you are in the initial loading state.
All of these work for me, especially the third one since I would merge in my self-rolled action queueing system that I have (i.e if a user opens a file, for instance). I also think the third one is generally the best design, null is a lot less obvious about what it implies than a deliberate Player implementation and swapping out Player instances seems dangerous.
Create the MediaSession with your real ExoPlayer, but add a placeholder MediaItem to the player that never finishes preparing. You can update the MediaItem to the real one once you are ready.
This one seems a lot more precarious than a forwarding player, don't really like it.
Creating the special notification can probably be done by using setMediaNotificationProvider and either calling the DefaultMediaNotificationProvider once your player is up and running or manually creating your placeholder notification instead.
Side question: Could you also create a media notification with just the placeholder text? This makes sure the system still sees your session associated to the notification. This may also become a requirement for media playback services in future Android versions.
This should work, but it would need to be set up as to not override or conflict with the normal media notification or MediaSession metadata.
It seems like the best course of action is some kind of DelayedPreparePlayer implementation and then the rest is my own internal overrides. As an interim measure I'll make my own implementation of this and see how well it works in my app. Thanks @tonihei
Hey @tonihei. I went for trying to create a perpetually loading MediaItem, but I ended up running into another issue.
My app can also reload music during runtime, not necessarily at startup. Theoretically, you can have ongoing OR paused music playback alongside a load. This makes anything revolving around MediaItem workarounds impossible, since I would have to override the current playback with my perpetually loading MediaItem and render the player unusable.
That leaves me with overriding the player's low-level playback state or the MediaSession's playback state. This would also make the player unusable as the play/pause button in the notification would be disabled while 'loading', even if I can still play music during a loading task.
Is there literally any other way for me to indicate to the OS that I'm doing work (even if it's not related to playback) and so it shouldn't boot me off of foreground until I'm done? There has to be a way, since I never encountered this behavior when using my self-rolled MediaSession management.
Sorry for the delay in coming back to this.
My app can also reload music during runtime, not necessarily at startup. Theoretically, you can have ongoing OR paused music playback alongside a load. This makes anything revolving around MediaItem workarounds impossible, since I would have to override the current playback with my perpetually loading MediaItem and render the player unusable.
The problem of what you are describing sounds like a disconnect between what the actual ExoPlayer is doing (=not even set up, playing something else) vs what you'd like to show in a media notification.
In this case, we generally recommend to use SimpleBasePlayer as a intermediate layer that allows you to set any state you like in your player and handle incoming requests as needed. We also recently added a ForwardingSimpleBasePlayer that can wrap ExoPlayer for example and still intercept and override state as needed, but will only be released in 1.5.0.
Not sure if that would help in your case? These classes are the existing utils for the "Create the MediaSession with a placeholder Player that is always buffering and then replace it with your real player once ready" idea. For example,
create a placeholder SimpleBasePlayer with initially and later swap it out by a real ExoPlayer (optionally wrapped in ForwardingSimpleBasePlayer for intercepting calls and overriding state) using MediaSession.setPlayer.
Create the MediaSession with a forwarding DelayedPreparePlayer that either returns placeholder states or forwards to the real Player once available.
This idea is almost covered by ForwardingSimpleBasePlayer, except that it currently doesn't have a way to update the wrapped player internally. Would it help to have ForwardingSimpleBasePlayer.setPlayer(...) so that you can initially set it up with a placeholder version and later set the real ExoPlayer while still having all the intercepting and state management logic in one place?
Is there literally any other way for me to indicate to the OS that I'm doing work (even if it's not related to playback) and so it shouldn't boot me off of foreground until I'm done? There has to be a way, since I never encountered this behavior when using my self-rolled MediaSession management.
From our perspective, the main requirement is to always have a MediaSession with a state and a notification as soon as possible after the user initially requested to play something. This helps as a feedback indicator to the user, ensures consistent design (e.g. integration into the media carousel in the system UI) and helps the system to keep your service alive if it knows you are preparing media for playback. When you are referring to a "self-rolled MediaSession management" I assume you mean the non-media notification you post already to get into the foreground state? In terms of best practices, it would be best to use a proper media notification for the reasons I just listed. The session can indicate it's doing work by being in STATE_BUFFERING (that should ensure you stay in the foreground as long as needed). So this is really what we should be aiming for here.