media: Blocked UI when using large playlists
Issue
We are experiencing UI lags when using big playlists containing podcast urls. Specifically, we are using a MediaController
where we usesetMediaItems
to pass around 600 MediaItem
s. The Items are presented in a RecyclerView
where users can select the desired item. After selecting an item by click, we call MediaController#seekToDefaultPosition
. This call clearly freezes the UI and also makes everything unresponsive for a few milliseconds. We have not checked if the problems also occurs with big playlists of local audio files.
Reproduce
We successfully reproduced the issue in your session demo project by slightly modifying the following line
with
subItemMediaList.addAll(List(500) { children.get(0) })
As you can imagine, this code change only provokes a large playlist.
First select any audio file in the album, artist or genre folder. For example: Artist Folder -> The Kyoto Connection -> click on Intro - The Way …
You should see the UI lag when you try to scroll in the list immediately after clicking an item in the bottom list view in the PlayerActivity
.
We also found out that switching between two MediaItem
s which have been played before does not produces any lags. So maybe the UI issues are related to ongoing network calls.
For us, the UI lags are visible on the following phones, however, the bug is “better” visible on the Samsung S9:
- Samsung S9 (Android 11)
- OnePlus 6 (Android 11)
media3 Version: 1.0.0-alpha03
Please let us know if you have any further questions or if there is anything we can do to support you in finding the cause. Thanks for building media3!
Julian
About this issue
- Original URL
- State: open
- Created 2 years ago
- Reactions: 1
- Comments: 25 (6 by maintainers)
Commits related to this issue
- Only set mediaId on MediaItems in media controller This makes the media controller more responsive, see https://github.com/androidx/media/issues/81 — committed to FossifyOrg/Music-Player by naveensingh 9 months ago
Yes, simply calling it multiple times in a loop likely makes no big difference I’d say. You’d need to wait with a timed delay or for the callback as you suggested. This isn’t super easy to achieve of course and only helps with setting new items from your code (data flow 1 in my post above). It doesn’t help with data flow 2 (sending updates to controllers) as this needs to be solved inside our library.
Should also help with the player preparation (see “paging” player idea that relies on a similar pattern)
Probably caused by the pending tasks kept in memory. This should resolve itself by reducing overall delays (as we then don’t keep a long lists of pending tasks anymore).
@tonihei thanks for the much-needed clarifications!
Blocked UI when using large playlists (pretty much resolved)
Only setting the
mediaId
definitely helped reduce UI lag significantly, before this the app was almost unusable with a large number of items. I should mention that once the media items are set and the player is prepared/playing, I’m still getting some slowness on the UI side (not to mention ANRs) but I’m not yet sure about the exact cause (no issues if the media items aren’t set).I did some tests with ~10000 items and after calling
MediaController.setMediaItems()
, most of the time MediaSession’sonSetMediaItems()
is called within 5-20 seconds (guess it’s acceptable(?) for such playlists).Could you please provide some guidance on how to send incremental updates using a media controller? A simple
mediaItems.chunked(500).forEach { controller.addMediaItems(it) }
doesn’t seem to work for me. Are we supposed to set up aPlayer.Listener
(on the controller side) and send each chunk only afteronTimelineChanged()
is called?I’m curious, does sending incremental updates help ExoPlayer prepare faster too or is it just for fast MediaController=>MediaSession communication?
Unresponsive/slow player
When this happens MediaSession => MediaController updates are delayed as well. The
Player.Listener
callbacks on the MediaSession side are also delayed so it’s probably caused by the player taking too long to prepare with large playlists. I hope this will be fixed by the planned improvements.Not sure if it’s relevant but I was looking at the memory profiler to reduce memory usage and I noticed that when I do next or previous multiple times, the memory usage spikes up and even leads to an OOM if I keep doing it:
My current workaround is to throttle the
seekToNext()
/seekToPrevious()
calls in a forwarding player but it doesn’t work very reliably since the player is slow in general when the playlist is huge.A “paging” Player wrapper (deserves to be highlighted)
That is a great idea. If implemented, I think it will solve most if not all of these problems (other optimizations are nice too).
Thanks for the thorough reply - that’s useful to hear and we definitely know that large playlists continue to be problematic.
Let me summarize some of things we’ve done, discussed and are planning to do.
There are two main data flows to look at:
MediaController
(this is technically tracked by #592). This involves (a) App code to generate these items (b)MediaController
toMediaSession
Binder communication © Player code to handle and prepare the playlist (e.g. ExoPlayer)MediaSession
toMediaController
Binder communication (e)MediaSession
to legacyMediaSessionCompat
compatibility layerNote that all these steps need to handle the large number of items somehow and to improve the performance, there are two options: optimize the operation or avoid the operation as much as possible.
What we’ve done so far
Bundle
operations required for individual items. (see this comment: https://github.com/androidx/media/issues/81#issuecomment-1378747452)mediaId
values and avoiding calls by suggesting to incrementally send updates instead of all at once. (see #592)What we are planning to do next
1.2.0-rc01
.For later releases:
MediaController
instances in other processed (d). This avoids the issue by simply shortening the playlist. This is also a loss of functionality for remote Media3 controllers. For legacy controllers it never worked anyway.Player
wrapper implementation that let’s you handle large playlists in the session and UI, but only sets a subset on the actual player. This helps with step © for all types of players (not just ExoPlayer).What we are not planning to do
MediaSession
,MediaController
andExoPlayer
are already on a background thread anyway.Binder
communication to a background thread would likely only help for local in-process calls, which are already optimized by the planned steps above. (The remote cross-process calls are already on background threads)Some comments on your concrete proposals from the comment above:
This sounds strange, but may be related to a huge backlog of work generated by these actions. We’ll have a look at this case to see if there is something else going on.
ANRs can be caused by any of the steps mentioned above if they run within one main thread iteration. We’ll submit some improvements, and you can also check in your app code that it is as efficient as possible and not handling too many items at once if it can be avoided. The code in MediaController in particular is not the slow part (and also not blocking). And as explained above, we are also not planning to consider any further background threads because we don’t see a case where they would help further.
The delay is likely not ExoPlayer related, but in the session/controller communication as said above. We’ll check your reproduction steps though, just in case there is some additional problem here.
This comparison is not really useful because this setup neither has a player that supports playlists nor a session that knows and shares all the items. So it’s fast by removing all the functionality and avoiding all the work.
@rohitjoins probably knows this a bit better, but if my understanding is correct, the blocking happens on the reading side (=the
MediaController
) while it’s waiting for all the data to be transferred. So I think using a different thread for your player on theMediaSession
process won’t help?