Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added better library browsing for Android Auto #1322

Merged
merged 16 commits into from
Jan 11, 2025
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package com.audiobookshelf.app.data

import android.content.Context
import android.os.Bundle
import android.support.v4.media.MediaDescriptionCompat
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties

@JsonIgnoreProperties(ignoreUnknown = true)
class CollapsedSeries(
id:String,
var libraryId:String?,
var name:String,
//var nameIgnorePrefix:String,
var sequence:String?,
var libraryItemIds:MutableList<String>
) : LibraryItemWrapper(id) {
@get:JsonIgnore
val title get() = name
@get:JsonIgnore
val numBooks get() = libraryItemIds.size

@JsonIgnore
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't tell if this function is being used anywhere. I was looking because I was checking if __SERIES__ was intentionally different than the mediaId used in the LibraryItem.getMediaDescription

val extras = Bundle()

val mediaId = "__LIBRARY__${libraryId}__SERIE__${id}"
return MediaDescriptionCompat.Builder()
.setMediaId(mediaId)
.setTitle(title)
//.setIconUri(getCoverUri())
.setSubtitle("${numBooks} books")
.setExtras(extras)
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ data class DeviceSettings(
var disableSleepTimerResetFeedback: Boolean,
var languageCode: String,
var downloadUsingCellular: DownloadUsingCellularSetting,
var streamingUsingCellular: StreamingUsingCellularSetting
var streamingUsingCellular: StreamingUsingCellularSetting,
var androidAutoBrowseForceGrouping: Boolean,
var androidAutoBrowseTopLevelLimitForGrouping: Int,
var androidAutoBrowseLimitForGrouping: Int
) {
companion object {
// Static method to get default device settings
Expand All @@ -159,7 +162,10 @@ data class DeviceSettings(
disableSleepTimerResetFeedback = false,
languageCode = "en-us",
downloadUsingCellular = DownloadUsingCellularSetting.ALWAYS,
streamingUsingCellular = StreamingUsingCellularSetting.ALWAYS
streamingUsingCellular = StreamingUsingCellularSetting.ALWAYS,
androidAutoBrowseForceGrouping = false,
androidAutoBrowseTopLevelLimitForGrouping = 100,
androidAutoBrowseLimitForGrouping = 50
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.audiobookshelf.app.data

import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.support.v4.media.MediaDescriptionCompat
import com.audiobookshelf.app.BuildConfig
import com.audiobookshelf.app.R
import com.audiobookshelf.app.device.DeviceManager
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties

@JsonIgnoreProperties(ignoreUnknown = true)
class LibraryAuthorItem(
id:String,
var libraryId:String,
var name:String,
var lastFirst:String,
var description:String?,
var imagePath:String?,
var addedAt:Long,
var updatedAt:Long,
var numBooks:Int?,
var libraryItems:MutableList<LibraryItem>?,
var series:MutableList<LibrarySeriesItem>?
) : LibraryItemWrapper(id) {
@get:JsonIgnore
val title get() = name

@get:JsonIgnore
val bookCount get() = if (numBooks != null) numBooks else libraryItems!!.size

@JsonIgnore
fun getPortraitUri(): Uri {
if (imagePath == null) {
return Uri.parse("android.resource://${BuildConfig.APPLICATION_ID}/" + R.drawable.md_account_outline)
}

return Uri.parse("${DeviceManager.serverAddress}/api/authors/$id/image?token=${DeviceManager.token}")
}

@JsonIgnore
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
val extras = Bundle()

val mediaId = "__LIBRARY__${libraryId}__AUTHOR__${id}"
return MediaDescriptionCompat.Builder()
.setMediaId(mediaId)
.setTitle(title)
.setIconUri(getPortraitUri())
.setSubtitle("${bookCount} books")
.setExtras(extras)
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.audiobookshelf.app.data

import android.content.Context
import android.os.Bundle
import android.support.v4.media.MediaDescriptionCompat
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties

@JsonIgnoreProperties(ignoreUnknown = true)
class LibraryCollection(
id:String,
var libraryId:String,
var name:String,
//var userId:String?,
var description:String?,
var books:MutableList<LibraryItem>?,
) : LibraryItemWrapper(id) {
@get:JsonIgnore
val title get() = name

@get:JsonIgnore
val bookCount get() = if (books != null) books!!.size else 0

@get:JsonIgnore
val audiobookCount get() = books?.filter { book -> (book.media as Book).getAudioTracks().isNotEmpty() }?.size ?: 0

@JsonIgnore
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
val extras = Bundle()

val mediaId = "__LIBRARY__${libraryId}__COLLECTION__${id}"
return MediaDescriptionCompat.Builder()
.setMediaId(mediaId)
.setTitle(title)
//.setIconUri(getCoverUri())
.setSubtitle("${bookCount} books")
.setExtras(extras)
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,17 @@ class LibraryItem(
var media:MediaType,
var libraryFiles:MutableList<LibraryFile>?,
var userMediaProgress:MediaProgress?, // Only included when requesting library item with progress (for downloads)
var collapsedSeries: CollapsedSeries?,
var localLibraryItemId:String? // For Android Auto
) : LibraryItemWrapper(id) {
@get:JsonIgnore
val title get() = media.metadata.title
val title: String
get() {
if (collapsedSeries != null) {
return collapsedSeries!!.title
}
return media.metadata.title
}
@get:JsonIgnore
val authorName get() = media.metadata.getAuthorDisplayName()

Expand All @@ -58,49 +65,76 @@ class LibraryItem(
}

@JsonIgnore
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context, authorId: String?): MediaDescriptionCompat {
val extras = Bundle()

if (localLibraryItemId != null) {
extras.putLong(
MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
MediaDescriptionCompat.STATUS_DOWNLOADED
)
}

if (progress != null) {
if (progress.isFinished) {
extras.putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
if (collapsedSeries == null) {
if (localLibraryItemId != null) {
extras.putLong(
MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
MediaDescriptionCompat.STATUS_DOWNLOADED
)
} else {
}

if (progress != null) {
if (progress.isFinished) {
extras.putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
)
} else {
extras.putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
)
extras.putDouble(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, progress.progress
)
}
} else if (mediaType != "podcast") {
extras.putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
)
extras.putDouble(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE, progress.progress
}

if (media.metadata.explicit) {
extras.putLong(
MediaConstants.METADATA_KEY_IS_EXPLICIT,
MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT
)
}
} else if (mediaType != "podcast") {
extras.putInt(
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
)
}

if (media.metadata.explicit) {
extras.putLong(MediaConstants.METADATA_KEY_IS_EXPLICIT, MediaConstants.METADATA_VALUE_ATTRIBUTE_PRESENT)
val mediaId = if (localLibraryItemId != null) {
localLibraryItemId
} else if (collapsedSeries != null) {
if (authorId != null) {
"__LIBRARY__${libraryId}__AUTHOR_SERIES__${authorId}__${collapsedSeries!!.id}"
} else {
"__LIBRARY__${libraryId}__SERIES__${collapsedSeries!!.id}"
}
} else {
id
}
var subtitle = authorName
if (collapsedSeries != null) {
subtitle = "${collapsedSeries!!.numBooks} books"
}

val mediaId = localLibraryItemId ?: id
return MediaDescriptionCompat.Builder()
.setMediaId(mediaId)
.setTitle(title)
.setIconUri(getCoverUri())
.setSubtitle(authorName)
.setSubtitle(subtitle)
.setExtras(extras)
.build()
}

@JsonIgnore
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
/*
This is needed so Android auto library hierarchy for author series can be implemented
*/
return getMediaDescription(progress, ctx, null)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.audiobookshelf.app.data

import android.content.Context
import android.os.Bundle
import android.support.v4.media.MediaDescriptionCompat
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties

@JsonIgnoreProperties(ignoreUnknown = true)
class LibrarySeriesItem(
id:String,
var libraryId:String,
var name:String,
var description:String?,
var addedAt:Long,
var updatedAt:Long,
var books:MutableList<LibraryItem>?,
var localLibraryItemId:String? // For Android Auto
) : LibraryItemWrapper(id) {
@get:JsonIgnore
val title get() = name

@get:JsonIgnore
val audiobookCount: Int
get() {
if (books == null) return 0
val booksWithAudio = books?.filter { b -> (b.media as Book).numTracks != 0 }
return booksWithAudio?.size ?: 0
}

@JsonIgnore
override fun getMediaDescription(progress:MediaProgressWrapper?, ctx: Context): MediaDescriptionCompat {
val extras = Bundle()

if (localLibraryItemId != null) {
extras.putLong(
MediaDescriptionCompat.EXTRA_DOWNLOAD_STATUS,
MediaDescriptionCompat.STATUS_DOWNLOADED
)
}

val mediaId = "__LIBRARY__${libraryId}__SERIES__${id}"
return MediaDescriptionCompat.Builder()
.setMediaId(mediaId)
.setTitle(title)
//.setIconUri(getCoverUri())
.setSubtitle("$audiobookCount books")
.setExtras(extras)
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,16 @@ object DeviceManager {
if (deviceData.deviceSettings?.streamingUsingCellular == null) {
deviceData.deviceSettings?.streamingUsingCellular = StreamingUsingCellularSetting.ALWAYS
}

if (deviceData.deviceSettings?.androidAutoBrowseForceGrouping == null) {
deviceData.deviceSettings?.androidAutoBrowseForceGrouping = false
}
if (deviceData.deviceSettings?.androidAutoBrowseTopLevelLimitForGrouping == null) {
deviceData.deviceSettings?.androidAutoBrowseTopLevelLimitForGrouping = 100
}
if (deviceData.deviceSettings?.androidAutoBrowseLimitForGrouping == null) {
deviceData.deviceSettings?.androidAutoBrowseLimitForGrouping = 50
}
}

fun getBase64Id(id:String):String {
Expand Down
Loading