Android Auto: Fixed and improved search

Search now queries data from server. Results are grouped by books, series and authors.
This commit is contained in:
ISO-B
2024-11-08 12:49:47 +02:00
parent 8134ec84c6
commit eedcd188c3
7 changed files with 119 additions and 11 deletions
@@ -404,3 +404,22 @@ data class LibraryItemWithEpisode(
var libraryItemWrapper:LibraryItemWrapper, var libraryItemWrapper:LibraryItemWrapper,
var episode:PodcastEpisode var episode:PodcastEpisode
) )
@JsonIgnoreProperties(ignoreUnknown = true)
data class LibraryItemSearchResultSeriesItemType(
var series: LibrarySeriesItem,
var books: List<LibraryItem>?
)
@JsonIgnoreProperties(ignoreUnknown = true)
data class LibraryItemSearchResultLibraryItemType(
val libraryItem: LibraryItem
)
@JsonIgnoreProperties(ignoreUnknown = true)
data class LibraryItemSearchResultType(
var book:List<LibraryItemSearchResultLibraryItemType>?,
var podcast:List<LibraryItemSearchResultLibraryItemType>?,
var series:List<LibraryItemSearchResultSeriesItemType>?,
var authors:List<LibraryAuthorItem>?
)
@@ -4,6 +4,7 @@ import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.support.v4.media.MediaDescriptionCompat import android.support.v4.media.MediaDescriptionCompat
import androidx.media.utils.MediaConstants
import com.audiobookshelf.app.BuildConfig import com.audiobookshelf.app.BuildConfig
import com.audiobookshelf.app.R import com.audiobookshelf.app.R
import com.audiobookshelf.app.device.DeviceManager import com.audiobookshelf.app.device.DeviceManager
@@ -15,7 +16,6 @@ class LibraryAuthorItem(
id:String, id:String,
var libraryId:String, var libraryId:String,
var name:String, var name:String,
var lastFirst:String,
var description:String?, var description:String?,
var imagePath:String?, var imagePath:String?,
var addedAt:Long, var addedAt:Long,
@@ -40,8 +40,11 @@ class LibraryAuthorItem(
} }
@JsonIgnore @JsonIgnore
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat { fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context, groupTitle: String?): MediaDescriptionCompat {
val extras = Bundle() val extras = Bundle()
if (groupTitle !== null) {
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, groupTitle)
}
val mediaId = "__LIBRARY__${libraryId}__AUTHOR__${id}" val mediaId = "__LIBRARY__${libraryId}__AUTHOR__${id}"
return MediaDescriptionCompat.Builder() return MediaDescriptionCompat.Builder()
@@ -52,4 +55,9 @@ class LibraryAuthorItem(
.setExtras(extras) .setExtras(extras)
.build() .build()
} }
@JsonIgnore
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
return getMediaDescription(progress, ctx, null)
}
} }
@@ -84,7 +84,7 @@ class LibraryItem(
} }
@JsonIgnore @JsonIgnore
fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context, authorId: String?, showSeriesNumber: Boolean?): MediaDescriptionCompat { fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context, authorId: String?, showSeriesNumber: Boolean?, groupTitle: String?): MediaDescriptionCompat {
val extras = Bundle() val extras = Bundle()
if (collapsedSeries == null) { if (collapsedSeries == null) {
@@ -124,6 +124,9 @@ class LibraryItem(
) )
} }
} }
if (groupTitle !== null) {
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, groupTitle)
}
val mediaId = if (localLibraryItemId != null) { val mediaId = if (localLibraryItemId != null) {
localLibraryItemId localLibraryItemId
@@ -153,9 +156,14 @@ class LibraryItem(
.build() .build()
} }
@JsonIgnore
fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context, authorId: String?, showSeriesNumber: Boolean?): MediaDescriptionCompat {
return getMediaDescription(progress, ctx, authorId, showSeriesNumber, null)
}
@JsonIgnore @JsonIgnore
fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context, authorId: String?): MediaDescriptionCompat { fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context, authorId: String?): MediaDescriptionCompat {
return getMediaDescription(progress, ctx, authorId, null) return getMediaDescription(progress, ctx, authorId, null, null)
} }
@JsonIgnore @JsonIgnore
@@ -163,6 +171,6 @@ class LibraryItem(
/* /*
This is needed so Android auto library hierarchy for author series can be implemented This is needed so Android auto library hierarchy for author series can be implemented
*/ */
return getMediaDescription(progress, ctx, null, null) return getMediaDescription(progress, ctx, null, null, null)
} }
} }
@@ -3,6 +3,7 @@ package com.audiobookshelf.app.data
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.support.v4.media.MediaDescriptionCompat import android.support.v4.media.MediaDescriptionCompat
import androidx.media.utils.MediaConstants
import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@@ -29,7 +30,7 @@ class LibrarySeriesItem(
} }
@JsonIgnore @JsonIgnore
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat { fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context, groupTitle: String?): MediaDescriptionCompat {
val extras = Bundle() val extras = Bundle()
if (localLibraryItemId != null) { if (localLibraryItemId != null) {
@@ -38,6 +39,9 @@ class LibrarySeriesItem(
MediaDescriptionCompat.STATUS_DOWNLOADED MediaDescriptionCompat.STATUS_DOWNLOADED
) )
} }
if (groupTitle !== null) {
extras.putString(MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE, groupTitle)
}
val mediaId = "__LIBRARY__${libraryId}__SERIES__${id}" val mediaId = "__LIBRARY__${libraryId}__SERIES__${id}"
return MediaDescriptionCompat.Builder() return MediaDescriptionCompat.Builder()
@@ -48,4 +52,9 @@ class LibrarySeriesItem(
.setExtras(extras) .setExtras(extras)
.build() .build()
} }
@JsonIgnore
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
return getMediaDescription(progress, ctx, null)
}
} }
@@ -619,6 +619,14 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
} }
} }
suspend fun searchLocalCache(libraryId: String, queryString: String) : LibraryItemSearchResultType? {
return suspendCoroutine {
apiHandler.getSearchResults(libraryId, queryString) { results ->
it.resume(results)
}
}
}
fun getFirstItem() : LibraryItemWrapper? { fun getFirstItem() : LibraryItemWrapper? {
if (serverLibraryItems.isNotEmpty()) { if (serverLibraryItems.isNotEmpty()) {
return serverLibraryItems[0] return serverLibraryItems[0]
@@ -47,6 +47,7 @@ import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.source.hls.HlsMediaSource import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.ui.PlayerNotificationManager import com.google.android.exoplayer2.ui.PlayerNotificationManager
import com.google.android.exoplayer2.upstream.* import com.google.android.exoplayer2.upstream.*
import kotlinx.coroutines.runBlocking
import java.util.* import java.util.*
import kotlin.concurrent.schedule import kotlin.concurrent.schedule
@@ -1350,13 +1351,55 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
override fun onSearch(query: String, extras: Bundle?, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) { override fun onSearch(query: String, extras: Bundle?, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
result.detach() result.detach()
mediaManager.loadAndroidAutoItems { Log.d(tag, "Search bundle: $extras")
browseTree = BrowseTree(this, mediaManager.serverItemsInProgress, mediaManager.serverLibraries) var foundBooks: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
val children = browseTree[LIBRARIES_ROOT]?.map { item -> var foundPodcasts: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
MediaBrowserCompat.MediaItem(item.description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE) var foundSeries: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
var foundAuthors: MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
mediaManager.serverLibraries.forEach { serverLibrary ->
runBlocking{
val searchResult = mediaManager.searchLocalCache(serverLibrary.id, query)
Log.d(tag, "onSearch: SearchResult: $searchResult")
if (searchResult === null) return@runBlocking
if (searchResult.book !== null && searchResult.book!!.isNotEmpty()) {
Log.d(tag, "onSearch: found ${searchResult.book!!.size} books")
val children = searchResult.book!!.map { bookResult ->
val libraryItem = bookResult.libraryItem
val progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == libraryItem.id }
val localLibraryItem = DeviceManager.dbManager.getLocalLibraryItemByLId(libraryItem.id)
libraryItem.localLibraryItemId = localLibraryItem?.id
val description = libraryItem.getMediaDescription(progress, ctx, null, null, "Books (${serverLibrary.name})")
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
}
foundBooks.addAll(children)
}
if (searchResult.series !== null && searchResult.series!!.isNotEmpty()) {
Log.d(tag, "onSearch: found ${searchResult.series!!.size} series")
val children = searchResult.series!!.map { seriesResult ->
val seriesItem = seriesResult.series
seriesItem.books = seriesResult.books as MutableList<LibraryItem>
val description = seriesItem.getMediaDescription(null, ctx, "Series (${serverLibrary.name})")
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
}
foundSeries.addAll(children)
}
if (searchResult.authors !== null && searchResult.authors!!.isNotEmpty()) {
Log.d(tag, "onSearch: found ${searchResult.authors!!.size} authors")
val children = searchResult.authors!!.map { authorItem ->
val description = authorItem.getMediaDescription(null, ctx, "Authors (${serverLibrary.name})")
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
}
foundAuthors.addAll(children)
}
Log.d(tag, "onSearch: Library ${serverLibrary.id} processed")
} }
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?) Log.d(tag, "onSearch: Library ${serverLibrary.id} scanned")
} }
foundBooks.addAll(foundSeries)
foundBooks.addAll(foundAuthors)
result.sendResult(foundBooks as MutableList<MediaBrowserCompat.MediaItem>?)
Log.d(tag, "onSearch: Done")
} }
// //
@@ -286,6 +286,19 @@ class ApiHandler(var ctx:Context) {
} }
} }
fun getSearchResults(libraryId:String, queryString:String, cb: (LibraryItemSearchResultType?) -> Unit) {
Log.d(tag, "Doing search for library $libraryId")
getRequest("/api/libraries/$libraryId/search?q=$queryString", null, null) {
if (it.has("error")) {
Log.e(tag, it.getString("error") ?: "getSearchResults Failed")
cb(null)
} else {
val librarySearchResults = jacksonMapper.readValue<LibraryItemSearchResultType>(it.toString())
cb(librarySearchResults)
}
}
}
fun getAllItemsInProgress(cb: (List<ItemInProgress>) -> Unit) { fun getAllItemsInProgress(cb: (List<ItemInProgress>) -> Unit) {
getRequest("/api/me/items-in-progress", null, null) { getRequest("/api/me/items-in-progress", null, null) {
val items = mutableListOf<ItemInProgress>() val items = mutableListOf<ItemInProgress>()