service: add search functionality
I cannot tell if this actually works yet.
This commit is contained in:
parent
e9a4b99aa5
commit
48275c4698
1 changed files with 149 additions and 22 deletions
|
@ -109,6 +109,7 @@ import org.oxycblt.auxio.playback.state.RawQueue
|
||||||
import org.oxycblt.auxio.playback.state.RepeatMode
|
import org.oxycblt.auxio.playback.state.RepeatMode
|
||||||
import org.oxycblt.auxio.playback.state.ShuffleMode
|
import org.oxycblt.auxio.playback.state.ShuffleMode
|
||||||
import org.oxycblt.auxio.playback.state.StateAck
|
import org.oxycblt.auxio.playback.state.StateAck
|
||||||
|
import org.oxycblt.auxio.search.SearchEngine
|
||||||
import org.oxycblt.auxio.util.getPlural
|
import org.oxycblt.auxio.util.getPlural
|
||||||
import org.oxycblt.auxio.util.getSystemServiceCompat
|
import org.oxycblt.auxio.util.getSystemServiceCompat
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
|
@ -157,6 +158,11 @@ class AuxioService :
|
||||||
@Inject lateinit var widgetComponent: WidgetComponent
|
@Inject lateinit var widgetComponent: WidgetComponent
|
||||||
@Inject lateinit var bitmapLoader: NeoBitmapLoader
|
@Inject lateinit var bitmapLoader: NeoBitmapLoader
|
||||||
|
|
||||||
|
@Inject lateinit var searchEngine: SearchEngine
|
||||||
|
private var searchResultsCache = mutableMapOf<String, SearchEngine.Items>()
|
||||||
|
private var searchScope = CoroutineScope(serviceJob + Dispatchers.Default)
|
||||||
|
private var searchJob: Job? = null
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
|
@ -692,11 +698,16 @@ class AuxioService :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMusicChanges(changes: MusicRepository.Changes) {
|
override fun onMusicChanges(changes: MusicRepository.Changes) {
|
||||||
if (changes.deviceLibrary && musicRepository.deviceLibrary != null) {
|
if (changes.deviceLibrary) {
|
||||||
|
if (musicRepository.deviceLibrary != null) {
|
||||||
// We now have a library, see if we have anything we need to do.
|
// We now have a library, see if we have anything we need to do.
|
||||||
logD("Library obtained, requesting action")
|
logD("Library obtained, requesting action")
|
||||||
playbackManager.requestAction(this)
|
playbackManager.requestAction(this)
|
||||||
}
|
}
|
||||||
|
// Invalidate anything we searched prior.
|
||||||
|
searchResultsCache.clear()
|
||||||
|
searchJob?.cancel()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- MEDIASESSION OVERRIDES ---
|
// --- MEDIASESSION OVERRIDES ---
|
||||||
|
@ -720,7 +731,6 @@ class AuxioService :
|
||||||
session: MediaSession,
|
session: MediaSession,
|
||||||
controller: MediaSession.ControllerInfo
|
controller: MediaSession.ControllerInfo
|
||||||
): ConnectionResult {
|
): ConnectionResult {
|
||||||
logD(Exception().stackTraceToString())
|
|
||||||
val sessionCommands =
|
val sessionCommands =
|
||||||
ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
|
ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
|
||||||
.add(SessionCommand(ACTION_INC_REPEAT_MODE, Bundle.EMPTY))
|
.add(SessionCommand(ACTION_INC_REPEAT_MODE, Bundle.EMPTY))
|
||||||
|
@ -738,9 +748,7 @@ class AuxioService :
|
||||||
browser: MediaSession.ControllerInfo,
|
browser: MediaSession.ControllerInfo,
|
||||||
params: LibraryParams?
|
params: LibraryParams?
|
||||||
): ListenableFuture<LibraryResult<MediaItem>> {
|
): ListenableFuture<LibraryResult<MediaItem>> {
|
||||||
val result =
|
val result = LibraryResult.ofItem(ExternalUID.Category.ROOT.toMediaItem(this), params)
|
||||||
LibraryResult.ofItem(
|
|
||||||
ExternalUID.Category.ROOT.toMediaItem(this), LibraryParams.Builder().build())
|
|
||||||
return Futures.immediateFuture(result)
|
return Futures.immediateFuture(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -764,15 +772,18 @@ class AuxioService :
|
||||||
val deviceLibrary = musicRepository.deviceLibrary
|
val deviceLibrary = musicRepository.deviceLibrary
|
||||||
val userLibrary = musicRepository.userLibrary
|
val userLibrary = musicRepository.userLibrary
|
||||||
if (deviceLibrary == null || userLibrary == null) {
|
if (deviceLibrary == null || userLibrary == null) {
|
||||||
return Futures.immediateFuture(
|
return Futures.immediateFuture(LibraryResult.ofItemList(emptyList(), params))
|
||||||
LibraryResult.ofItemList(emptyList(), LibraryParams.Builder().build()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val items =
|
val items =
|
||||||
getMediaItemList(parentId, deviceLibrary, userLibrary)
|
getMediaItemList(parentId, deviceLibrary, userLibrary)
|
||||||
?: return Futures.immediateFuture(
|
?: return Futures.immediateFuture(
|
||||||
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE))
|
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE))
|
||||||
val result = LibraryResult.ofItemList(items, LibraryParams.Builder().build())
|
val paginatedItems =
|
||||||
|
items.paginate(page, pageSize)
|
||||||
|
?: return Futures.immediateFuture(
|
||||||
|
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE))
|
||||||
|
val result = LibraryResult.ofItemList(paginatedItems, params)
|
||||||
return Futures.immediateFuture(result)
|
return Futures.immediateFuture(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -833,6 +844,128 @@ class AuxioService :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSearch(
|
||||||
|
session: MediaLibrarySession,
|
||||||
|
browser: MediaSession.ControllerInfo,
|
||||||
|
query: String,
|
||||||
|
params: LibraryParams?
|
||||||
|
): ListenableFuture<LibraryResult<Void>> {
|
||||||
|
val deviceLibrary = musicRepository.deviceLibrary
|
||||||
|
val userLibrary = musicRepository.userLibrary
|
||||||
|
if (deviceLibrary == null || userLibrary == null) {
|
||||||
|
return Futures.immediateFuture(
|
||||||
|
LibraryResult.ofError(LibraryResult.RESULT_ERROR_INVALID_STATE))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.isEmpty()) {
|
||||||
|
return Futures.immediateFuture(LibraryResult.ofVoid())
|
||||||
|
}
|
||||||
|
|
||||||
|
val future = SettableFuture.create<LibraryResult<Void>>()
|
||||||
|
searchTo(query, deviceLibrary, userLibrary) { future.set(LibraryResult.ofVoid()) }
|
||||||
|
return Futures.immediateFuture(LibraryResult.ofVoid())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGetSearchResult(
|
||||||
|
session: MediaLibrarySession,
|
||||||
|
browser: MediaSession.ControllerInfo,
|
||||||
|
query: String,
|
||||||
|
page: Int,
|
||||||
|
pageSize: Int,
|
||||||
|
params: LibraryParams?
|
||||||
|
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||||
|
val deviceLibrary = musicRepository.deviceLibrary
|
||||||
|
val userLibrary = musicRepository.userLibrary
|
||||||
|
if (deviceLibrary == null || userLibrary == null) {
|
||||||
|
return Futures.immediateFuture(
|
||||||
|
LibraryResult.ofError(LibraryResult.RESULT_ERROR_INVALID_STATE))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (query.isEmpty()) {
|
||||||
|
return Futures.immediateFuture(LibraryResult.ofItemList(emptyList(), params))
|
||||||
|
}
|
||||||
|
|
||||||
|
val items = searchResultsCache[query]
|
||||||
|
if (items != null) {
|
||||||
|
val concatenatedItems = items.concat()
|
||||||
|
val paginatedItems =
|
||||||
|
concatenatedItems.paginate(page, pageSize)
|
||||||
|
?: return Futures.immediateFuture(
|
||||||
|
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE))
|
||||||
|
val result = LibraryResult.ofItemList(paginatedItems, params)
|
||||||
|
return Futures.immediateFuture(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
val future = SettableFuture.create<LibraryResult<ImmutableList<MediaItem>>>()
|
||||||
|
searchTo(query, deviceLibrary, userLibrary) {
|
||||||
|
val concatenatedItems = it.concat()
|
||||||
|
val paginatedItems = concatenatedItems.paginate(page, pageSize) ?: return@searchTo
|
||||||
|
val result = LibraryResult.ofItemList(paginatedItems, params)
|
||||||
|
future.set(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
return future
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun SearchEngine.Items.concat(): MutableList<MediaItem> {
|
||||||
|
val music = mutableListOf<MediaItem>()
|
||||||
|
if (songs != null) {
|
||||||
|
music.addAll(songs.map { it.toMediaItem(this@AuxioService, null) })
|
||||||
|
}
|
||||||
|
if (albums != null) {
|
||||||
|
music.addAll(albums.map { it.toMediaItem(this@AuxioService, null) })
|
||||||
|
}
|
||||||
|
if (artists != null) {
|
||||||
|
music.addAll(artists.map { it.toMediaItem(this@AuxioService, null) })
|
||||||
|
}
|
||||||
|
if (genres != null) {
|
||||||
|
music.addAll(genres.map { it.toMediaItem(this@AuxioService) })
|
||||||
|
}
|
||||||
|
if (playlists != null) {
|
||||||
|
music.addAll(playlists.map { it.toMediaItem(this@AuxioService) })
|
||||||
|
}
|
||||||
|
return music
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchTo(
|
||||||
|
query: String,
|
||||||
|
deviceLibrary: DeviceLibrary,
|
||||||
|
userLibrary: UserLibrary,
|
||||||
|
cb: (SearchEngine.Items) -> Unit
|
||||||
|
) {
|
||||||
|
// TODO: Queue up searches rather than clobbering the last one
|
||||||
|
searchJob?.cancel()
|
||||||
|
searchJob =
|
||||||
|
searchScope.launch {
|
||||||
|
val items =
|
||||||
|
SearchEngine.Items(
|
||||||
|
deviceLibrary.songs,
|
||||||
|
deviceLibrary.albums,
|
||||||
|
deviceLibrary.artists,
|
||||||
|
deviceLibrary.genres,
|
||||||
|
userLibrary.playlists)
|
||||||
|
val results = searchEngine.search(items, query)
|
||||||
|
searchResultsCache[query] = results
|
||||||
|
cb(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 = (page + 1) * pageSize
|
||||||
|
if (start !in indices || end - 1 !in indices) {
|
||||||
|
// Assume that everything out of bounds is a weird magic value implying that it
|
||||||
|
// actually wants all of the pages. This will not backfire at all.
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
return subList(page * pageSize, (page + 1) * pageSize).toMutableList()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onSetMediaItems(
|
override fun onSetMediaItems(
|
||||||
mediaSession: MediaSession,
|
mediaSession: MediaSession,
|
||||||
controller: MediaSession.ControllerInfo,
|
controller: MediaSession.ControllerInfo,
|
||||||
|
@ -840,14 +973,11 @@ class AuxioService :
|
||||||
startIndex: Int,
|
startIndex: Int,
|
||||||
startPositionMs: Long
|
startPositionMs: Long
|
||||||
): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
|
): ListenableFuture<MediaSession.MediaItemsWithStartPosition> {
|
||||||
val deviceLibrary =
|
|
||||||
musicRepository.deviceLibrary
|
|
||||||
?: return Futures.immediateFailedFuture(Exception("Invalid state"))
|
|
||||||
val result =
|
val result =
|
||||||
if (mediaItems.size > 1) {
|
if (mediaItems.size > 1) {
|
||||||
playMediaItemSelection(mediaItems, startIndex, deviceLibrary)
|
playMediaItemSelection(mediaItems, startIndex)
|
||||||
} else {
|
} else {
|
||||||
playSingleMediaItem(mediaItems.first(), deviceLibrary)
|
playSingleMediaItem(mediaItems.first())
|
||||||
}
|
}
|
||||||
return if (result) {
|
return if (result) {
|
||||||
// This will not actually do anything to the player, I patched that out
|
// This will not actually do anything to the player, I patched that out
|
||||||
|
@ -858,11 +988,8 @@ class AuxioService :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playMediaItemSelection(
|
private fun playMediaItemSelection(mediaItems: List<MediaItem>, startIndex: Int): Boolean {
|
||||||
mediaItems: List<MediaItem>,
|
val deviceLibrary = musicRepository.deviceLibrary ?: return false
|
||||||
startIndex: Int,
|
|
||||||
deviceLibrary: DeviceLibrary
|
|
||||||
): Boolean {
|
|
||||||
val targetSong = mediaItems.getOrNull(startIndex)?.toSong(deviceLibrary)
|
val targetSong = mediaItems.getOrNull(startIndex)?.toSong(deviceLibrary)
|
||||||
val songs = mediaItems.mapNotNull { it.toSong(deviceLibrary) }
|
val songs = mediaItems.mapNotNull { it.toSong(deviceLibrary) }
|
||||||
var index = startIndex
|
var index = startIndex
|
||||||
|
@ -875,7 +1002,7 @@ class AuxioService :
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playSingleMediaItem(mediaItem: MediaItem, deviceLibrary: DeviceLibrary): Boolean {
|
private fun playSingleMediaItem(mediaItem: MediaItem): Boolean {
|
||||||
val uid = ExternalUID.fromString(mediaItem.mediaId) ?: return false
|
val uid = ExternalUID.fromString(mediaItem.mediaId) ?: return false
|
||||||
val music: Music
|
val music: Music
|
||||||
var parent: MusicParent? = null
|
var parent: MusicParent? = null
|
||||||
|
|
Loading…
Reference in a new issue