mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-30 15:17:14 +02:00
Add:Android auto show currently reading
This commit is contained in:
@@ -84,6 +84,7 @@ class Audiobook {
|
|||||||
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getCover().toString())
|
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getCover().toString())
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_ART_URI, getCover().toString())
|
putString(MediaMetadataCompat.METADATA_KEY_ART_URI, getCover().toString())
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, book.authorFL)
|
putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, book.authorFL)
|
||||||
|
|
||||||
// val extras = Bundle()
|
// val extras = Bundle()
|
||||||
// if (isDownloaded) {
|
// if (isDownloaded) {
|
||||||
// extras.putLong(
|
// extras.putLong(
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import com.getcapacitor.JSArray
|
|||||||
import com.getcapacitor.JSObject
|
import com.getcapacitor.JSObject
|
||||||
import com.jeep.plugin.capacitor.capacitordatastoragesqlite.CapacitorDataStorageSqlite
|
import com.jeep.plugin.capacitor.capacitordatastoragesqlite.CapacitorDataStorageSqlite
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
|
import org.json.JSONArray
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class AudiobookManager {
|
class AudiobookManager {
|
||||||
@@ -29,6 +30,7 @@ class AudiobookManager {
|
|||||||
lateinit var localMediaManager:LocalMediaManager
|
lateinit var localMediaManager:LocalMediaManager
|
||||||
|
|
||||||
var audiobooks:MutableList<Audiobook> = mutableListOf()
|
var audiobooks:MutableList<Audiobook> = mutableListOf()
|
||||||
|
var audiobooksInProgress:MutableList<Audiobook> = mutableListOf()
|
||||||
|
|
||||||
constructor(_ctx:Context, _client:OkHttpClient) {
|
constructor(_ctx:Context, _client:OkHttpClient) {
|
||||||
ctx = _ctx
|
ctx = _ctx
|
||||||
@@ -45,6 +47,63 @@ class AudiobookManager {
|
|||||||
Log.d(tag, "SHARED PREF TOKEN $token")
|
Log.d(tag, "SHARED PREF TOKEN $token")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadCategories(cb: (() -> Unit)) {
|
||||||
|
Log.d(tag, "LOAD Categories $serverUrl | $token")
|
||||||
|
var url = "$serverUrl/api/libraries/main/categories"
|
||||||
|
val request = Request.Builder()
|
||||||
|
.url(url).addHeader("Authorization", "Bearer $token")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
client.newCall(request).enqueue(object : Callback {
|
||||||
|
override fun onFailure(call: Call, e: IOException) {
|
||||||
|
Log.d(tag, "FAILURE TO CONNECT")
|
||||||
|
e.printStackTrace()
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResponse(call: Call, response: Response) {
|
||||||
|
response.use {
|
||||||
|
if (!response.isSuccessful) throw IOException("Unexpected code $response")
|
||||||
|
|
||||||
|
var bodyString = response.body!!.string()
|
||||||
|
var results = JSONArray(bodyString)
|
||||||
|
// var results = resJson.getJSONArray("results")
|
||||||
|
|
||||||
|
var totalShelves = results.length() - 1
|
||||||
|
Log.d(tag, "Got categories $totalShelves")
|
||||||
|
for (i in 0..totalShelves) {
|
||||||
|
var shelfobj = results.get(i)
|
||||||
|
var jsobj = JSObject(shelfobj.toString())
|
||||||
|
var shelfId = jsobj.getString("id", "")
|
||||||
|
Log.d(tag, "Category shelf id $shelfId")
|
||||||
|
if (shelfId == "continue-reading") {
|
||||||
|
var entities = jsobj.getJSONArray("entities")
|
||||||
|
var totalEntities = entities.length() - 1
|
||||||
|
Log.d(tag, "Shelf total entities $totalEntities")
|
||||||
|
for (y in 0..totalEntities) {
|
||||||
|
var abobj = entities.get(y)
|
||||||
|
Log.d(tag, "Shelf category ab id $y = ${abobj.toString()}")
|
||||||
|
var abjsobj = JSObject(abobj.toString())
|
||||||
|
abjsobj.put("isDownloaded", false)
|
||||||
|
var audiobook = Audiobook(abjsobj, serverUrl, token)
|
||||||
|
if (audiobook.isMissing || audiobook.isInvalid || audiobook.numTracks <= 0) {
|
||||||
|
Log.d(tag, "Not an audiobook or invalid/missing")
|
||||||
|
} else {
|
||||||
|
var audiobookExists = audiobooksInProgress.find { it.id == audiobook.id }
|
||||||
|
if (audiobookExists == null) {
|
||||||
|
audiobooksInProgress.add(audiobook)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Log.d(tag, "${audiobooksInProgress.size} Audiobooks In Progress Loaded")
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
fun loadAudiobooks(cb: (() -> Unit)) {
|
fun loadAudiobooks(cb: (() -> Unit)) {
|
||||||
Log.d(tag, "LOAD AUDIBOOOSK $serverUrl | $token")
|
Log.d(tag, "LOAD AUDIBOOOSK $serverUrl | $token")
|
||||||
if (serverUrl == "" || token == "") {
|
if (serverUrl == "" || token == "") {
|
||||||
@@ -57,6 +116,9 @@ class AudiobookManager {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First load currently reading
|
||||||
|
loadCategories() {
|
||||||
|
// Then load all
|
||||||
var url = "$serverUrl/api/libraries/main/books/all"
|
var url = "$serverUrl/api/libraries/main/books/all"
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(url).addHeader("Authorization", "Bearer $token")
|
.url(url).addHeader("Authorization", "Bearer $token")
|
||||||
@@ -104,6 +166,7 @@ class AudiobookManager {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun load() {
|
fun load() {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import androidx.annotation.AnyRes
|
|||||||
|
|
||||||
class BrowseTree(
|
class BrowseTree(
|
||||||
val context: Context,
|
val context: Context,
|
||||||
|
val audiobooksInProgress: List<Audiobook>,
|
||||||
val audiobooks: List<Audiobook>,
|
val audiobooks: List<Audiobook>,
|
||||||
val localAudio: List<LocalMediaManager.LocalAudio>,
|
val localAudio: List<LocalMediaManager.LocalAudio>,
|
||||||
val recentMediaId: String? = null
|
val recentMediaId: String? = null
|
||||||
@@ -33,6 +34,13 @@ class BrowseTree(
|
|||||||
init {
|
init {
|
||||||
val rootList = mediaIdToChildren[AUTO_BROWSE_ROOT] ?: mutableListOf()
|
val rootList = mediaIdToChildren[AUTO_BROWSE_ROOT] ?: mutableListOf()
|
||||||
|
|
||||||
|
|
||||||
|
val continueReadingMetadata = MediaMetadataCompat.Builder().apply {
|
||||||
|
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, CONTINUE_ROOT)
|
||||||
|
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Reading")
|
||||||
|
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(context, R.drawable.exo_icon_localaudio).toString())
|
||||||
|
}.build()
|
||||||
|
|
||||||
val allMetadata = MediaMetadataCompat.Builder().apply {
|
val allMetadata = MediaMetadataCompat.Builder().apply {
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, ALL_ROOT)
|
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, ALL_ROOT)
|
||||||
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Audiobooks")
|
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Audiobooks")
|
||||||
@@ -42,6 +50,7 @@ class BrowseTree(
|
|||||||
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, resource)
|
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, resource)
|
||||||
}.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")
|
||||||
@@ -54,11 +63,20 @@ class BrowseTree(
|
|||||||
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(context, R.drawable.exo_icon_localaudio).toString())
|
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(context, R.drawable.exo_icon_localaudio).toString())
|
||||||
}.build()
|
}.build()
|
||||||
|
|
||||||
|
if (audiobooksInProgress.isNotEmpty()) {
|
||||||
|
rootList += continueReadingMetadata
|
||||||
|
}
|
||||||
rootList += allMetadata
|
rootList += allMetadata
|
||||||
rootList += downloadsMetadata
|
rootList += downloadsMetadata
|
||||||
rootList += localsMetadata
|
rootList += localsMetadata
|
||||||
mediaIdToChildren[AUTO_BROWSE_ROOT] = rootList
|
mediaIdToChildren[AUTO_BROWSE_ROOT] = rootList
|
||||||
|
|
||||||
|
audiobooksInProgress.forEach { audiobook ->
|
||||||
|
val children = mediaIdToChildren[CONTINUE_ROOT] ?: mutableListOf()
|
||||||
|
children += audiobook.toMediaMetadata()
|
||||||
|
mediaIdToChildren[CONTINUE_ROOT] = children
|
||||||
|
}
|
||||||
|
|
||||||
audiobooks.forEach { audiobook ->
|
audiobooks.forEach { audiobook ->
|
||||||
if (audiobook.isDownloaded) {
|
if (audiobook.isDownloaded) {
|
||||||
val downloadsChildren = mediaIdToChildren[DOWNLOADS_ROOT] ?: mutableListOf()
|
val downloadsChildren = mediaIdToChildren[DOWNLOADS_ROOT] ?: mutableListOf()
|
||||||
@@ -83,5 +101,6 @@ class BrowseTree(
|
|||||||
|
|
||||||
const val AUTO_BROWSE_ROOT = "/"
|
const val AUTO_BROWSE_ROOT = "/"
|
||||||
const val ALL_ROOT = "__ALL__"
|
const val ALL_ROOT = "__ALL__"
|
||||||
|
const val CONTINUE_ROOT = "__CONTINUE__"
|
||||||
const val DOWNLOADS_ROOT = "__DOWNLOADS__"
|
const val DOWNLOADS_ROOT = "__DOWNLOADS__"
|
||||||
const val LOCAL_ROOT = "__LOCAL__"
|
const val LOCAL_ROOT = "__LOCAL__"
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ import com.bumptech.glide.request.RequestOptions
|
|||||||
import com.getcapacitor.JSObject
|
import com.getcapacitor.JSObject
|
||||||
import com.getcapacitor.PluginCall
|
import com.getcapacitor.PluginCall
|
||||||
import com.google.android.exoplayer2.*
|
import com.google.android.exoplayer2.*
|
||||||
|
import com.google.android.exoplayer2.MediaMetadata
|
||||||
import com.google.android.exoplayer2.audio.AudioAttributes
|
import com.google.android.exoplayer2.audio.AudioAttributes
|
||||||
import com.google.android.exoplayer2.ext.cast.CastPlayer
|
import com.google.android.exoplayer2.ext.cast.CastPlayer
|
||||||
import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener
|
import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener
|
||||||
@@ -42,10 +43,10 @@ import com.google.android.exoplayer2.source.hls.HlsMediaSource
|
|||||||
import com.google.android.exoplayer2.ui.PlayerNotificationManager
|
import com.google.android.exoplayer2.ui.PlayerNotificationManager
|
||||||
import com.google.android.exoplayer2.upstream.*
|
import com.google.android.exoplayer2.upstream.*
|
||||||
import com.google.android.exoplayer2.util.MimeTypes
|
import com.google.android.exoplayer2.util.MimeTypes
|
||||||
|
import com.google.android.gms.cast.*
|
||||||
import com.google.android.gms.cast.Cast.MessageReceivedCallback
|
import com.google.android.gms.cast.Cast.MessageReceivedCallback
|
||||||
import com.google.android.gms.cast.CastDevice
|
|
||||||
import com.google.android.gms.cast.CastMediaControlIntent
|
|
||||||
import com.google.android.gms.cast.framework.*
|
import com.google.android.gms.cast.framework.*
|
||||||
|
import com.google.android.gms.cast.framework.media.MediaQueue
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
@@ -303,7 +304,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Log.d(tag, "Media Session Set")
|
Log.d(tag, "Media Session Set")
|
||||||
|
|
||||||
val mediaController = MediaControllerCompat(ctx, mediaSession.sessionToken)
|
val mediaController = MediaControllerCompat(ctx, mediaSession.sessionToken)
|
||||||
@@ -916,10 +916,10 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true)
|
MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true)
|
||||||
extras.putInt(
|
extras.putInt(
|
||||||
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
|
||||||
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
|
||||||
extras.putInt(
|
extras.putInt(
|
||||||
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
|
MediaConstants.DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
|
||||||
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM)
|
MediaConstants.DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM)
|
||||||
|
|
||||||
BrowserRoot(AUTO_MEDIA_ROOT, extras)
|
BrowserRoot(AUTO_MEDIA_ROOT, extras)
|
||||||
}
|
}
|
||||||
@@ -937,7 +937,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
audiobookManager.isLoading = false
|
audiobookManager.isLoading = false
|
||||||
|
|
||||||
Log.d(tag, "LOADED AUDIOBOOKS")
|
Log.d(tag, "LOADED AUDIOBOOKS")
|
||||||
browseTree = BrowseTree(this, audiobookManager.audiobooks, audiobookManager.localMediaManager.localAudioFiles, null)
|
browseTree = BrowseTree(this, audiobookManager.audiobooksInProgress, audiobookManager.audiobooks, audiobookManager.localMediaManager.localAudioFiles, null)
|
||||||
val children = browseTree[parentMediaId]?.map { item ->
|
val children = browseTree[parentMediaId]?.map { item ->
|
||||||
MediaBrowserCompat.MediaItem(item.description, flag)
|
MediaBrowserCompat.MediaItem(item.description, flag)
|
||||||
}
|
}
|
||||||
@@ -990,7 +990,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
audiobookManager.isLoading = false
|
audiobookManager.isLoading = false
|
||||||
|
|
||||||
Log.d(tag, "LOADED AUDIOBOOKS")
|
Log.d(tag, "LOADED AUDIOBOOKS")
|
||||||
browseTree = BrowseTree(this, audiobookManager.audiobooks, audiobookManager.localMediaManager.localAudioFiles, null)
|
browseTree = BrowseTree(this, audiobookManager.audiobooksInProgress, audiobookManager.audiobooks, audiobookManager.localMediaManager.localAudioFiles, null)
|
||||||
val children = browseTree[ALL_ROOT]?.map { item ->
|
val children = browseTree[ALL_ROOT]?.map { item ->
|
||||||
MediaBrowserCompat.MediaItem(item.description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
MediaBrowserCompat.MediaItem(item.description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
|
||||||
}
|
}
|
||||||
@@ -1488,20 +1488,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
|||||||
setSessionAvailabilityListener(CastSessionAvailabilityListener())
|
setSessionAvailabilityListener(CastSessionAvailabilityListener())
|
||||||
addListener(getPlayerListener())
|
addListener(getPlayerListener())
|
||||||
}
|
}
|
||||||
|
|
||||||
currentPlayer = castPlayer as CastPlayer
|
|
||||||
|
|
||||||
if (currentAudiobookStreamData != null) {
|
|
||||||
var mimeType = MimeTypes.AUDIO_AAC
|
|
||||||
|
|
||||||
val mediaItem: MediaItem = MediaItem.Builder()
|
|
||||||
.setUri(currentAudiobookStreamData!!.contentUri)
|
|
||||||
.setMediaId(currentAudiobookStreamData!!.id).setMimeType(mimeType)
|
|
||||||
// .setTag(metadata)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
castPlayer?.setMediaItem(mediaItem, currentAudiobookStreamData!!.startTime)
|
|
||||||
}
|
|
||||||
Log.d(tag, "CAST Cast Player Applied")
|
Log.d(tag, "CAST Cast Player Applied")
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// We wouldn't normally catch the generic `Exception` however
|
// We wouldn't normally catch the generic `Exception` however
|
||||||
|
|||||||
Reference in New Issue
Block a user