diff --git a/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemBrowser.kt b/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemBrowser.kt index a9c42527b..1b0252e10 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemBrowser.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/service/MediaItemBrowser.kt @@ -20,6 +20,7 @@ package org.oxycblt.auxio.music.service import android.content.Context import androidx.media3.common.MediaItem +import androidx.media3.session.MediaSession.ControllerInfo import dagger.hilt.android.qualifiers.ApplicationContext import javax.inject.Inject import kotlin.math.min @@ -51,11 +52,14 @@ constructor( ) : MusicRepository.UpdateListener { private val browserJob = Job() private val searchScope = CoroutineScope(browserJob + Dispatchers.Default) + private val searchSubscribers = mutableMapOf() private val searchResults = mutableMapOf>() private var invalidator: Invalidator? = null interface Invalidator { fun invalidate(ids: List) + + fun invalidate(controller: ControllerInfo, query: String, itemCount: Int) } fun attach(invalidator: Invalidator) { @@ -93,9 +97,16 @@ constructor( if (invalidateSearch) { for (entry in searchResults.entries) { - entry.value.cancel() + searchResults[entry.key]?.cancel() } searchResults.clear() + + for (entry in searchSubscribers.entries) { + if (searchResults[entry.value] != null) { + continue + } + searchResults[entry.value] = searchTo(entry.value) + } } } @@ -190,7 +201,8 @@ constructor( is Genre -> { val artists = GENRE_ARTISTS_SORT.artists(item.artists) val songs = listSettings.genreSongSort.songs(item.songs) - artists.map { it.toMediaItem(context) } + songs.map { it.toMediaItem(context, null) } + artists.map { it.toMediaItem(context) } + + songs.map { it.toMediaItem(context, null) } } is Playlist -> { item.songs.map { it.toMediaItem(context, item) } @@ -200,20 +212,17 @@ constructor( } } - suspend fun prepareSearch(query: String): Int { - val deviceLibrary = musicRepository.deviceLibrary - val userLibrary = musicRepository.userLibrary - if (deviceLibrary == null || userLibrary == null) { - return 0 + suspend fun prepareSearch(query: String, controller: ControllerInfo) { + searchSubscribers[controller] = query + val existing = searchResults[query] + if (existing == null) { + val new = searchTo(query) + searchResults[query] = new + new.await() + } else { + val items = existing.await() + invalidator?.invalidate(controller, query, items.count()) } - - if (query.isEmpty()) { - return 0 - } - - val deferred = searchTo(query, deviceLibrary, userLibrary) - searchResults[query] = deferred - return deferred.await().count() } suspend fun getSearchResult( @@ -221,22 +230,8 @@ constructor( page: Int, pageSize: Int, ): List? { - val deviceLibrary = musicRepository.deviceLibrary - val userLibrary = musicRepository.userLibrary - if (deviceLibrary == null || userLibrary == null) { - return listOf() - } - - if (query.isEmpty()) { - return listOf() - } - - val existing = searchResults[query] - if (existing != null) { - return existing.await().concat().paginate(page, pageSize) - } - - return searchTo(query, deviceLibrary, userLibrary).await().concat().paginate(page, pageSize) + val deferred = searchResults[query] ?: searchTo(query).also { searchResults[query] = it } + return deferred.await().concat().paginate(page, pageSize) } private fun SearchEngine.Items.concat(): MutableList { @@ -279,8 +274,13 @@ constructor( return count } - private fun searchTo(query: String, deviceLibrary: DeviceLibrary, userLibrary: UserLibrary) = + private fun searchTo(query: String) = searchScope.async { + if (query.isEmpty()) { + return@async SearchEngine.Items() + } + val deviceLibrary = musicRepository.deviceLibrary ?: return@async SearchEngine.Items() + val userLibrary = musicRepository.userLibrary ?: return@async SearchEngine.Items() val items = SearchEngine.Items( deviceLibrary.songs, @@ -288,7 +288,13 @@ constructor( deviceLibrary.artists, deviceLibrary.genres, userLibrary.playlists) - searchEngine.search(items, query) + val results = searchEngine.search(items, query) + for (entry in searchSubscribers.entries) { + if (entry.value == query) { + invalidator?.invalidate(entry.key, query, results.count()) + } + } + results } private fun List.paginate(page: Int, pageSize: Int): List? { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt index a93aa019a..91635d7dc 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/service/MediaSessionServiceFragment.kt @@ -224,8 +224,8 @@ constructor( ): ListenableFuture> = waitScope .async { - val count = mediaItemBrowser.prepareSearch(query) - session.notifySearchResultChanged(browser, query, count, params) + mediaItemBrowser.prepareSearch(query, browser) + // Invalidator will send the notify result LibraryResult.ofVoid() } .asListenableFuture() @@ -260,4 +260,12 @@ constructor( mediaSession.notifyChildrenChanged(id, Int.MAX_VALUE, null) } } + + override fun invalidate( + controller: MediaSession.ControllerInfo, + query: String, + itemCount: Int + ) { + mediaSession.notifySearchResultChanged(controller, query, itemCount, null) + } } diff --git a/app/src/main/java/org/oxycblt/auxio/search/SearchEngine.kt b/app/src/main/java/org/oxycblt/auxio/search/SearchEngine.kt index 0e0944961..7853bcca3 100644 --- a/app/src/main/java/org/oxycblt/auxio/search/SearchEngine.kt +++ b/app/src/main/java/org/oxycblt/auxio/search/SearchEngine.kt @@ -56,11 +56,11 @@ interface SearchEngine { * @param playlists A list of [Playlist], null if empty. */ data class Items( - val songs: Collection?, - val albums: Collection?, - val artists: Collection?, - val genres: Collection?, - val playlists: Collection? + val songs: Collection? = null, + val albums: Collection? = null, + val artists: Collection? = null, + val genres: Collection? = null, + val playlists: Collection? = null ) } diff --git a/app/src/main/java/org/oxycblt/auxio/tasker/Tasker.kt b/app/src/main/java/org/oxycblt/auxio/tasker/Tasker.kt new file mode 100644 index 000000000..e823bb338 --- /dev/null +++ b/app/src/main/java/org/oxycblt/auxio/tasker/Tasker.kt @@ -0,0 +1,2 @@ +package org.oxycblt.auxio.tasker +