From 719e517dda851f8feaf3470f21406648f745eae3 Mon Sep 17 00:00:00 2001 From: Lauris van Rijn Date: Fri, 29 Aug 2025 00:19:24 +0200 Subject: [PATCH] =?UTF-8?q?fix(androidauto):=20async=20handling=20of=20bro?= =?UTF-8?q?wseTree=20init=20instead=20of=20busy=E2=80=91loop?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../app/player/PlayerNotificationService.kt | 64 +++++++++++++++---- 1 file changed, 52 insertions(+), 12 deletions(-) diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt index e37e7147..5e3af81e 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt @@ -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,22 +1325,40 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { AbsLogger.info(tag, "onLoadChildren: Android auto data loaded") result.sendResult(children as MutableList?) } - } 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 -> - Log.d(tag, "[MENU: $parentMediaId] Showing list item ${item.description.title}") - MediaBrowserCompat.MediaItem( - item.description, - MediaBrowserCompat.MediaItem.FLAG_BROWSABLE - ) - } + + 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?) + } + 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, + MediaBrowserCompat.MediaItem.FLAG_BROWSABLE + ) + } result.sendResult(children as MutableList?) } else if (mediaManager.getIsLibrary(parentMediaId)) { // Load library items for library Log.d(tag, "Loading items for library $parentMediaId")