Add audiobook support to Android Auto

This commit is contained in:
Niels van Velzen authored and Niels van Velzen committed 2026-05-17 10:49:53 +02:00
1 parent 0055b5a808
commit deabcafd49
7 files changed
+109 -29

No files matched your search

@@ -60,6 +60,12 @@ sealed interface LibraryPageElement {
tag = albumPrimaryImageTag,
).toUri()
parentId != null && parentPrimaryImageTag != null -> api.imageApi.getItemImageUrl(
itemId = requireNotNull(parentId),
imageType = ImageType.PRIMARY,
tag = parentPrimaryImageTag,
).toUri()
else -> null
}
}
@@ -4,6 +4,7 @@ package org.jellyfin.mobile.sessionbrowser
import kotlinx.serialization.Serializable
import kotlinx.serialization.UseSerializers
import org.jellyfin.sdk.model.api.CollectionType
import org.jellyfin.sdk.model.serializer.UUIDSerializer
import java.util.UUID
@@ -22,7 +23,7 @@ sealed interface LibraryRoute {
data class Search(val query: String? = null) : LibraryRoute
@Serializable
data class Library(val libraryId: UUID) : LibraryRoute
data class Library(val libraryId: UUID, val collectionType: CollectionType? = null) : LibraryRoute
@Serializable
data class Albums(val libraryId: UUID, val startLetter: String? = null) : LibraryRoute
@@ -30,6 +31,9 @@ sealed interface LibraryRoute {
@Serializable
data class Album(val albumId: UUID) : LibraryRoute
@Serializable
data class AudioBooks(val libraryId: UUID, val startLetter: String? = null) : LibraryRoute
@Serializable
data class Artists(val libraryId: UUID, val startLetter: String? = null) : LibraryRoute
@@ -22,6 +22,7 @@ import org.jellyfin.mobile.sessionbrowser.page.AlbumLibraryPage
import org.jellyfin.mobile.sessionbrowser.page.AlbumsLibraryPage
import org.jellyfin.mobile.sessionbrowser.page.ArtistLibraryPage
import org.jellyfin.mobile.sessionbrowser.page.ArtistsLibraryPage
import org.jellyfin.mobile.sessionbrowser.page.AudioBooksLibraryPage
import org.jellyfin.mobile.sessionbrowser.page.FavoritesLibraryPage
import org.jellyfin.mobile.sessionbrowser.page.GenreLibraryPage
import org.jellyfin.mobile.sessionbrowser.page.GenresLibraryPage
@@ -54,6 +55,7 @@ class SessionBrowserCallback(
UserViewLibraryPage(context),
AlbumsLibraryPage(api),
AlbumLibraryPage(api),
AudioBooksLibraryPage(api),
ArtistsLibraryPage(api),
ArtistLibraryPage(api),
FavoritesLibraryPage(api),
@@ -0,0 +1,38 @@
package org.jellyfin.mobile.sessionbrowser.page
import org.jellyfin.mobile.sessionbrowser.LibraryItemAction
import org.jellyfin.mobile.sessionbrowser.LibraryPageElement
import org.jellyfin.mobile.sessionbrowser.LibraryRoute
import org.jellyfin.mobile.sessionbrowser.libraryPage
import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.itemsApi
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.ImageType
import org.jellyfin.sdk.model.api.ItemSortBy
val AudioBooksLibraryPage = { api: ApiClient ->
libraryPage<LibraryRoute.AudioBooks>(grid = true) { route, offset, limit ->
if (route.startLetter == null) return@libraryPage createAlphaBrowser { route.copy(startLetter = it) }
val result by api.itemsApi.getItems(
parentId = route.libraryId,
includeItemTypes = listOf(BaseItemKind.AUDIO_BOOK),
sortBy = listOf(ItemSortBy.SORT_NAME),
recursive = true,
imageTypeLimit = 1,
enableImageTypes = listOf(ImageType.PRIMARY),
nameLessThan = if (route.startLetter == ALPHA_BROWSER_OTHER) ALPHA_BROWSER_LETTERS[0].toString() else null,
nameStartsWith = if (route.startLetter == ALPHA_BROWSER_OTHER) null else route.startLetter,
startIndex = offset,
limit = limit,
)
result.items.map {
LibraryPageElement.baseItem(
api = api,
item = it,
action = LibraryItemAction.Play(it),
)
}
}
}
@@ -8,6 +8,8 @@ import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.userViewsApi
import org.jellyfin.sdk.model.api.CollectionType
private val collectionTypes = setOf(CollectionType.MUSIC, CollectionType.BOOKS)
/**
* Root library page that returns the available libraries (user views) for Android Auto playback.
* Note that this should ideally not exceed 4 items.
@@ -17,7 +19,7 @@ val RootLibraryPage = { api: ApiClient ->
val userViews by api.userViewsApi.getUserViews()
userViews
.items
.filter { it.collectionType == CollectionType.MUSIC }
.filter { collectionTypes.contains(it.collectionType) }
.drop(offset)
.take(limit)
.map {
@@ -25,7 +27,7 @@ val RootLibraryPage = { api: ApiClient ->
api = api,
item = it,
image = null,
action = LibraryItemAction.Navigate(LibraryRoute.Library(it.id)),
action = LibraryItemAction.Navigate(LibraryRoute.Library(it.id, it.collectionType)),
)
}
}
@@ -6,39 +6,66 @@ import org.jellyfin.mobile.sessionbrowser.LibraryItemAction
import org.jellyfin.mobile.sessionbrowser.LibraryPageElement
import org.jellyfin.mobile.sessionbrowser.LibraryRoute
import org.jellyfin.mobile.sessionbrowser.libraryPage
import org.jellyfin.sdk.model.api.CollectionType
val UserViewLibraryPage = { context: Context ->
libraryPage<LibraryRoute.Library> { route, offset, limit ->
listOf(
LibraryPageElement.Item(
title = context.getString(R.string.media_service_car_section_albums),
action = LibraryItemAction.Navigate(LibraryRoute.Albums(route.libraryId)),
),
buildList {
val isMusic = route.collectionType == CollectionType.MUSIC
val isBooks = route.collectionType == CollectionType.BOOKS
LibraryPageElement.Item(
title = context.getString(R.string.media_service_car_section_artists),
action = LibraryItemAction.Navigate(LibraryRoute.Artists(route.libraryId)),
),
if (isMusic) {
add(
LibraryPageElement.Item(
title = context.getString(R.string.media_service_car_section_albums),
action = LibraryItemAction.Navigate(LibraryRoute.Albums(route.libraryId)),
),
)
LibraryPageElement.Item(
title = context.getString(R.string.media_service_car_section_favorites),
action = LibraryItemAction.Navigate(LibraryRoute.Favorites(route.libraryId)),
),
add(
LibraryPageElement.Item(
title = context.getString(R.string.media_service_car_section_artists),
action = LibraryItemAction.Navigate(LibraryRoute.Artists(route.libraryId)),
),
)
}
LibraryPageElement.Item(
title = context.getString(R.string.media_service_car_section_genres),
action = LibraryItemAction.Navigate(LibraryRoute.Genres(route.libraryId)),
),
if (isBooks) {
add(
LibraryPageElement.Item(
title = context.getString(R.string.media_service_car_section_audiobooks),
action = LibraryItemAction.Navigate(LibraryRoute.AudioBooks(route.libraryId)),
),
)
}
LibraryPageElement.Item(
title = context.getString(R.string.media_service_car_section_playlists),
action = LibraryItemAction.Navigate(LibraryRoute.Playlists(route.libraryId)),
),
add(
LibraryPageElement.Item(
title = context.getString(R.string.media_service_car_section_favorites),
action = LibraryItemAction.Navigate(LibraryRoute.Favorites(route.libraryId)),
),
)
LibraryPageElement.Item(
title = context.getString(R.string.media_service_car_section_recents),
action = LibraryItemAction.Navigate(LibraryRoute.Recent(route.libraryId)),
),
).drop(offset).take(limit)
add(
LibraryPageElement.Item(
title = context.getString(R.string.media_service_car_section_genres),
action = LibraryItemAction.Navigate(LibraryRoute.Genres(route.libraryId)),
),
)
add(
LibraryPageElement.Item(
title = context.getString(R.string.media_service_car_section_playlists),
action = LibraryItemAction.Navigate(LibraryRoute.Playlists(route.libraryId)),
),
)
add(
LibraryPageElement.Item(
title = context.getString(R.string.media_service_car_section_recents),
action = LibraryItemAction.Navigate(LibraryRoute.Recent(route.libraryId)),
),
)
}.drop(offset).take(limit)
}
}
+1
View File
@@ -50,6 +50,7 @@
<string name="media_service_car_section_recents">Recently played</string>
<string name="media_service_car_section_favorites">Favorites</string>
<string name="media_service_car_section_albums">Albums</string>
<string name="media_service_car_section_audiobooks">Audiobooks</string>
<string name="media_service_car_section_artists">Artists</string>
<string name="media_service_car_section_songs">Songs</string>
<string name="media_service_car_section_genres">Genres</string>