fix(androidauto): async handling of browseTree init instead of busy‑loop

Removed blocking `while (!browseTree.isInitialized){}` in
`onLoadChildren`. Added `waitForBrowseTree` and `onBrowseTreeInitialized`
helpers to queue pending results until browseTree is ready. All
browseTree assignments now call `onBrowseTreeInitialized()`. This avoids
ANRs and high CPU when Android Auto requests children before init.
This commit is contained in:
Lauris van Rijn 2025-08-29 00:19:24 +02:00 committed by Lauris van Rijn
parent 361c55c5ac
commit 719e517dda

View file

@ -1087,6 +1087,26 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
private val DOWNLOADS_ROOT = "__DOWNLOADS__"
private val CONTINUE_ROOT = "__CONTINUE__"
private lateinit var browseTree: BrowseTree
private val browseTreeInitListeners = mutableListOf<() -> Unit>()
private fun waitForBrowseTree(cb: () -> Unit)
{
if (this::browseTree.isInitialized)
{
cb()
}
else
{
browseTreeInitListeners += cb
}
}
private fun onBrowseTreeInitialized()
{
// Called after browseTree is assigned for the first time
browseTreeInitListeners.forEach { it.invoke() }
browseTreeInitListeners.clear()
}
// Only allowing android auto or similar to access media browser service
// normal loading of audiobooks is handled in webview (not natively)
@ -1257,6 +1277,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
mediaManager.serverLibraries,
mediaManager.allLibraryPersonalizationsDone
)
onBrowseTreeInitialized()
val children =
browseTree[parentMediaId]?.map { item ->
Log.d(tag, "Found top menu item: ${item.description.title}")
@ -1291,6 +1312,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
mediaManager.serverLibraries,
mediaManager.allLibraryPersonalizationsDone
)
onBrowseTreeInitialized()
val children =
browseTree[parentMediaId]?.map { item ->
Log.d(tag, "Found top menu item: ${item.description.title}")
@ -1303,16 +1325,34 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
AbsLogger.info(tag, "onLoadChildren: Android auto data loaded")
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
}
} else if (parentMediaId == LIBRARIES_ROOT || parentMediaId == RECENTLY_ROOT) {
} else if (parentMediaId == LIBRARIES_ROOT || parentMediaId == RECENTLY_ROOT)
{
Log.d(tag, "First load done: $firstLoadDone")
if (!firstLoadDone) {
if (!firstLoadDone)
{
result.sendResult(null)
return
}
// Wait until top-menu is initialized
while (!this::browseTree.isInitialized) {}
val children =
browseTree[parentMediaId]?.map { item ->
if (!this::browseTree.isInitialized)
{
// ✅ good: detach and wait for init
result.detach()
waitForBrowseTree {
val children = browseTree[parentMediaId]?.map { item ->
Log.d(tag, "[MENU: $parentMediaId] Showing list item ${item.description.title}")
MediaBrowserCompat.MediaItem(
item.description,
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
)
}
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
}
return
}
// Already initialized: just return
val children = browseTree[parentMediaId]?.map { item ->
Log.d(tag, "[MENU: $parentMediaId] Showing list item ${item.description.title}")
MediaBrowserCompat.MediaItem(
item.description,