From a295889a52fd1b123d650517366baf3d967bd2d7 Mon Sep 17 00:00:00 2001 From: advplyr Date: Sat, 11 Dec 2021 19:49:22 -0600 Subject: [PATCH] Add:Android auto show currently reading --- .../java/com/audiobookshelf/app/Audiobook.kt | 1 + .../audiobookshelf/app/AudiobookManager.kt | 131 +++++++++++++----- .../java/com/audiobookshelf/app/BrowseTree.kt | 19 +++ .../app/PlayerNotificationService.kt | 34 ++--- 4 files changed, 127 insertions(+), 58 deletions(-) diff --git a/android/app/src/main/java/com/audiobookshelf/app/Audiobook.kt b/android/app/src/main/java/com/audiobookshelf/app/Audiobook.kt index f924e78a..2ba2c0c9 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/Audiobook.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/Audiobook.kt @@ -84,6 +84,7 @@ class Audiobook { putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getCover().toString()) putString(MediaMetadataCompat.METADATA_KEY_ART_URI, getCover().toString()) putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, book.authorFL) + // val extras = Bundle() // if (isDownloaded) { // extras.putLong( diff --git a/android/app/src/main/java/com/audiobookshelf/app/AudiobookManager.kt b/android/app/src/main/java/com/audiobookshelf/app/AudiobookManager.kt index f8556eda..ef9aaa01 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/AudiobookManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/AudiobookManager.kt @@ -10,6 +10,7 @@ import com.getcapacitor.JSArray import com.getcapacitor.JSObject import com.jeep.plugin.capacitor.capacitordatastoragesqlite.CapacitorDataStorageSqlite import okhttp3.* +import org.json.JSONArray import java.io.IOException class AudiobookManager { @@ -29,6 +30,7 @@ class AudiobookManager { lateinit var localMediaManager:LocalMediaManager var audiobooks:MutableList = mutableListOf() + var audiobooksInProgress:MutableList = mutableListOf() constructor(_ctx:Context, _client:OkHttpClient) { ctx = _ctx @@ -45,19 +47,9 @@ class AudiobookManager { Log.d(tag, "SHARED PREF TOKEN $token") } - fun loadAudiobooks(cb: (() -> Unit)) { - Log.d(tag, "LOAD AUDIBOOOSK $serverUrl | $token") - if (serverUrl == "" || token == "") { - Log.d(tag, "No Server or Token set") - cb() - return - } else if (!serverUrl.startsWith("http")) { - Log.e(tag, "Invalid server url $serverUrl") - cb() - return - } - - var url = "$serverUrl/api/libraries/main/books/all" + 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() @@ -74,37 +66,108 @@ class AudiobookManager { if (!response.isSuccessful) throw IOException("Unexpected code $response") var bodyString = response.body!!.string() - var resJson = JSObject(bodyString) - var results = resJson.getJSONArray("results") + var results = JSONArray(bodyString) +// var results = resJson.getJSONArray("results") - var totalBooks = results.length() - 1 - for (i in 0..totalBooks) { - var abobj = results.get(i) - var jsobj = JSObject(abobj.toString()) - - jsobj.put("isDownloaded", false) - var audiobook = Audiobook(jsobj, serverUrl, token) - - if (audiobook.isMissing || audiobook.isInvalid) { - Log.d(tag, "Audiobook ${audiobook.book.title} is missing or invalid") - } else if (audiobook.numTracks <= 0) { - Log.d(tag, "Audiobook ${audiobook.book.title} has audio tracks") - } else { - var audiobookExists = audiobooks.find { it.id == audiobook.id } - if (audiobookExists == null) { - audiobooks.add(audiobook) - } else { - Log.d(tag, "Audiobook already there from downloaded") + 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, "${audiobooks.size} Audiobooks Loaded") + Log.d(tag, "${audiobooksInProgress.size} Audiobooks In Progress Loaded") cb() } } }) } + fun loadAudiobooks(cb: (() -> Unit)) { + Log.d(tag, "LOAD AUDIBOOOSK $serverUrl | $token") + if (serverUrl == "" || token == "") { + Log.d(tag, "No Server or Token set") + cb() + return + } else if (!serverUrl.startsWith("http")) { + Log.e(tag, "Invalid server url $serverUrl") + cb() + return + } + + // First load currently reading + loadCategories() { + // Then load all + var url = "$serverUrl/api/libraries/main/books/all" + 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 resJson = JSObject(bodyString) + var results = resJson.getJSONArray("results") + + var totalBooks = results.length() - 1 + for (i in 0..totalBooks) { + var abobj = results.get(i) + var jsobj = JSObject(abobj.toString()) + + jsobj.put("isDownloaded", false) + var audiobook = Audiobook(jsobj, serverUrl, token) + + if (audiobook.isMissing || audiobook.isInvalid) { + Log.d(tag, "Audiobook ${audiobook.book.title} is missing or invalid") + } else if (audiobook.numTracks <= 0) { + Log.d(tag, "Audiobook ${audiobook.book.title} has audio tracks") + } else { + var audiobookExists = audiobooks.find { it.id == audiobook.id } + if (audiobookExists == null) { + audiobooks.add(audiobook) + } else { + Log.d(tag, "Audiobook already there from downloaded") + } + } + } + Log.d(tag, "${audiobooks.size} Audiobooks Loaded") + cb() + } + } + }) + } + } + fun load() { isLoading = true hasLoaded = true diff --git a/android/app/src/main/java/com/audiobookshelf/app/BrowseTree.kt b/android/app/src/main/java/com/audiobookshelf/app/BrowseTree.kt index ec6c1c0a..94cf80ca 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/BrowseTree.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/BrowseTree.kt @@ -10,6 +10,7 @@ import androidx.annotation.AnyRes class BrowseTree( val context: Context, + val audiobooksInProgress: List, val audiobooks: List, val localAudio: List, val recentMediaId: String? = null @@ -33,6 +34,13 @@ class BrowseTree( init { 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 { putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, ALL_ROOT) putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Audiobooks") @@ -42,6 +50,7 @@ class BrowseTree( putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, resource) }.build() + val downloadsMetadata = MediaMetadataCompat.Builder().apply { putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, DOWNLOADS_ROOT) 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()) }.build() + if (audiobooksInProgress.isNotEmpty()) { + rootList += continueReadingMetadata + } rootList += allMetadata rootList += downloadsMetadata rootList += localsMetadata mediaIdToChildren[AUTO_BROWSE_ROOT] = rootList + audiobooksInProgress.forEach { audiobook -> + val children = mediaIdToChildren[CONTINUE_ROOT] ?: mutableListOf() + children += audiobook.toMediaMetadata() + mediaIdToChildren[CONTINUE_ROOT] = children + } + audiobooks.forEach { audiobook -> if (audiobook.isDownloaded) { val downloadsChildren = mediaIdToChildren[DOWNLOADS_ROOT] ?: mutableListOf() @@ -83,5 +101,6 @@ class BrowseTree( const val AUTO_BROWSE_ROOT = "/" const val ALL_ROOT = "__ALL__" +const val CONTINUE_ROOT = "__CONTINUE__" const val DOWNLOADS_ROOT = "__DOWNLOADS__" const val LOCAL_ROOT = "__LOCAL__" diff --git a/android/app/src/main/java/com/audiobookshelf/app/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/PlayerNotificationService.kt index 17c427b2..cbe94b3e 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/PlayerNotificationService.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/PlayerNotificationService.kt @@ -31,6 +31,7 @@ import com.bumptech.glide.request.RequestOptions import com.getcapacitor.JSObject import com.getcapacitor.PluginCall import com.google.android.exoplayer2.* +import com.google.android.exoplayer2.MediaMetadata import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.ext.cast.CastPlayer 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.upstream.* 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.CastDevice -import com.google.android.gms.cast.CastMediaControlIntent import com.google.android.gms.cast.framework.* +import com.google.android.gms.cast.framework.media.MediaQueue import kotlinx.coroutines.* import okhttp3.OkHttpClient import org.json.JSONObject @@ -69,7 +70,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { fun onMetadata(metadata: JSObject) fun onPrepare(audiobookId: String, playWhenReady: Boolean) fun onSleepTimerEnded(currentPosition: Long) - fun onSleepTimerSet(sleepTimerEndTime:Long) + fun onSleepTimerSet(sleepTimerEndTime: Long) } private val tag = "PlayerService" @@ -303,7 +304,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { } - Log.d(tag, "Media Session Set") val mediaController = MediaControllerCompat(ctx, mediaSession.sessionToken) @@ -916,10 +916,10 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { MediaConstants.BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED, true) extras.putInt( 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( 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) } @@ -937,7 +937,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { audiobookManager.isLoading = false 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 -> MediaBrowserCompat.MediaItem(item.description, flag) } @@ -990,7 +990,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { audiobookManager.isLoading = false 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 -> MediaBrowserCompat.MediaItem(item.description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE) } @@ -1149,7 +1149,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { if (sleepTimerRunning || sleepTimerFinishedAt > 0L) checkShouldExtendSleepTimer() } - fun increaseSleepTime(time:Long) { + fun increaseSleepTime(time: Long) { Log.d(tag, "Increase Sleep time $time") if (!sleepTimerRunning) return var newSleepEndTime = sleepTimerEndTime + time @@ -1162,7 +1162,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { listener?.onSleepTimerSet(sleepTimerEndTime) } - fun decreaseSleepTime(time:Long) { + fun decreaseSleepTime(time: Long) { Log.d(tag, "Decrease Sleep time $time") if (!sleepTimerRunning) return var newSleepEndTime = sleepTimerEndTime - time @@ -1488,20 +1488,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { setSessionAvailabilityListener(CastSessionAvailabilityListener()) 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") } catch (e: Exception) { // We wouldn't normally catch the generic `Exception` however