mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-30 15:17:14 +02:00
Update:Android auto show in progress media items across all libraries #285
This commit is contained in:
@@ -1,69 +1,11 @@
|
|||||||
package com.audiobookshelf.app.data
|
package com.audiobookshelf.app.data
|
||||||
|
|
||||||
import android.net.Uri
|
import android.os.Bundle
|
||||||
|
import android.support.v4.media.MediaDescriptionCompat
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
import android.support.v4.media.MediaMetadataCompat
|
||||||
import com.audiobookshelf.app.R
|
import androidx.media.utils.MediaConstants
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
|
||||||
import com.fasterxml.jackson.annotation.*
|
import com.fasterxml.jackson.annotation.*
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
|
||||||
data class LibraryItem(
|
|
||||||
var id:String,
|
|
||||||
var ino:String,
|
|
||||||
var libraryId:String,
|
|
||||||
var folderId:String,
|
|
||||||
var path:String,
|
|
||||||
var relPath:String,
|
|
||||||
var mtimeMs:Long,
|
|
||||||
var ctimeMs:Long,
|
|
||||||
var birthtimeMs:Long,
|
|
||||||
var addedAt:Long,
|
|
||||||
var updatedAt:Long,
|
|
||||||
var lastScan:Long?,
|
|
||||||
var scanVersion:String?,
|
|
||||||
var isMissing:Boolean,
|
|
||||||
var isInvalid:Boolean,
|
|
||||||
var mediaType:String,
|
|
||||||
var media:MediaType,
|
|
||||||
var libraryFiles:MutableList<LibraryFile>?,
|
|
||||||
var userMediaProgress:MediaProgress? // Only included when requesting library item with progress (for downloads)
|
|
||||||
) : LibraryItemWrapper() {
|
|
||||||
@get:JsonIgnore
|
|
||||||
val title get() = media.metadata.title
|
|
||||||
@get:JsonIgnore
|
|
||||||
val authorName get() = media.metadata.getAuthorDisplayName()
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
fun getCoverUri():Uri {
|
|
||||||
if (media.coverPath == null) {
|
|
||||||
return Uri.parse("android.resource://com.audiobookshelf.app/" + R.drawable.icon)
|
|
||||||
}
|
|
||||||
|
|
||||||
return Uri.parse("${DeviceManager.serverAddress}/api/items/$id/cover?token=${DeviceManager.token}")
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
fun checkHasTracks():Boolean {
|
|
||||||
return if (mediaType == "podcast") {
|
|
||||||
((media as Podcast).numEpisodes ?: 0) > 0
|
|
||||||
} else {
|
|
||||||
((media as Book).numTracks ?: 0) > 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
fun getMediaMetadata(): MediaMetadataCompat {
|
|
||||||
return MediaMetadataCompat.Builder().apply {
|
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
|
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, title)
|
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
|
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, authorName)
|
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, getCoverUri().toString())
|
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, authorName)
|
|
||||||
}.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This auto-detects whether it is a Book or Podcast
|
// This auto-detects whether it is a Book or Podcast
|
||||||
@JsonTypeInfo(use=JsonTypeInfo.Id.DEDUCTION)
|
@JsonTypeInfo(use=JsonTypeInfo.Id.DEDUCTION)
|
||||||
@JsonSubTypes(
|
@JsonSubTypes(
|
||||||
@@ -121,7 +63,7 @@ class Podcast(
|
|||||||
}
|
}
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
override fun addAudioTrack(audioTrack:AudioTrack) {
|
override fun addAudioTrack(audioTrack:AudioTrack) {
|
||||||
val newEpisode = PodcastEpisode("local_" + audioTrack.localFileId,episodes?.size ?: 0 + 1,null,null,audioTrack.title,null,null,null,audioTrack,audioTrack.duration,0, null)
|
val newEpisode = PodcastEpisode("local_" + audioTrack.localFileId,(episodes?.size ?: 0) + 1,null,null,audioTrack.title,null,null,null,audioTrack,audioTrack.duration,0, null)
|
||||||
episodes?.add(newEpisode)
|
episodes?.add(newEpisode)
|
||||||
|
|
||||||
var index = 1
|
var index = 1
|
||||||
@@ -142,7 +84,7 @@ class Podcast(
|
|||||||
}
|
}
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun addEpisode(audioTrack:AudioTrack, episode:PodcastEpisode):PodcastEpisode {
|
fun addEpisode(audioTrack:AudioTrack, episode:PodcastEpisode):PodcastEpisode {
|
||||||
val newEpisode = PodcastEpisode("local_" + episode.id,episodes?.size ?: 0 + 1,episode.episode,episode.episodeType,episode.title,episode.subtitle,episode.description,null,audioTrack,audioTrack.duration,0, episode.id)
|
val newEpisode = PodcastEpisode("local_" + episode.id,(episodes?.size ?: 0) + 1,episode.episode,episode.episodeType,episode.title,episode.subtitle,episode.description,null,audioTrack,audioTrack.duration,0, episode.id)
|
||||||
episodes?.add(newEpisode)
|
episodes?.add(newEpisode)
|
||||||
|
|
||||||
var index = 1
|
var index = 1
|
||||||
@@ -293,24 +235,51 @@ data class PodcastEpisode(
|
|||||||
var serverEpisodeId:String? // For local podcasts to match with server podcasts
|
var serverEpisodeId:String? // For local podcasts to match with server podcasts
|
||||||
) {
|
) {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getMediaMetadata(libraryItem:LibraryItemWrapper): MediaMetadataCompat {
|
fun getMediaDescription(libraryItem:LibraryItemWrapper, progress:MediaProgressWrapper?): MediaDescriptionCompat {
|
||||||
var coverUri:Uri = Uri.EMPTY
|
val coverUri = if(libraryItem is LocalLibraryItem) {
|
||||||
val podcast = if(libraryItem is LocalLibraryItem) {
|
libraryItem.getCoverUri()
|
||||||
coverUri = libraryItem.getCoverUri()
|
|
||||||
libraryItem.media as Podcast
|
|
||||||
} else {
|
} else {
|
||||||
coverUri = (libraryItem as LibraryItem).getCoverUri()
|
(libraryItem as LibraryItem).getCoverUri()
|
||||||
(libraryItem as LibraryItem).media as Podcast
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return MediaMetadataCompat.Builder().apply {
|
val extras = Bundle()
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
|
if (progress != null) {
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, title)
|
if (progress.isFinished) {
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
|
extras.putInt(
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, podcast.metadata.getAuthorDisplayName())
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, coverUri.toString())
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
|
||||||
|
)
|
||||||
}.build()
|
} 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 {
|
||||||
|
extras.putInt(
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
|
||||||
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// return MediaMetadataCompat.Builder().apply {
|
||||||
|
// putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
|
||||||
|
// putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, title)
|
||||||
|
// putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
|
||||||
|
// putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, podcast.metadata.getAuthorDisplayName())
|
||||||
|
// putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, coverUri.toString())
|
||||||
|
//
|
||||||
|
// }.build()
|
||||||
|
val libraryItemDescription = libraryItem.getMediaDescription(null)
|
||||||
|
return MediaDescriptionCompat.Builder()
|
||||||
|
.setMediaId(id)
|
||||||
|
.setTitle(title)
|
||||||
|
.setIconUri(coverUri)
|
||||||
|
.setSubtitle(libraryItemDescription.title)
|
||||||
|
.setExtras(extras)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.audiobookshelf.app.data
|
package com.audiobookshelf.app.data
|
||||||
|
|
||||||
|
import android.support.v4.media.MediaDescriptionCompat
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
import com.fasterxml.jackson.annotation.JsonSubTypes
|
import com.fasterxml.jackson.annotation.JsonSubTypes
|
||||||
@@ -92,7 +93,10 @@ data class LocalFolder(
|
|||||||
JsonSubTypes.Type(LibraryItem::class),
|
JsonSubTypes.Type(LibraryItem::class),
|
||||||
JsonSubTypes.Type(LocalLibraryItem::class)
|
JsonSubTypes.Type(LocalLibraryItem::class)
|
||||||
)
|
)
|
||||||
open class LibraryItemWrapper()
|
open class LibraryItemWrapper(var id:String) {
|
||||||
|
@JsonIgnore
|
||||||
|
open fun getMediaDescription(progress:MediaProgressWrapper?): MediaDescriptionCompat { return MediaDescriptionCompat.Builder().build() }
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
data class DeviceInfo(
|
data class DeviceInfo(
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
Used in Android Auto to represent a podcast episode or an audiobook in progress
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.audiobookshelf.app.data
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||||
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
data class ItemInProgress(
|
||||||
|
val libraryItemWrapper: LibraryItemWrapper,
|
||||||
|
val episode: PodcastEpisode?,
|
||||||
|
val progressLastUpdate: Long,
|
||||||
|
val isLocal: Boolean
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun makeFromServerObject(serverItem: JSONObject):ItemInProgress {
|
||||||
|
val jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||||
|
val libraryItem = jacksonMapper.readValue<LibraryItem>(serverItem.toString())
|
||||||
|
|
||||||
|
var episode:PodcastEpisode? = null
|
||||||
|
if (serverItem.has("recentEpisode")) {
|
||||||
|
episode = jacksonMapper.readValue<PodcastEpisode>(serverItem.get("recentEpisode").toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
val progressLastUpdate:Long = serverItem.getLong("progressLastUpdate")
|
||||||
|
return ItemInProgress(libraryItem, episode, progressLastUpdate, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.audiobookshelf.app.data
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.support.v4.media.MediaDescriptionCompat
|
||||||
|
import androidx.media.utils.MediaConstants
|
||||||
|
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 LibraryItem(
|
||||||
|
id:String,
|
||||||
|
var ino:String,
|
||||||
|
var libraryId:String,
|
||||||
|
var folderId:String,
|
||||||
|
var path:String,
|
||||||
|
var relPath:String,
|
||||||
|
var mtimeMs:Long,
|
||||||
|
var ctimeMs:Long,
|
||||||
|
var birthtimeMs:Long,
|
||||||
|
var addedAt:Long,
|
||||||
|
var updatedAt:Long,
|
||||||
|
var lastScan:Long?,
|
||||||
|
var scanVersion:String?,
|
||||||
|
var isMissing:Boolean,
|
||||||
|
var isInvalid:Boolean,
|
||||||
|
var mediaType:String,
|
||||||
|
var media:MediaType,
|
||||||
|
var libraryFiles:MutableList<LibraryFile>?,
|
||||||
|
var userMediaProgress:MediaProgress? // Only included when requesting library item with progress (for downloads)
|
||||||
|
) : LibraryItemWrapper(id) {
|
||||||
|
@get:JsonIgnore
|
||||||
|
val title get() = media.metadata.title
|
||||||
|
@get:JsonIgnore
|
||||||
|
val authorName get() = media.metadata.getAuthorDisplayName()
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun getCoverUri(): Uri {
|
||||||
|
if (media.coverPath == null) {
|
||||||
|
return Uri.parse("android.resource://com.audiobookshelf.app/" + R.drawable.icon)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Uri.parse("${DeviceManager.serverAddress}/api/items/$id/cover?token=${DeviceManager.token}")
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun checkHasTracks():Boolean {
|
||||||
|
return if (mediaType == "podcast") {
|
||||||
|
((media as Podcast).numEpisodes ?: 0) > 0
|
||||||
|
} else {
|
||||||
|
((media as Book).numTracks ?: 0) > 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
override fun getMediaDescription(progress:MediaProgressWrapper?): MediaDescriptionCompat {
|
||||||
|
val extras = Bundle()
|
||||||
|
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_NOT_PLAYED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return MediaDescriptionCompat.Builder()
|
||||||
|
.setMediaId(id)
|
||||||
|
.setTitle(title)
|
||||||
|
.setIconUri(getCoverUri())
|
||||||
|
.setSubtitle(authorName)
|
||||||
|
.setExtras(extras)
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
package com.audiobookshelf.app.data
|
package com.audiobookshelf.app.data
|
||||||
|
|
||||||
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 android.support.v4.media.MediaMetadataCompat
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.media.utils.MediaConstants
|
import androidx.media.utils.MediaConstants
|
||||||
import com.audiobookshelf.app.R
|
import com.audiobookshelf.app.R
|
||||||
@@ -14,8 +12,8 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
data class LocalLibraryItem(
|
class LocalLibraryItem(
|
||||||
var id:String,
|
id:String,
|
||||||
var folderId:String,
|
var folderId:String,
|
||||||
var basePath:String,
|
var basePath:String,
|
||||||
var absolutePath:String,
|
var absolutePath:String,
|
||||||
@@ -32,7 +30,7 @@ data class LocalLibraryItem(
|
|||||||
var serverAddress:String?,
|
var serverAddress:String?,
|
||||||
var serverUserId:String?,
|
var serverUserId:String?,
|
||||||
var libraryItemId:String?
|
var libraryItemId:String?
|
||||||
) : LibraryItemWrapper() {
|
) : LibraryItemWrapper(id) {
|
||||||
@get:JsonIgnore
|
@get:JsonIgnore
|
||||||
val title get() = media.metadata.title
|
val title get() = media.metadata.title
|
||||||
@get:JsonIgnore
|
@get:JsonIgnore
|
||||||
@@ -98,16 +96,38 @@ data class LocalLibraryItem(
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getMediaMetadata(): MediaMetadataCompat {
|
override fun getMediaDescription(progress:MediaProgressWrapper?): MediaDescriptionCompat {
|
||||||
val coverUri = getCoverUri()
|
val coverUri = getCoverUri()
|
||||||
|
|
||||||
return MediaMetadataCompat.Builder().apply {
|
val extras = Bundle()
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, id)
|
if (progress != null) {
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_TITLE, title)
|
if (progress.isFinished) {
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_TITLE, title)
|
extras.putInt(
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, authorName)
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON_URI, coverUri.toString())
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, authorName)
|
)
|
||||||
}.build()
|
} 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_NOT_PLAYED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return MediaDescriptionCompat.Builder()
|
||||||
|
.setMediaId(id)
|
||||||
|
.setTitle(title)
|
||||||
|
.setIconUri(coverUri)
|
||||||
|
.setSubtitle(authorName)
|
||||||
|
.setExtras(extras)
|
||||||
|
.build()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,12 +2,8 @@ package com.audiobookshelf.app.media
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
|
||||||
import android.support.v4.media.MediaBrowserCompat
|
import android.support.v4.media.MediaBrowserCompat
|
||||||
import android.support.v4.media.MediaDescriptionCompat
|
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.media.utils.MediaConstants
|
|
||||||
import com.audiobookshelf.app.data.*
|
import com.audiobookshelf.app.data.*
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.server.ApiHandler
|
import com.audiobookshelf.app.server.ApiHandler
|
||||||
@@ -26,11 +22,11 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
var selectedLibraryItems = mutableListOf<LibraryItem>()
|
var selectedLibraryItems = mutableListOf<LibraryItem>()
|
||||||
var selectedLibraryId = ""
|
var selectedLibraryId = ""
|
||||||
|
|
||||||
var selectedLibraryItemWrapper:LibraryItemWrapper? = null
|
|
||||||
var selectedPodcast:Podcast? = null
|
var selectedPodcast:Podcast? = null
|
||||||
var selectedLibraryItemId:String? = null
|
var selectedLibraryItemId:String? = null
|
||||||
var serverPodcastEpisodes = listOf<PodcastEpisode>()
|
var podcastEpisodeLibraryItemMap = mutableMapOf<String, LibraryItemWithEpisode>()
|
||||||
var serverLibraryCategories = listOf<LibraryCategory>()
|
var serverLibraryCategories = listOf<LibraryCategory>()
|
||||||
|
var serverItemsInProgress = listOf<ItemInProgress>()
|
||||||
var serverLibraries = listOf<Library>()
|
var serverLibraries = listOf<Library>()
|
||||||
var serverConfigIdUsed:String? = null
|
var serverConfigIdUsed:String? = null
|
||||||
var serverConfigLastPing:Long = 0L
|
var serverConfigLastPing:Long = 0L
|
||||||
@@ -71,7 +67,7 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
val serverConnConfig = if (DeviceManager.isConnectedToServer) DeviceManager.serverConnectionConfig else DeviceManager.deviceData.getLastServerConnectionConfig()
|
val serverConnConfig = if (DeviceManager.isConnectedToServer) DeviceManager.serverConnectionConfig else DeviceManager.deviceData.getLastServerConnectionConfig()
|
||||||
|
|
||||||
if (!DeviceManager.isConnectedToServer || !apiHandler.isOnline() || serverConnConfig == null || serverConnConfig.id !== serverConfigIdUsed) {
|
if (!DeviceManager.isConnectedToServer || !apiHandler.isOnline() || serverConnConfig == null || serverConnConfig.id !== serverConfigIdUsed) {
|
||||||
serverPodcastEpisodes = listOf()
|
podcastEpisodeLibraryItemMap = mutableMapOf()
|
||||||
serverLibraryCategories = listOf()
|
serverLibraryCategories = listOf()
|
||||||
serverLibraries = listOf()
|
serverLibraries = listOf()
|
||||||
serverLibraryItems = mutableListOf()
|
serverLibraryItems = mutableListOf()
|
||||||
@@ -80,13 +76,13 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadLibraryCategories(libraryId:String, cb: (List<LibraryCategory>) -> Unit) {
|
fun loadItemsInProgressForAllLibraries(cb: (List<ItemInProgress>) -> Unit) {
|
||||||
if (serverLibraryCategories.isNotEmpty()) {
|
if (serverItemsInProgress.isNotEmpty()) {
|
||||||
cb(serverLibraryCategories)
|
cb(serverItemsInProgress)
|
||||||
} else {
|
} else {
|
||||||
apiHandler.getLibraryCategories(libraryId) {
|
apiHandler.getAllItemsInProgress { itemsInProgress ->
|
||||||
serverLibraryCategories = it
|
serverItemsInProgress = itemsInProgress
|
||||||
cb(it)
|
cb(serverItemsInProgress)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -129,44 +125,39 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
loadLibraryItem(libraryItemId) { libraryItemWrapper ->
|
loadLibraryItem(libraryItemId) { libraryItemWrapper ->
|
||||||
Log.d(tag, "Loaded Podcast library item $libraryItemWrapper")
|
Log.d(tag, "Loaded Podcast library item $libraryItemWrapper")
|
||||||
|
|
||||||
selectedLibraryItemWrapper = libraryItemWrapper
|
|
||||||
|
|
||||||
libraryItemWrapper?.let {
|
libraryItemWrapper?.let {
|
||||||
if (libraryItemWrapper is LocalLibraryItem) { // Local podcast episodes
|
if (libraryItemWrapper is LocalLibraryItem) { // Local podcast episodes
|
||||||
if (libraryItemWrapper.mediaType != "podcast" || libraryItemWrapper.media.getAudioTracks().isEmpty()) {
|
if (libraryItemWrapper.mediaType != "podcast" || libraryItemWrapper.media.getAudioTracks().isEmpty()) {
|
||||||
serverPodcastEpisodes = listOf()
|
|
||||||
cb(mutableListOf())
|
cb(mutableListOf())
|
||||||
} else {
|
} else {
|
||||||
val podcast = libraryItemWrapper.media as Podcast
|
val podcast = libraryItemWrapper.media as Podcast
|
||||||
serverPodcastEpisodes = podcast.episodes ?: listOf()
|
|
||||||
selectedLibraryItemId = libraryItemWrapper.id
|
selectedLibraryItemId = libraryItemWrapper.id
|
||||||
selectedPodcast = podcast
|
selectedPodcast = podcast
|
||||||
|
|
||||||
val children = podcast.episodes?.map { podcastEpisode ->
|
val children = podcast.episodes?.map { podcastEpisode ->
|
||||||
Log.d(tag, "Local Podcast Episode ${podcastEpisode.title} | ${podcastEpisode.id}")
|
Log.d(tag, "Local Podcast Episode ${podcastEpisode.title} | ${podcastEpisode.id}")
|
||||||
|
|
||||||
val mediaMetadata = podcastEpisode.getMediaMetadata(libraryItemWrapper)
|
|
||||||
val progress = DeviceManager.dbManager.getLocalMediaProgress("${libraryItemWrapper.id}-${podcastEpisode.id}")
|
val progress = DeviceManager.dbManager.getLocalMediaProgress("${libraryItemWrapper.id}-${podcastEpisode.id}")
|
||||||
val description = getMediaDescriptionFromMediaMetadata(mediaMetadata, progress)
|
val description = podcastEpisode.getMediaDescription(libraryItemWrapper, progress)
|
||||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||||
}
|
}
|
||||||
children?.let { cb(children as MutableList) } ?: cb(mutableListOf())
|
children?.let { cb(children as MutableList) } ?: cb(mutableListOf())
|
||||||
}
|
}
|
||||||
} else if (libraryItemWrapper is LibraryItem) { // Server podcast episodes
|
} else if (libraryItemWrapper is LibraryItem) { // Server podcast episodes
|
||||||
if (libraryItemWrapper.mediaType != "podcast" || libraryItemWrapper.media.getAudioTracks().isEmpty()) {
|
if (libraryItemWrapper.mediaType != "podcast" || libraryItemWrapper.media.getAudioTracks().isEmpty()) {
|
||||||
serverPodcastEpisodes = listOf()
|
|
||||||
cb(mutableListOf())
|
cb(mutableListOf())
|
||||||
} else {
|
} else {
|
||||||
val podcast = libraryItemWrapper.media as Podcast
|
val podcast = libraryItemWrapper.media as Podcast
|
||||||
serverPodcastEpisodes = podcast.episodes ?: listOf()
|
podcast.episodes?.forEach { podcastEpisode ->
|
||||||
|
podcastEpisodeLibraryItemMap[podcastEpisode.id] = LibraryItemWithEpisode(libraryItemWrapper, podcastEpisode)
|
||||||
|
}
|
||||||
selectedLibraryItemId = libraryItemWrapper.id
|
selectedLibraryItemId = libraryItemWrapper.id
|
||||||
selectedPodcast = podcast
|
selectedPodcast = podcast
|
||||||
|
|
||||||
val children = podcast.episodes?.map { podcastEpisode ->
|
val children = podcast.episodes?.map { podcastEpisode ->
|
||||||
|
|
||||||
val mediaMetadata = podcastEpisode.getMediaMetadata(libraryItemWrapper)
|
|
||||||
val progress = serverUserMediaProgress.find { it.libraryItemId == libraryItemWrapper.id && it.episodeId == podcastEpisode.id }
|
val progress = serverUserMediaProgress.find { it.libraryItemId == libraryItemWrapper.id && it.episodeId == podcastEpisode.id }
|
||||||
val description = getMediaDescriptionFromMediaMetadata(mediaMetadata, progress)
|
val description = podcastEpisode.getMediaDescription(libraryItemWrapper, progress)
|
||||||
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||||
}
|
}
|
||||||
children?.let { cb(children as MutableList) } ?: cb(mutableListOf())
|
children?.let { cb(children as MutableList) } ?: cb(mutableListOf())
|
||||||
@@ -275,26 +266,8 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Load currently listening category for local items
|
fun loadAndroidAutoItems(cb: () -> Unit) {
|
||||||
fun loadLocalCategory():List<LibraryCategory> {
|
|
||||||
val localBooks = DeviceManager.dbManager.getLocalLibraryItems("book")
|
|
||||||
val localPodcasts = DeviceManager.dbManager.getLocalLibraryItems("podcast")
|
|
||||||
val cats = mutableListOf<LibraryCategory>()
|
|
||||||
if (localBooks.isNotEmpty()) {
|
|
||||||
cats.add(LibraryCategory("local-books", "Local Books", "book", localBooks, true))
|
|
||||||
}
|
|
||||||
if (localPodcasts.isNotEmpty()) {
|
|
||||||
cats.add(LibraryCategory("local-podcasts", "Local Podcasts", "podcast", localPodcasts, true))
|
|
||||||
}
|
|
||||||
return cats
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadAndroidAutoItems(cb: (List<LibraryCategory>) -> Unit) {
|
|
||||||
Log.d(tag, "Load android auto items")
|
Log.d(tag, "Load android auto items")
|
||||||
val cats = mutableListOf<LibraryCategory>()
|
|
||||||
|
|
||||||
val localCategories = loadLocalCategory()
|
|
||||||
cats.addAll(localCategories)
|
|
||||||
|
|
||||||
// Check if any valid server connection if not use locally downloaded books
|
// Check if any valid server connection if not use locally downloaded books
|
||||||
checkSetValidServerConnectionConfig { isConnected ->
|
checkSetValidServerConnectionConfig { isConnected ->
|
||||||
@@ -304,40 +277,29 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
loadLibraries { libraries ->
|
loadLibraries { libraries ->
|
||||||
if (libraries.isEmpty()) {
|
if (libraries.isEmpty()) {
|
||||||
Log.w(tag, "No libraries returned from server request")
|
Log.w(tag, "No libraries returned from server request")
|
||||||
cb(cats) // Return download category only
|
cb()
|
||||||
} else {
|
} else {
|
||||||
val library = libraries[0]
|
val library = libraries[0]
|
||||||
Log.d(tag, "Loading categories for library ${library.name} - ${library.id} - ${library.mediaType}")
|
Log.d(tag, "Loading categories for library ${library.name} - ${library.id} - ${library.mediaType}")
|
||||||
|
|
||||||
loadLibraryCategories(library.id) { libraryCategories ->
|
loadItemsInProgressForAllLibraries { itemsInProgress ->
|
||||||
|
itemsInProgress.forEach {
|
||||||
// Only using book or podcast library categories for now
|
val libraryItem = it.libraryItemWrapper as LibraryItem
|
||||||
libraryCategories.forEach {
|
if (serverLibraryItems.find { li -> li.id == libraryItem.id } == null) {
|
||||||
|
serverLibraryItems.add(libraryItem)
|
||||||
// Add items in continue listening to serverLibraryItems
|
|
||||||
if (it.id == "continue-listening") {
|
|
||||||
it.entities.forEach { libraryItemWrapper ->
|
|
||||||
val libraryItem = libraryItemWrapper as LibraryItem
|
|
||||||
if (serverLibraryItems.find { li -> li.id == libraryItem.id } == null) {
|
|
||||||
serverLibraryItems.add(libraryItem)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (it.episode != null) {
|
||||||
// Log.d(tag, "Found library category ${it.label} with type ${it.type}")
|
podcastEpisodeLibraryItemMap[it.episode.id] = LibraryItemWithEpisode(it.libraryItemWrapper, it.episode)
|
||||||
if (it.type == library.mediaType) {
|
|
||||||
// Log.d(tag, "Using library category ${it.id}")
|
|
||||||
cats.add(it)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cb(cats)
|
cb() // Fully loaded
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else { // Not connected/no internet sent downloaded cats only
|
} else { // Not connected to server
|
||||||
cb(cats)
|
cb()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -355,12 +317,7 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
if (id.startsWith("local")) {
|
if (id.startsWith("local")) {
|
||||||
return DeviceManager.dbManager.getLocalLibraryItemWithEpisode(id)
|
return DeviceManager.dbManager.getLocalLibraryItemWithEpisode(id)
|
||||||
} else {
|
} else {
|
||||||
val podcastEpisode = serverPodcastEpisodes.find { it.id == id }
|
return podcastEpisodeLibraryItemMap[id]
|
||||||
return if (podcastEpisode != null && selectedLibraryItemWrapper != null) {
|
|
||||||
LibraryItemWithEpisode(selectedLibraryItemWrapper!!, podcastEpisode)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,41 +351,6 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getMediaDescriptionFromMediaMetadata(item: MediaMetadataCompat, progress:MediaProgressWrapper?): MediaDescriptionCompat {
|
|
||||||
|
|
||||||
val extras = Bundle()
|
|
||||||
if (progress != null) {
|
|
||||||
Log.d(tag, "Has media progress for ${item.description.title} | ${progress}")
|
|
||||||
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 {
|
|
||||||
Log.d(tag, "No media progress for ${item.description.title} | ${item.description.mediaId}")
|
|
||||||
extras.putInt(
|
|
||||||
MediaConstants.DESCRIPTION_EXTRAS_KEY_COMPLETION_STATUS,
|
|
||||||
MediaConstants.DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return MediaDescriptionCompat.Builder()
|
|
||||||
.setMediaId(item.description.mediaId)
|
|
||||||
.setTitle(item.description.title)
|
|
||||||
.setIconUri(item.description.iconUri)
|
|
||||||
.setSubtitle(item.description.subtitle)
|
|
||||||
.setExtras(extras).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun levenshtein(lhs : CharSequence, rhs : CharSequence) : Int {
|
private fun levenshtein(lhs : CharSequence, rhs : CharSequence) : Int {
|
||||||
val lhsLength = lhs.length + 1
|
val lhsLength = lhs.length + 1
|
||||||
val rhsLength = rhs.length + 1
|
val rhsLength = rhs.length + 1
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import com.audiobookshelf.app.data.*
|
|||||||
|
|
||||||
class BrowseTree(
|
class BrowseTree(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
libraryCategories: List<LibraryCategory>,
|
itemsInProgress: List<ItemInProgress>,
|
||||||
libraries: List<Library>
|
libraries: List<Library>
|
||||||
) {
|
) {
|
||||||
private val mediaIdToChildren = mutableMapOf<String, MutableList<MediaMetadataCompat>>()
|
private val mediaIdToChildren = mutableMapOf<String, MutableList<MediaMetadataCompat>>()
|
||||||
@@ -21,8 +21,7 @@ class BrowseTree(
|
|||||||
* @param drawableId - drawable res id
|
* @param drawableId - drawable res id
|
||||||
* @return - uri
|
* @return - uri
|
||||||
*/
|
*/
|
||||||
fun getUriToDrawable(context: Context,
|
fun getUriToDrawable(@AnyRes drawableId: Int): Uri {
|
||||||
@AnyRes drawableId: Int): Uri {
|
|
||||||
return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE
|
return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE
|
||||||
+ "://" + context.resources.getResourcePackageName(drawableId)
|
+ "://" + context.resources.getResourcePackageName(drawableId)
|
||||||
+ '/' + context.resources.getResourceTypeName(drawableId)
|
+ '/' + context.resources.getResourceTypeName(drawableId)
|
||||||
@@ -32,38 +31,26 @@ class BrowseTree(
|
|||||||
init {
|
init {
|
||||||
val rootList = mediaIdToChildren[AUTO_BROWSE_ROOT] ?: mutableListOf()
|
val rootList = mediaIdToChildren[AUTO_BROWSE_ROOT] ?: mutableListOf()
|
||||||
|
|
||||||
val continueReadingMetadata = MediaMetadataCompat.Builder().apply {
|
val continueListeningMetadata = MediaMetadataCompat.Builder().apply {
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, CONTINUE_ROOT)
|
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, CONTINUE_ROOT)
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Listening")
|
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Listening")
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(context, R.drawable.exo_icon_localaudio).toString())
|
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(R.drawable.exo_icon_localaudio).toString())
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
val downloadsMetadata = MediaMetadataCompat.Builder().apply {
|
val downloadsMetadata = MediaMetadataCompat.Builder().apply {
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, DOWNLOADS_ROOT)
|
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, DOWNLOADS_ROOT)
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Downloads")
|
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Downloads")
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(context, R.drawable.exo_icon_downloaddone).toString())
|
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(R.drawable.exo_icon_downloaddone).toString())
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
val librariesMetadata = MediaMetadataCompat.Builder().apply {
|
val librariesMetadata = MediaMetadataCompat.Builder().apply {
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, LIBRARIES_ROOT)
|
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, LIBRARIES_ROOT)
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Libraries")
|
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Libraries")
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(context, R.drawable.icon_library_folder).toString())
|
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(R.drawable.icon_library_folder).toString())
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
// Server continue Listening cat
|
if (!itemsInProgress.isEmpty()) {
|
||||||
libraryCategories.find { it.id == "continue-listening" }?.let { continueListeningCategory ->
|
rootList += continueListeningMetadata
|
||||||
val continueListeningMediaMetadata = continueListeningCategory.entities.map { liw ->
|
|
||||||
val libraryItem = liw as LibraryItem
|
|
||||||
libraryItem.getMediaMetadata()
|
|
||||||
}
|
|
||||||
if (continueListeningMediaMetadata.isNotEmpty()) {
|
|
||||||
rootList += continueReadingMetadata
|
|
||||||
}
|
|
||||||
continueListeningMediaMetadata.forEach {
|
|
||||||
val children = mediaIdToChildren[CONTINUE_ROOT] ?: mutableListOf()
|
|
||||||
children += it
|
|
||||||
mediaIdToChildren[CONTINUE_ROOT] = children
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (libraries.isNotEmpty()) {
|
if (libraries.isNotEmpty()) {
|
||||||
@@ -78,23 +65,6 @@ class BrowseTree(
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootList += downloadsMetadata
|
rootList += downloadsMetadata
|
||||||
libraryCategories.find { it.id == "local-books" }?.let { localBooksCat ->
|
|
||||||
localBooksCat.entities.forEach { libc ->
|
|
||||||
val libraryItem = libc as LocalLibraryItem
|
|
||||||
val children = mediaIdToChildren[DOWNLOADS_ROOT] ?: mutableListOf()
|
|
||||||
children += libraryItem.getMediaMetadata()
|
|
||||||
mediaIdToChildren[DOWNLOADS_ROOT] = children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
libraryCategories.find { it.id == "local-podcasts" }?.let { localPodcastsCat ->
|
|
||||||
localPodcastsCat.entities.forEach { libc ->
|
|
||||||
val libraryItem = libc as LocalLibraryItem
|
|
||||||
val children = mediaIdToChildren[DOWNLOADS_ROOT] ?: mutableListOf()
|
|
||||||
children += libraryItem.getMediaMetadata()
|
|
||||||
mediaIdToChildren[DOWNLOADS_ROOT] = children
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaIdToChildren[AUTO_BROWSE_ROOT] = rootList
|
mediaIdToChildren[AUTO_BROWSE_ROOT] = rootList
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-30
@@ -795,8 +795,9 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
private val VALID_MEDIA_BROWSERS = mutableListOf("com.audiobookshelf.app", ANDROID_AUTO_PKG_NAME, ANDROID_AUTO_SIMULATOR_PKG_NAME, ANDROID_WEARABLE_PKG_NAME, ANDROID_GSEARCH_PKG_NAME, ANDROID_AUTOMOTIVE_PKG_NAME)
|
private val VALID_MEDIA_BROWSERS = mutableListOf("com.audiobookshelf.app", ANDROID_AUTO_PKG_NAME, ANDROID_AUTO_SIMULATOR_PKG_NAME, ANDROID_WEARABLE_PKG_NAME, ANDROID_GSEARCH_PKG_NAME, ANDROID_AUTOMOTIVE_PKG_NAME)
|
||||||
|
|
||||||
private val AUTO_MEDIA_ROOT = "/"
|
private val AUTO_MEDIA_ROOT = "/"
|
||||||
private val ALL_ROOT = "__ALL__"
|
|
||||||
private val LIBRARIES_ROOT = "__LIBRARIES__"
|
private val LIBRARIES_ROOT = "__LIBRARIES__"
|
||||||
|
private val DOWNLOADS_ROOT = "__DOWNLOADS__"
|
||||||
|
private val CONTINUE_ROOT = "__CONTINUE__"
|
||||||
private lateinit var browseTree:BrowseTree
|
private lateinit var browseTree:BrowseTree
|
||||||
|
|
||||||
|
|
||||||
@@ -839,8 +840,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
override fun onLoadChildren(parentMediaId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
|
override fun onLoadChildren(parentMediaId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
|
||||||
Log.d(tag, "ON LOAD CHILDREN $parentMediaId")
|
Log.d(tag, "ON LOAD CHILDREN $parentMediaId")
|
||||||
|
|
||||||
val flag = if (parentMediaId == AUTO_MEDIA_ROOT || parentMediaId == LIBRARIES_ROOT) MediaBrowserCompat.MediaItem.FLAG_BROWSABLE else MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
|
|
||||||
|
|
||||||
result.detach()
|
result.detach()
|
||||||
|
|
||||||
if (parentMediaId.startsWith("li_") || parentMediaId.startsWith("local_")) { // Show podcast episodes
|
if (parentMediaId.startsWith("li_") || parentMediaId.startsWith("local_")) { // Show podcast episodes
|
||||||
@@ -852,53 +851,67 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
|
|
||||||
mediaManager.loadLibraryItemsWithAudio(parentMediaId) { libraryItems ->
|
mediaManager.loadLibraryItemsWithAudio(parentMediaId) { libraryItems ->
|
||||||
val children = libraryItems.map { libraryItem ->
|
val children = libraryItems.map { libraryItem ->
|
||||||
val libraryItemMediaMetadata = libraryItem.getMediaMetadata()
|
|
||||||
|
|
||||||
if (libraryItem.mediaType == "podcast") { // Podcasts are browseable
|
if (libraryItem.mediaType == "podcast") { // Podcasts are browseable
|
||||||
MediaBrowserCompat.MediaItem(libraryItemMediaMetadata.description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
val mediaDescription = libraryItem.getMediaDescription(null)
|
||||||
|
MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||||
} else {
|
} else {
|
||||||
val progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == libraryItemMediaMetadata.description.mediaId }
|
val progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == libraryItem.id }
|
||||||
val description = mediaManager.getMediaDescriptionFromMediaMetadata(libraryItemMediaMetadata, progress)
|
val description = libraryItem.getMediaDescription(progress)
|
||||||
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>?)
|
||||||
}
|
}
|
||||||
} else if (parentMediaId == "__DOWNLOADS__") { // Load downloads
|
} else if (parentMediaId == DOWNLOADS_ROOT) { // Load downloads
|
||||||
|
|
||||||
val localBooks = DeviceManager.dbManager.getLocalLibraryItems("book")
|
val localBooks = DeviceManager.dbManager.getLocalLibraryItems("book")
|
||||||
val localPodcasts = DeviceManager.dbManager.getLocalLibraryItems("podcast")
|
val localPodcasts = DeviceManager.dbManager.getLocalLibraryItems("podcast")
|
||||||
val localBrowseItems:MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
|
val localBrowseItems:MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
|
||||||
|
|
||||||
localBooks.forEach { localLibraryItem ->
|
localBooks.forEach { localLibraryItem ->
|
||||||
val mediaMetadata = localLibraryItem.getMediaMetadata()
|
val progress = DeviceManager.dbManager.getLocalMediaProgress(localLibraryItem.id)
|
||||||
val progress = DeviceManager.dbManager.getLocalMediaProgress(mediaMetadata.description.mediaId ?: "")
|
val description = localLibraryItem.getMediaDescription(progress)
|
||||||
val description = mediaManager.getMediaDescriptionFromMediaMetadata(mediaMetadata, progress)
|
|
||||||
|
|
||||||
localBrowseItems += MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
localBrowseItems += MediaBrowserCompat.MediaItem(description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
localPodcasts.forEach { localLibraryItem ->
|
localPodcasts.forEach { localLibraryItem ->
|
||||||
val mediaMetadata = localLibraryItem.getMediaMetadata()
|
val mediaDescription = localLibraryItem.getMediaDescription(null)
|
||||||
localBrowseItems += MediaBrowserCompat.MediaItem(mediaMetadata.description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
localBrowseItems += MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
result.sendResult(localBrowseItems)
|
result.sendResult(localBrowseItems)
|
||||||
|
|
||||||
} else { // Load categories
|
} else if (parentMediaId == CONTINUE_ROOT) {
|
||||||
mediaManager.loadAndroidAutoItems { libraryCategories ->
|
val localBrowseItems:MutableList<MediaBrowserCompat.MediaItem> = mutableListOf()
|
||||||
browseTree = BrowseTree(this, libraryCategories, mediaManager.serverLibraries)
|
mediaManager.serverItemsInProgress.forEach { itemInProgress ->
|
||||||
|
val progress: MediaProgressWrapper?
|
||||||
|
val mediaDescription:MediaDescriptionCompat
|
||||||
|
if (itemInProgress.episode != null) {
|
||||||
|
if (itemInProgress.isLocal) {
|
||||||
|
progress = DeviceManager.dbManager.getLocalMediaProgress("${itemInProgress.libraryItemWrapper.id}-${itemInProgress.episode.id}")
|
||||||
|
} else {
|
||||||
|
progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == itemInProgress.libraryItemWrapper.id && it.episodeId == itemInProgress.episode.id }
|
||||||
|
}
|
||||||
|
mediaDescription = itemInProgress.episode.getMediaDescription(itemInProgress.libraryItemWrapper,progress)
|
||||||
|
} else {
|
||||||
|
if (itemInProgress.isLocal) {
|
||||||
|
progress = DeviceManager.dbManager.getLocalMediaProgress(itemInProgress.libraryItemWrapper.id)
|
||||||
|
} else {
|
||||||
|
progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == itemInProgress.libraryItemWrapper.id }
|
||||||
|
}
|
||||||
|
mediaDescription = itemInProgress.libraryItemWrapper.getMediaDescription(progress)
|
||||||
|
}
|
||||||
|
localBrowseItems += MediaBrowserCompat.MediaItem(mediaDescription, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||||
|
}
|
||||||
|
result.sendResult(localBrowseItems)
|
||||||
|
} else {
|
||||||
|
mediaManager.loadAndroidAutoItems {
|
||||||
|
browseTree = BrowseTree(this, mediaManager.serverItemsInProgress, mediaManager.serverLibraries)
|
||||||
|
|
||||||
val children = browseTree[parentMediaId]?.map { item ->
|
val children = browseTree[parentMediaId]?.map { item ->
|
||||||
Log.d(tag, "Loading Browser Media Item ${item.description.title} $flag")
|
Log.d(tag, "Loading Browser Media Item ${item.description.title}")
|
||||||
|
MediaBrowserCompat.MediaItem(item.description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||||
if (flag == MediaBrowserCompat.MediaItem.FLAG_PLAYABLE) {
|
|
||||||
val progress = mediaManager.serverUserMediaProgress.find { it.libraryItemId == item.description.mediaId }
|
|
||||||
val description = mediaManager.getMediaDescriptionFromMediaMetadata(item, progress)
|
|
||||||
MediaBrowserCompat.MediaItem(description, flag)
|
|
||||||
} else {
|
|
||||||
MediaBrowserCompat.MediaItem(item.description, flag)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||||
}
|
}
|
||||||
@@ -907,10 +920,10 @@ 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() { libraryCategories ->
|
mediaManager.loadAndroidAutoItems {
|
||||||
browseTree = BrowseTree(this, libraryCategories, mediaManager.serverLibraries)
|
browseTree = BrowseTree(this, mediaManager.serverItemsInProgress, mediaManager.serverLibraries)
|
||||||
val children = browseTree[ALL_ROOT]?.map { item ->
|
val children = browseTree[LIBRARIES_ROOT]?.map { item ->
|
||||||
MediaBrowserCompat.MediaItem(item.description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
MediaBrowserCompat.MediaItem(item.description, MediaBrowserCompat.MediaItem.FLAG_BROWSABLE)
|
||||||
}
|
}
|
||||||
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
result.sendResult(children as MutableList<MediaBrowserCompat.MediaItem>?)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -189,21 +189,16 @@ class ApiHandler(var ctx:Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLibraryCategories(libraryId:String, cb: (List<LibraryCategory>) -> Unit) {
|
fun getAllItemsInProgress(cb: (List<ItemInProgress>) -> Unit) {
|
||||||
getRequest("/api/libraries/$libraryId/personalized", null, null) {
|
getRequest("/api/me/items-in-progress", null, null) {
|
||||||
val items = mutableListOf<LibraryCategory>()
|
val items = mutableListOf<ItemInProgress>()
|
||||||
if (it.has("value")) {
|
if (it.has("libraryItems")) {
|
||||||
val array = it.getJSONArray("value")
|
val array = it.getJSONArray("libraryItems")
|
||||||
for (i in 0 until array.length()) {
|
for (i in 0 until array.length()) {
|
||||||
val jsobj = array.get(i) as JSONObject
|
val jsobj = array.get(i) as JSONObject
|
||||||
|
|
||||||
val type = jsobj.get("type").toString()
|
val itemInProgress = ItemInProgress.makeFromServerObject(jsobj)
|
||||||
// Only support for podcast and book in android auto
|
items.add(itemInProgress)
|
||||||
if (type == "podcast" || type == "book") {
|
|
||||||
jsobj.put("isLocal", false)
|
|
||||||
val item = jacksonMapper.readValue<LibraryCategory>(jsobj.toString())
|
|
||||||
items.add(item)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cb(items)
|
cb(items)
|
||||||
|
|||||||
Reference in New Issue
Block a user