music: re-add search browsing

This commit is contained in:
Alexander Capehart 2024-08-27 16:46:44 -06:00
parent b1e871c6e1
commit 924e3d1801
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 79 additions and 35 deletions

View file

@ -105,7 +105,7 @@ class AuxioService : MediaBrowserServiceCompat(), ForegroundListener, MusicServi
extras: Bundle?, extras: Bundle?,
result: Result<MutableList<MediaItem>> result: Result<MutableList<MediaItem>>
) { ) {
super.onSearch(query, extras, result) musicFragment.search(query, result)
} }
@SuppressLint("RestrictedApi") @SuppressLint("RestrictedApi")

View file

@ -307,21 +307,6 @@ constructor(
// results // results
// } // }
private fun List<MediaItem>.paginate(page: Int, pageSize: Int): List<MediaItem>? {
if (page == Int.MAX_VALUE) {
// I think if someone requests this page it more or less implies that I should
// return all of the pages.
return this
}
val start = page * pageSize
val end = min((page + 1) * pageSize, size) // Tolerate partial page queries
if (pageSize == 0 || start !in indices) {
// These pages are probably invalid. Hopefully this won't backfire.
return null
}
return subList(start, end).toMutableList()
}
private companion object { private companion object {
// TODO: Rely on detail item gen logic? // TODO: Rely on detail item gen logic?
val ARTIST_ALBUMS_SORT = Sort(Sort.Mode.ByDate, Sort.Direction.DESCENDING) val ARTIST_ALBUMS_SORT = Sort(Sort.Mode.ByDate, Sort.Direction.DESCENDING)

View file

@ -15,47 +15,45 @@
* You should have received a copy of the GNU General Public License * You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package org.oxycblt.auxio.music.service package org.oxycblt.auxio.music.service
import android.content.Context import android.content.Context
import android.os.Bundle
import android.os.PowerManager
import androidx.media.MediaBrowserServiceCompat.BrowserRoot
import androidx.media.MediaBrowserServiceCompat
import androidx.media.utils.MediaConstants
import android.support.v4.media.MediaBrowserCompat.MediaItem import android.support.v4.media.MediaBrowserCompat.MediaItem
import coil.ImageLoader import androidx.media.MediaBrowserServiceCompat
import androidx.media.MediaBrowserServiceCompat.BrowserRoot
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import org.oxycblt.auxio.BuildConfig import kotlinx.coroutines.launch
import org.oxycblt.auxio.ForegroundListener import org.oxycblt.auxio.ForegroundListener
import org.oxycblt.auxio.ForegroundServiceNotification import org.oxycblt.auxio.ForegroundServiceNotification
import org.oxycblt.auxio.music.IndexingState import org.oxycblt.auxio.music.IndexingState
import org.oxycblt.auxio.music.MusicRepository import org.oxycblt.auxio.music.MusicRepository
import org.oxycblt.auxio.music.MusicSettings import org.oxycblt.auxio.music.MusicSettings
import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.search.SearchEngine
import org.oxycblt.auxio.util.getSystemServiceCompat
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.logW
import javax.inject.Inject
class MusicServiceFragment class MusicServiceFragment
@Inject @Inject
constructor( constructor(
@ApplicationContext context: Context, @ApplicationContext private val context: Context,
private val indexer: Indexer, private val indexer: Indexer,
private val browser: MusicBrowser, private val musicBrowser: MusicBrowser,
private val musicRepository: MusicRepository, private val musicRepository: MusicRepository,
private val musicSettings: MusicSettings, private val musicSettings: MusicSettings,
private val searchEngine: SearchEngine,
private val contentObserver: SystemContentObserver, private val contentObserver: SystemContentObserver,
) : MusicBrowser.Invalidator, MusicSettings.Listener { ) : MusicBrowser.Invalidator, MusicSettings.Listener {
private val indexingNotification = IndexingNotification(context) private val indexingNotification = IndexingNotification(context)
private val observingNotification = ObservingNotification(context) private val observingNotification = ObservingNotification(context)
private var invalidator: Invalidator? = null private var invalidator: Invalidator? = null
private var foregroundListener: ForegroundListener? = null private var foregroundListener: ForegroundListener? = null
private val dispatchJob = Job()
private val dispatchScope = CoroutineScope(dispatchJob + Dispatchers.Default)
interface Invalidator { interface Invalidator {
fun invalidateMusic(mediaId: String) fun invalidateMusic(mediaId: String)
@ -64,7 +62,7 @@ constructor(
fun attach(foregroundListener: ForegroundListener, invalidator: Invalidator) { fun attach(foregroundListener: ForegroundListener, invalidator: Invalidator) {
this.invalidator = invalidator this.invalidator = invalidator
indexer.attach(foregroundListener) indexer.attach(foregroundListener)
browser.attach(this) musicBrowser.attach(this)
contentObserver.attach() contentObserver.attach()
musicSettings.registerListener(this) musicSettings.registerListener(this)
} }
@ -72,7 +70,8 @@ constructor(
fun release() { fun release() {
musicSettings.unregisterListener(this) musicSettings.unregisterListener(this)
contentObserver.release() contentObserver.release()
browser.release() dispatchJob.cancel()
musicBrowser.release()
indexer.release() indexer.release()
invalidator = null invalidator = null
} }
@ -127,10 +126,53 @@ constructor(
fun getRoot() = BrowserRoot(Category.ROOT.id, null) fun getRoot() = BrowserRoot(Category.ROOT.id, null)
fun getItem(mediaId: String, result: MediaBrowserServiceCompat.Result<MediaItem>) = fun getItem(mediaId: String, result: MediaBrowserServiceCompat.Result<MediaItem>) =
result.dispatch { browser.getItem(mediaId) } result.dispatch { musicBrowser.getItem(mediaId) }
fun getChildren(mediaId: String, result: MediaBrowserServiceCompat.Result<MutableList<MediaItem>>) = fun getChildren(
result.dispatch { browser.getChildren(mediaId)?.toMutableList() } mediaId: String,
result: MediaBrowserServiceCompat.Result<MutableList<MediaItem>>
) =
result.dispatch { musicBrowser.getChildren(mediaId)?.toMutableList() }
fun search(query: String, result: MediaBrowserServiceCompat.Result<MutableList<MediaItem>>) =
result.dispatchAsync {
if (query.isEmpty()) {
return@dispatchAsync mutableListOf()
}
val deviceLibrary =
musicRepository.deviceLibrary ?: return@dispatchAsync mutableListOf()
val userLibrary = musicRepository.userLibrary ?: return@dispatchAsync mutableListOf()
val items =
SearchEngine.Items(
deviceLibrary.songs,
deviceLibrary.albums,
deviceLibrary.artists,
deviceLibrary.genres,
userLibrary.playlists
)
searchEngine.search(items, query).concat()
}
private fun SearchEngine.Items.concat(): MutableList<MediaItem> {
val music = mutableListOf<MediaItem>()
if (songs != null) {
music.addAll(songs.map { it.toMediaItem(context, null) })
}
if (albums != null) {
music.addAll(albums.map { it.toMediaItem(context) })
}
if (artists != null) {
music.addAll(artists.map { it.toMediaItem(context) })
}
if (genres != null) {
music.addAll(genres.map { it.toMediaItem(context) })
}
if (playlists != null) {
music.addAll(playlists.map { it.toMediaItem(context) })
}
return music
}
private fun <T> MediaBrowserServiceCompat.Result<T>.dispatch(body: () -> T?) { private fun <T> MediaBrowserServiceCompat.Result<T>.dispatch(body: () -> T?) {
try { try {
@ -144,4 +186,19 @@ constructor(
sendResult(null) sendResult(null)
} }
} }
private fun <T> MediaBrowserServiceCompat.Result<T>.dispatchAsync(body: suspend () -> T?) {
dispatchScope.launch {
try {
val result = body()
if (result == null) {
logW("Result is null")
}
sendResult(result)
} catch (e: Exception) {
logD("Error while dispatching: $e")
sendResult(null)
}
}
}
} }

View file

@ -61,7 +61,9 @@ interface SearchEngine {
val artists: Collection<Artist>? = null, val artists: Collection<Artist>? = null,
val genres: Collection<Genre>? = null, val genres: Collection<Genre>? = null,
val playlists: Collection<Playlist>? = null val playlists: Collection<Playlist>? = null
) ) {
}
} }
class SearchEngineImpl @Inject constructor(@ApplicationContext private val context: Context) : class SearchEngineImpl @Inject constructor(@ApplicationContext private val context: Context) :