mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-30 23:27:17 +02:00
Enchancements for Android Auto library
- Hide libraries without audiobooks - Sort books in series by sequence value - Added option for selecting ASC or DESC sorting for series - Order authors alphabetically
This commit is contained in:
@@ -214,7 +214,9 @@ class BookMetadata(
|
|||||||
var authorName:String?,
|
var authorName:String?,
|
||||||
var authorNameLF:String?,
|
var authorNameLF:String?,
|
||||||
var narratorName:String?,
|
var narratorName:String?,
|
||||||
var seriesName:String?
|
var seriesName:String?,
|
||||||
|
@JsonFormat(with=[JsonFormat.Feature.ACCEPT_SINGLE_VALUE_AS_ARRAY])
|
||||||
|
var series:List<SeriesType>?
|
||||||
) : MediaTypeMetadata(title, explicit) {
|
) : MediaTypeMetadata(title, explicit) {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
override fun getAuthorDisplayName():String { return authorName ?: "Unknown" }
|
override fun getAuthorDisplayName():String { return authorName ?: "Unknown" }
|
||||||
@@ -342,7 +344,8 @@ data class Library(
|
|||||||
var name:String,
|
var name:String,
|
||||||
var folders:MutableList<Folder>,
|
var folders:MutableList<Folder>,
|
||||||
var icon:String,
|
var icon:String,
|
||||||
var mediaType:String
|
var mediaType:String,
|
||||||
|
var stats: LibraryStats?
|
||||||
) {
|
) {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getMediaMetadata(): MediaMetadataCompat {
|
fun getMediaMetadata(): MediaMetadataCompat {
|
||||||
@@ -354,6 +357,20 @@ data class Library(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
data class LibraryStats(
|
||||||
|
var totalItems: Int,
|
||||||
|
var totalAuthors: Int,
|
||||||
|
var numAudioTracks: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
data class SeriesType(
|
||||||
|
var id: String,
|
||||||
|
var name: String,
|
||||||
|
var sequence: String?
|
||||||
|
)
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
data class Folder(
|
data class Folder(
|
||||||
var id:String,
|
var id:String,
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ enum class StreamingUsingCellularSetting {
|
|||||||
ASK, ALWAYS, NEVER
|
ASK, ALWAYS, NEVER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class AndroidAutoBrowseSeriesSequenceOrderSetting {
|
||||||
|
ASC, DESC
|
||||||
|
}
|
||||||
|
|
||||||
data class ServerConnectionConfig(
|
data class ServerConnectionConfig(
|
||||||
var id:String,
|
var id:String,
|
||||||
var index:Int,
|
var index:Int,
|
||||||
@@ -136,7 +140,8 @@ data class DeviceSettings(
|
|||||||
var streamingUsingCellular: StreamingUsingCellularSetting,
|
var streamingUsingCellular: StreamingUsingCellularSetting,
|
||||||
var androidAutoBrowseForceGrouping: Boolean,
|
var androidAutoBrowseForceGrouping: Boolean,
|
||||||
var androidAutoBrowseTopLevelLimitForGrouping: Int,
|
var androidAutoBrowseTopLevelLimitForGrouping: Int,
|
||||||
var androidAutoBrowseLimitForGrouping: Int
|
var androidAutoBrowseLimitForGrouping: Int,
|
||||||
|
var androidAutoBrowseSeriesSequenceOrder: AndroidAutoBrowseSeriesSequenceOrderSetting
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
// Static method to get default device settings
|
// Static method to get default device settings
|
||||||
@@ -165,7 +170,8 @@ data class DeviceSettings(
|
|||||||
streamingUsingCellular = StreamingUsingCellularSetting.ALWAYS,
|
streamingUsingCellular = StreamingUsingCellularSetting.ALWAYS,
|
||||||
androidAutoBrowseForceGrouping = false,
|
androidAutoBrowseForceGrouping = false,
|
||||||
androidAutoBrowseTopLevelLimitForGrouping = 100,
|
androidAutoBrowseTopLevelLimitForGrouping = 100,
|
||||||
androidAutoBrowseLimitForGrouping = 50
|
androidAutoBrowseLimitForGrouping = 50,
|
||||||
|
androidAutoBrowseSeriesSequenceOrder = AndroidAutoBrowseSeriesSequenceOrderSetting.ASC
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,8 +64,27 @@ class LibraryItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@get:JsonIgnore
|
||||||
|
val seriesSequence: String
|
||||||
|
get() {
|
||||||
|
if (mediaType != "podcast") {
|
||||||
|
return ((media as Book).metadata as BookMetadata).series?.get(0)?.sequence.orEmpty()
|
||||||
|
} else {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:JsonIgnore
|
||||||
|
val seriesSequenceParts: List<String>
|
||||||
|
get() {
|
||||||
|
if (seriesSequence.isEmpty()) {
|
||||||
|
return listOf("")
|
||||||
|
}
|
||||||
|
return seriesSequence.split(".", limit = 2)
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context, authorId: String?): MediaDescriptionCompat {
|
fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context, authorId: String?, showSeriesNumber: Boolean?): MediaDescriptionCompat {
|
||||||
val extras = Bundle()
|
val extras = Bundle()
|
||||||
|
|
||||||
if (collapsedSeries == null) {
|
if (collapsedSeries == null) {
|
||||||
@@ -121,20 +140,29 @@ class LibraryItem(
|
|||||||
if (collapsedSeries != null) {
|
if (collapsedSeries != null) {
|
||||||
subtitle = "${collapsedSeries!!.numBooks} books"
|
subtitle = "${collapsedSeries!!.numBooks} books"
|
||||||
}
|
}
|
||||||
|
var itemTitle = title
|
||||||
|
if (showSeriesNumber == true && seriesSequence != "") {
|
||||||
|
itemTitle = "$seriesSequence. $itemTitle"
|
||||||
|
}
|
||||||
return MediaDescriptionCompat.Builder()
|
return MediaDescriptionCompat.Builder()
|
||||||
.setMediaId(mediaId)
|
.setMediaId(mediaId)
|
||||||
.setTitle(title)
|
.setTitle(itemTitle)
|
||||||
.setIconUri(getCoverUri())
|
.setIconUri(getCoverUri())
|
||||||
.setSubtitle(subtitle)
|
.setSubtitle(subtitle)
|
||||||
.setExtras(extras)
|
.setExtras(extras)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context, authorId: String?): MediaDescriptionCompat {
|
||||||
|
return getMediaDescription(progress, ctx, authorId, null)
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
|
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
|
||||||
/*
|
/*
|
||||||
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)
|
return getMediaDescription(progress, ctx, null, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ data class LocalMediaItem(
|
|||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getMediaMetadata():MediaTypeMetadata {
|
fun getMediaMetadata():MediaTypeMetadata {
|
||||||
return if (mediaType == "book") {
|
return if (mediaType == "book") {
|
||||||
BookMetadata(name,null, mutableListOf(), mutableListOf(), mutableListOf(),null,null,null,null,null,null,null,false,null,null,null,null)
|
BookMetadata(name,null, mutableListOf(), mutableListOf(), mutableListOf(),null,null,null,null,null,null,null,false,null,null,null, null, null)
|
||||||
} else {
|
} else {
|
||||||
PodcastMetadata(name,null,null, mutableListOf(), false)
|
PodcastMetadata(name,null,null, mutableListOf(), false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,6 +71,9 @@ object DeviceManager {
|
|||||||
if (deviceData.deviceSettings?.androidAutoBrowseLimitForGrouping == null) {
|
if (deviceData.deviceSettings?.androidAutoBrowseLimitForGrouping == null) {
|
||||||
deviceData.deviceSettings?.androidAutoBrowseLimitForGrouping = 50
|
deviceData.deviceSettings?.androidAutoBrowseLimitForGrouping = 50
|
||||||
}
|
}
|
||||||
|
if (deviceData.deviceSettings?.androidAutoBrowseSeriesSequenceOrder == null) {
|
||||||
|
deviceData.deviceSettings?.androidAutoBrowseSeriesSequenceOrder = AndroidAutoBrowseSeriesSequenceOrderSetting.ASC
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBase64Id(id:String):String {
|
fun getBase64Id(id:String):String {
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
return serverLibraries.find { it.id == id } != null
|
return serverLibraries.find { it.id == id } != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getLibrary(id:String) : Library? {
|
||||||
|
return serverLibraries.find { it.id == id }
|
||||||
|
}
|
||||||
|
|
||||||
fun getSavedPlaybackRate():Float {
|
fun getSavedPlaybackRate():Float {
|
||||||
if (userSettingsPlaybackRate != null) {
|
if (userSettingsPlaybackRate != null) {
|
||||||
return userSettingsPlaybackRate ?: 1f
|
return userSettingsPlaybackRate ?: 1f
|
||||||
@@ -185,6 +189,14 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
cb(seriesWithBooks)
|
cb(seriesWithBooks)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun sortSeriesBooks(seriesBooks: List<LibraryItem>) : List<LibraryItem> {
|
||||||
|
val sortingLogic = compareBy<LibraryItem> { it.seriesSequenceParts[0].length }
|
||||||
|
.thenBy { it.seriesSequenceParts[0].ifEmpty { "" } }
|
||||||
|
.thenBy { it.seriesSequenceParts.getOrElse(1) { "" }.length }
|
||||||
|
.thenBy { it.seriesSequenceParts.getOrElse(1) { "" } }
|
||||||
|
return seriesBooks.sortedWith(sortingLogic)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns books for series from library.
|
* Returns books for series from library.
|
||||||
* If data is not found from local cache then it will be fetched from server
|
* If data is not found from local cache then it will be fetched from server
|
||||||
@@ -202,14 +214,15 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
Log.d(tag, "Items for series $seriesId loaded from server | Library $libraryId")
|
Log.d(tag, "Items for series $seriesId loaded from server | Library $libraryId")
|
||||||
val libraryItemsWithAudio = libraryItems.filter { li -> li.checkHasTracks() }
|
val libraryItemsWithAudio = libraryItems.filter { li -> li.checkHasTracks() }
|
||||||
|
|
||||||
cachedLibrarySeriesItem[libraryId]!![seriesId] = libraryItemsWithAudio
|
val sortedLibraryItemsWithAudio = sortSeriesBooks(libraryItemsWithAudio)
|
||||||
|
cachedLibrarySeriesItem[libraryId]!![seriesId] = sortedLibraryItemsWithAudio
|
||||||
|
|
||||||
libraryItemsWithAudio.forEach { libraryItem ->
|
sortedLibraryItemsWithAudio.forEach { libraryItem ->
|
||||||
if (serverLibraryItems.find { li -> li.id == libraryItem.id } == null) {
|
if (serverLibraryItems.find { li -> li.id == libraryItem.id } == null) {
|
||||||
serverLibraryItems.add(libraryItem)
|
serverLibraryItems.add(libraryItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cb(libraryItemsWithAudio)
|
cb(sortedLibraryItemsWithAudio)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -228,8 +241,8 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
apiHandler.getLibraryAuthors(libraryId) { authorItems ->
|
apiHandler.getLibraryAuthors(libraryId) { authorItems ->
|
||||||
Log.d(tag, "Authors with books loaded from server | Library $libraryId ")
|
Log.d(tag, "Authors with books loaded from server | Library $libraryId ")
|
||||||
// TO-DO: This check won't ensure that there is audiobooks. Current API won't offer ability to do so
|
// TO-DO: This check won't ensure that there is audiobooks. Current API won't offer ability to do so
|
||||||
val authorItemsWithBooks = authorItems.filter { li -> li.bookCount != null && li.bookCount!! > 0 }
|
var authorItemsWithBooks = authorItems.filter { li -> li.bookCount != null && li.bookCount!! > 0 }
|
||||||
|
authorItemsWithBooks = authorItemsWithBooks.sortedBy { it.name }
|
||||||
// Ensure that there is map for library
|
// Ensure that there is map for library
|
||||||
cachedLibraryAuthors[libraryId] = mutableMapOf()
|
cachedLibraryAuthors[libraryId] = mutableMapOf()
|
||||||
// Cache authors
|
// Cache authors
|
||||||
@@ -314,15 +327,16 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
Log.d(tag, "Using author name: $authorName")
|
Log.d(tag, "Using author name: $authorName")
|
||||||
val libraryItemsFromAuthorWithAudio = libraryItemsWithAudio.filter { li -> li.authorName.indexOf(authorName, ignoreCase = true) >= 0 }
|
val libraryItemsFromAuthorWithAudio = libraryItemsWithAudio.filter { li -> li.authorName.indexOf(authorName, ignoreCase = true) >= 0 }
|
||||||
|
|
||||||
cachedLibraryAuthorSeriesItems[libraryId]!![authorId] = libraryItemsFromAuthorWithAudio
|
val sortedLibraryItemsWithAudio = sortSeriesBooks(libraryItemsFromAuthorWithAudio)
|
||||||
|
cachedLibraryAuthorSeriesItems[libraryId]!![authorId] = sortedLibraryItemsWithAudio
|
||||||
|
|
||||||
libraryItemsFromAuthorWithAudio.forEach { libraryItem ->
|
sortedLibraryItemsWithAudio.forEach { libraryItem ->
|
||||||
if (serverLibraryItems.find { li -> li.id == libraryItem.id } == null) {
|
if (serverLibraryItems.find { li -> li.id == libraryItem.id } == null) {
|
||||||
serverLibraryItems.add(libraryItem)
|
serverLibraryItems.add(libraryItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cb(libraryItemsFromAuthorWithAudio)
|
cb(sortedLibraryItemsWithAudio)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -443,9 +457,15 @@ class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
if (serverLibraries.isNotEmpty()) {
|
if (serverLibraries.isNotEmpty()) {
|
||||||
cb(serverLibraries)
|
cb(serverLibraries)
|
||||||
} else {
|
} else {
|
||||||
apiHandler.getLibraries {
|
apiHandler.getLibraries { loadedLibraries ->
|
||||||
serverLibraries = it
|
serverLibraries = loadedLibraries.map { library ->
|
||||||
cb(it)
|
apiHandler.getLibraryStats(library.id) { libraryStats ->
|
||||||
|
Log.d(tag, "Library stats for library ${library.id} | $libraryStats")
|
||||||
|
library.stats = libraryStats
|
||||||
|
}
|
||||||
|
library
|
||||||
|
}
|
||||||
|
cb(serverLibraries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class BrowseTree(
|
|||||||
rootList += librariesMetadata
|
rootList += librariesMetadata
|
||||||
|
|
||||||
libraries.forEach { library ->
|
libraries.forEach { library ->
|
||||||
|
if (library.stats?.numAudioTracks == 0) return@forEach
|
||||||
val libraryMediaMetadata = library.getMediaMetadata()
|
val libraryMediaMetadata = library.getMediaMetadata()
|
||||||
val children = mediaIdToChildren[LIBRARIES_ROOT] ?: mutableListOf()
|
val children = mediaIdToChildren[LIBRARIES_ROOT] ?: mutableListOf()
|
||||||
children += libraryMediaMetadata
|
children += libraryMediaMetadata
|
||||||
|
|||||||
+49
-27
@@ -1099,30 +1099,44 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
}
|
}
|
||||||
} else if (mediaManager.getIsLibrary(parentMediaId)) { // Load library items for library
|
} else if (mediaManager.getIsLibrary(parentMediaId)) { // Load library items for library
|
||||||
Log.d(tag, "Loading items for library $parentMediaId")
|
Log.d(tag, "Loading items for library $parentMediaId")
|
||||||
val children = mutableListOf(
|
val selectedLibrary = mediaManager.getLibrary(parentMediaId)
|
||||||
MediaBrowserCompat.MediaItem(
|
if (selectedLibrary?.mediaType == "podcast") { // Podcasts are browseable
|
||||||
MediaDescriptionCompat.Builder()
|
mediaManager.loadLibraryItemsWithAudio(parentMediaId) { libraryItems ->
|
||||||
.setTitle("Library")
|
val children = libraryItems.map { libraryItem ->
|
||||||
.setMediaId("__LIBRARY__${parentMediaId}__AUTHORS")
|
val mediaDescription = libraryItem.getMediaDescription(null, ctx)
|
||||||
.build(),
|
MediaBrowserCompat.MediaItem(
|
||||||
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
mediaDescription,
|
||||||
),
|
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||||
MediaBrowserCompat.MediaItem(
|
)
|
||||||
MediaDescriptionCompat.Builder()
|
}
|
||||||
.setTitle("Series")
|
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||||
.setMediaId("__LIBRARY__${parentMediaId}__SERIES_LIST")
|
}
|
||||||
.build(),
|
} else {
|
||||||
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
val children = mutableListOf(
|
||||||
),
|
MediaBrowserCompat.MediaItem(
|
||||||
MediaBrowserCompat.MediaItem(
|
MediaDescriptionCompat.Builder()
|
||||||
MediaDescriptionCompat.Builder()
|
.setTitle("Library")
|
||||||
.setTitle("Collections")
|
.setMediaId("__LIBRARY__${parentMediaId}__AUTHORS")
|
||||||
.setMediaId("__LIBRARY__${parentMediaId}__COLLECTIONS")
|
.build(),
|
||||||
.build(),
|
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||||
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
),
|
||||||
|
MediaBrowserCompat.MediaItem(
|
||||||
|
MediaDescriptionCompat.Builder()
|
||||||
|
.setTitle("Series")
|
||||||
|
.setMediaId("__LIBRARY__${parentMediaId}__SERIES_LIST")
|
||||||
|
.build(),
|
||||||
|
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||||
|
),
|
||||||
|
MediaBrowserCompat.MediaItem(
|
||||||
|
MediaDescriptionCompat.Builder()
|
||||||
|
.setTitle("Collections")
|
||||||
|
.setMediaId("__LIBRARY__${parentMediaId}__COLLECTIONS")
|
||||||
|
.build(),
|
||||||
|
MediaBrowserCompat.MediaItem.FLAG_BROWSABLE
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
}
|
||||||
} else if (parentMediaId.startsWith("__LIBRARY__")) {
|
} else if (parentMediaId.startsWith("__LIBRARY__")) {
|
||||||
Log.d(tag, "Browsing library $parentMediaId")
|
Log.d(tag, "Browsing library $parentMediaId")
|
||||||
val mediaIdParts = parentMediaId.split("__")
|
val mediaIdParts = parentMediaId.split("__")
|
||||||
@@ -1201,12 +1215,16 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
mediaIdParts[4]
|
mediaIdParts[4]
|
||||||
) { libraryItems ->
|
) { libraryItems ->
|
||||||
Log.d(tag, "Received ${libraryItems.size} library items")
|
Log.d(tag, "Received ${libraryItems.size} library items")
|
||||||
val children = libraryItems.map { libraryItem ->
|
var items = libraryItems
|
||||||
|
if (DeviceManager.deviceData.deviceSettings!!.androidAutoBrowseSeriesSequenceOrder === AndroidAutoBrowseSeriesSequenceOrderSetting.DESC) {
|
||||||
|
items = libraryItems.reversed()
|
||||||
|
}
|
||||||
|
val children = items.map { libraryItem ->
|
||||||
val progress =
|
val progress =
|
||||||
mediaManager.serverUserMediaProgress.find { it.libraryItemId == libraryItem.id }
|
mediaManager.serverUserMediaProgress.find { it.libraryItemId == libraryItem.id }
|
||||||
val localLibraryItem = DeviceManager.dbManager.getLocalLibraryItemByLId(libraryItem.id)
|
val localLibraryItem = DeviceManager.dbManager.getLocalLibraryItemByLId(libraryItem.id)
|
||||||
libraryItem.localLibraryItemId = localLibraryItem?.id
|
libraryItem.localLibraryItemId = localLibraryItem?.id
|
||||||
val description = libraryItem.getMediaDescription(progress, ctx)
|
val description = libraryItem.getMediaDescription(progress, ctx, null, true)
|
||||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||||
}
|
}
|
||||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||||
@@ -1279,11 +1297,15 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
}
|
}
|
||||||
} else if (mediaIdParts[3] == "AUTHOR_SERIES") {
|
} else if (mediaIdParts[3] == "AUTHOR_SERIES") {
|
||||||
mediaManager.loadAuthorSeriesBooksWithAudio(mediaIdParts[2], mediaIdParts[4], mediaIdParts[5]) { libraryItems ->
|
mediaManager.loadAuthorSeriesBooksWithAudio(mediaIdParts[2], mediaIdParts[4], mediaIdParts[5]) { libraryItems ->
|
||||||
val children = libraryItems.map { libraryItem ->
|
var items = libraryItems
|
||||||
|
if (DeviceManager.deviceData.deviceSettings!!.androidAutoBrowseSeriesSequenceOrder === AndroidAutoBrowseSeriesSequenceOrderSetting.DESC) {
|
||||||
|
items = libraryItems.reversed()
|
||||||
|
}
|
||||||
|
val children = items.map { libraryItem ->
|
||||||
val progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == libraryItem.id }
|
val progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == libraryItem.id }
|
||||||
val localLibraryItem = DeviceManager.dbManager.getLocalLibraryItemByLId(libraryItem.id)
|
val localLibraryItem = DeviceManager.dbManager.getLocalLibraryItemByLId(libraryItem.id)
|
||||||
libraryItem.localLibraryItemId = localLibraryItem?.id
|
libraryItem.localLibraryItemId = localLibraryItem?.id
|
||||||
val description = libraryItem.getMediaDescription(progress, ctx)
|
val description = libraryItem.getMediaDescription(progress, ctx, null, true)
|
||||||
if (libraryItem.collapsedSeries != null) {
|
if (libraryItem.collapsedSeries != null) {
|
||||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -150,6 +150,18 @@ class ApiHandler(var ctx:Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getLibraryStats(libraryItemId:String, cb: (LibraryStats?) -> Unit) {
|
||||||
|
getRequest("/api/libraries/$libraryItemId/stats", null, null) {
|
||||||
|
if (it.has("error")) {
|
||||||
|
Log.e(tag, it.getString("error") ?: "getLibraryStats Failed")
|
||||||
|
cb(null)
|
||||||
|
} else {
|
||||||
|
val libraryStats = jacksonMapper.readValue<LibraryStats>(it.toString())
|
||||||
|
cb(libraryStats)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getLibraryItem(libraryItemId:String, cb: (LibraryItem?) -> Unit) {
|
fun getLibraryItem(libraryItemId:String, cb: (LibraryItem?) -> Unit) {
|
||||||
getRequest("/api/items/$libraryItemId?expanded=1", null, null) {
|
getRequest("/api/items/$libraryItemId?expanded=1", null, null) {
|
||||||
if (it.has("error")) {
|
if (it.has("error")) {
|
||||||
|
|||||||
+31
-1
@@ -170,6 +170,12 @@
|
|||||||
<ui-text-input type="number" v-model="settings.androidAutoBrowseLimitForGrouping" style="width: 145px; max-width: 145px" @input="androidAutoBrowseLimitForGroupingUpdated" />
|
<ui-text-input type="number" v-model="settings.androidAutoBrowseLimitForGrouping" style="width: 145px; max-width: 145px" @input="androidAutoBrowseLimitForGroupingUpdated" />
|
||||||
<span class="material-icons-outlined ml-2" @click.stop="showInfo('androidAutoBrowseLimitForGrouping')">info</span>
|
<span class="material-icons-outlined ml-2" @click.stop="showInfo('androidAutoBrowseLimitForGrouping')">info</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="py-3 flex items-center">
|
||||||
|
<p class="pr-4 w-36">{{ $strings.LabelAndroidAutoBrowseSeriesSequenceOrder }}</p>
|
||||||
|
<div @click.stop="showAndroidAutoBrowseSeriesSequenceOrderOptions">
|
||||||
|
<ui-text-input :value="androidAutoBrowseSeriesSequenceOrderOption" readonly append-icon="expand_more" style="max-width: 200px" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div v-show="loading" class="w-full h-full absolute top-0 left-0 flex items-center justify-center z-10">
|
<div v-show="loading" class="w-full h-full absolute top-0 left-0 flex items-center justify-center z-10">
|
||||||
@@ -218,7 +224,8 @@ export default {
|
|||||||
streamingUsingCellular: 'ALWAYS',
|
streamingUsingCellular: 'ALWAYS',
|
||||||
androidAutoBrowseForceGrouping: false,
|
androidAutoBrowseForceGrouping: false,
|
||||||
androidAutoBrowseTopLevelLimitForGrouping: 100,
|
androidAutoBrowseTopLevelLimitForGrouping: 100,
|
||||||
androidAutoBrowseLimitForGrouping: 50
|
androidAutoBrowseLimitForGrouping: 50,
|
||||||
|
androidAutoBrowseSeriesSequenceOrder: 'ASC'
|
||||||
},
|
},
|
||||||
theme: 'dark',
|
theme: 'dark',
|
||||||
lockCurrentOrientation: false,
|
lockCurrentOrientation: false,
|
||||||
@@ -323,6 +330,16 @@ export default {
|
|||||||
text: this.$strings.LabelNever,
|
text: this.$strings.LabelNever,
|
||||||
value: 'NEVER'
|
value: 'NEVER'
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
androidAutoBrowseSeriesSequenceOrderItems: [
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelAscending,
|
||||||
|
value: 'ASC'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.$strings.LabelDescending,
|
||||||
|
value: 'DESC'
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -405,6 +422,10 @@ export default {
|
|||||||
const item = this.streamingUsingCellularItems.find((i) => i.value === this.settings.streamingUsingCellular)
|
const item = this.streamingUsingCellularItems.find((i) => i.value === this.settings.streamingUsingCellular)
|
||||||
return item?.text || 'Error'
|
return item?.text || 'Error'
|
||||||
},
|
},
|
||||||
|
androidAutoBrowseSeriesSequenceOrderOption() {
|
||||||
|
const item = this.androidAutoBrowseSeriesSequenceOrderItems.find((i) => i.value === this.settings.androidAutoBrowseSeriesSequenceOrder)
|
||||||
|
return item?.text || 'Error'
|
||||||
|
},
|
||||||
moreMenuItems() {
|
moreMenuItems() {
|
||||||
if (this.moreMenuSetting === 'shakeSensitivity') return this.shakeSensitivityItems
|
if (this.moreMenuSetting === 'shakeSensitivity') return this.shakeSensitivityItems
|
||||||
else if (this.moreMenuSetting === 'hapticFeedback') return this.hapticFeedbackItems
|
else if (this.moreMenuSetting === 'hapticFeedback') return this.hapticFeedbackItems
|
||||||
@@ -412,6 +433,7 @@ export default {
|
|||||||
else if (this.moreMenuSetting === 'theme') return this.themeOptionItems
|
else if (this.moreMenuSetting === 'theme') return this.themeOptionItems
|
||||||
else if (this.moreMenuSetting === 'downloadUsingCellular') return this.downloadUsingCellularItems
|
else if (this.moreMenuSetting === 'downloadUsingCellular') return this.downloadUsingCellularItems
|
||||||
else if (this.moreMenuSetting === 'streamingUsingCellular') return this.streamingUsingCellularItems
|
else if (this.moreMenuSetting === 'streamingUsingCellular') return this.streamingUsingCellularItems
|
||||||
|
else if (this.moreMenuSetting === 'androidAutoBrowseSeriesSequenceOrder') return this.androidAutoBrowseSeriesSequenceOrderItems
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -454,6 +476,10 @@ export default {
|
|||||||
this.moreMenuSetting = 'streamingUsingCellular'
|
this.moreMenuSetting = 'streamingUsingCellular'
|
||||||
this.showMoreMenuDialog = true
|
this.showMoreMenuDialog = true
|
||||||
},
|
},
|
||||||
|
showAndroidAutoBrowseSeriesSequenceOrderOptions() {
|
||||||
|
this.moreMenuSetting = 'androidAutoBrowseSeriesSequenceOrder'
|
||||||
|
this.showMoreMenuDialog = true
|
||||||
|
},
|
||||||
clickMenuAction(action) {
|
clickMenuAction(action) {
|
||||||
this.showMoreMenuDialog = false
|
this.showMoreMenuDialog = false
|
||||||
if (this.moreMenuSetting === 'shakeSensitivity') {
|
if (this.moreMenuSetting === 'shakeSensitivity') {
|
||||||
@@ -474,6 +500,9 @@ export default {
|
|||||||
} else if (this.moreMenuSetting === 'streamingUsingCellular') {
|
} else if (this.moreMenuSetting === 'streamingUsingCellular') {
|
||||||
this.settings.streamingUsingCellular = action
|
this.settings.streamingUsingCellular = action
|
||||||
this.saveSettings()
|
this.saveSettings()
|
||||||
|
} else if (this.moreMenuSetting === 'androidAutoBrowseSeriesSequenceOrder') {
|
||||||
|
this.settings.androidAutoBrowseSeriesSequenceOrder = action
|
||||||
|
this.saveSettings()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
saveTheme(theme) {
|
saveTheme(theme) {
|
||||||
@@ -629,6 +658,7 @@ export default {
|
|||||||
this.settings.androidAutoBrowseForceGrouping = deviceSettings.androidAutoBrowseForceGrouping
|
this.settings.androidAutoBrowseForceGrouping = deviceSettings.androidAutoBrowseForceGrouping
|
||||||
this.settings.androidAutoBrowseTopLevelLimitForGrouping = deviceSettings.androidAutoBrowseTopLevelLimitForGrouping
|
this.settings.androidAutoBrowseTopLevelLimitForGrouping = deviceSettings.androidAutoBrowseTopLevelLimitForGrouping
|
||||||
this.settings.androidAutoBrowseLimitForGrouping = deviceSettings.androidAutoBrowseLimitForGrouping
|
this.settings.androidAutoBrowseLimitForGrouping = deviceSettings.androidAutoBrowseLimitForGrouping
|
||||||
|
this.settings.androidAutoBrowseSeriesSequenceOrder = deviceSettings.androidAutoBrowseSeriesSequenceOrder || 'ASC'
|
||||||
},
|
},
|
||||||
async init() {
|
async init() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
|
|||||||
@@ -85,6 +85,7 @@
|
|||||||
"HeaderTableOfContents": "Table of Contents",
|
"HeaderTableOfContents": "Table of Contents",
|
||||||
"HeaderUserInterfaceSettings": "User Interface Settings",
|
"HeaderUserInterfaceSettings": "User Interface Settings",
|
||||||
"HeaderYourStats": "Your Stats",
|
"HeaderYourStats": "Your Stats",
|
||||||
|
"LabelAscending": "Ascending",
|
||||||
"LabelAddToPlaylist": "Add to Playlist",
|
"LabelAddToPlaylist": "Add to Playlist",
|
||||||
"LabelAdded": "Added",
|
"LabelAdded": "Added",
|
||||||
"LabelAddedAt": "Added At",
|
"LabelAddedAt": "Added At",
|
||||||
@@ -95,6 +96,7 @@
|
|||||||
"LabelAndroidAutoBrowseForceGroupingHelp": "Forces alphabetical drawdown while browsing library and series in Android Auto",
|
"LabelAndroidAutoBrowseForceGroupingHelp": "Forces alphabetical drawdown while browsing library and series in Android Auto",
|
||||||
"LabelAndroidAutoBrowseLimitForGrouping": "Alphabetical drawdown stopitems",
|
"LabelAndroidAutoBrowseLimitForGrouping": "Alphabetical drawdown stopitems",
|
||||||
"LabelAndroidAutoBrowseLimitForGroupingHelp": "Stop alphabetical drawdown when there is less than this amount of items to show",
|
"LabelAndroidAutoBrowseLimitForGroupingHelp": "Stop alphabetical drawdown when there is less than this amount of items to show",
|
||||||
|
"LabelAndroidAutoBrowseSeriesSequenceOrder": "Series books order",
|
||||||
"LabelAndroidAutoBrowseTopLevelLimitForGrouping": "Alphabetical drawdown start items",
|
"LabelAndroidAutoBrowseTopLevelLimitForGrouping": "Alphabetical drawdown start items",
|
||||||
"LabelAndroidAutoBrowseTopLevelLimitForGroupingHelp": "If top-level has more items than this alphabetical drawdown will be used",
|
"LabelAndroidAutoBrowseTopLevelLimitForGroupingHelp": "If top-level has more items than this alphabetical drawdown will be used",
|
||||||
"LabelAskConfirmation": "Ask for confirmation",
|
"LabelAskConfirmation": "Ask for confirmation",
|
||||||
@@ -120,6 +122,7 @@
|
|||||||
"LabelContinueReading": "Continue Reading",
|
"LabelContinueReading": "Continue Reading",
|
||||||
"LabelContinueSeries": "Continue Series",
|
"LabelContinueSeries": "Continue Series",
|
||||||
"LabelCustomTime": "Custom time",
|
"LabelCustomTime": "Custom time",
|
||||||
|
"LabelDescending": "Descending",
|
||||||
"LabelDescription": "Description",
|
"LabelDescription": "Description",
|
||||||
"LabelDisableAudioFadeOut": "Disable audio fade out",
|
"LabelDisableAudioFadeOut": "Disable audio fade out",
|
||||||
"LabelDisableAudioFadeOutHelp": "Audio volume will start decreasing when there is less than 1 minute remaining on the sleep timer. Enable this setting to not fade out.",
|
"LabelDisableAudioFadeOutHelp": "Audio volume will start decreasing when there is less than 1 minute remaining on the sleep timer. Enable this setting to not fade out.",
|
||||||
|
|||||||
Reference in New Issue
Block a user