mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-28 22:27:14 +02:00
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:
@@ -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]
|
||||||
|
|||||||
+48
-5
@@ -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>()
|
||||||
|
|||||||
Reference in New Issue
Block a user