Android auto update for downloaded media category, currently listening, and initialize when MainActivity wasnt started

This commit is contained in:
advplyr 2022-04-18 20:23:26 -05:00
parent ae65bed352
commit df1ce6d91f
12 changed files with 302 additions and 96 deletions

View file

@ -70,8 +70,9 @@ class MainActivity : BridgeActivity() {
Log.d(tag, "onCreate")
// var ss = SimpleStorage(this)
// ss.requestFullStorageAccess()
// Grant full storage access for testing
// var ss = SimpleStorage(this)
// ss.requestFullStorageAccess()
var permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
if (permission != PackageManager.PERMISSION_GRANTED) {

View file

@ -26,7 +26,7 @@ data class LibraryItem(
var mediaType:String,
var media:MediaType,
var libraryFiles:MutableList<LibraryFile>?
) {
) : LibraryItemWrapper() {
@get:JsonIgnore
val title get() = media.metadata.title
@get:JsonIgnore

View file

@ -2,6 +2,8 @@ package com.audiobookshelf.app.data
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonSubTypes
import com.fasterxml.jackson.annotation.JsonTypeInfo
import java.util.*
data class ServerConnectionConfig(
@ -18,7 +20,14 @@ data class DeviceData(
var serverConnectionConfigs:MutableList<ServerConnectionConfig>,
var lastServerConnectionConfigId:String?,
var currentLocalPlaybackSession:PlaybackSession? // Stored to open up where left off for local media
)
) {
@JsonIgnore
fun getLastServerConnectionConfig():ServerConnectionConfig? {
return lastServerConnectionConfigId?.let { lsccid ->
return serverConnectionConfigs.find { it.id == lsccid }
}
}
}
@JsonIgnoreProperties(ignoreUnknown = true)
data class LocalFile(
@ -48,3 +57,10 @@ data class LocalFolder(
var storageType:String,
var mediaType:String
)
@JsonTypeInfo(use= JsonTypeInfo.Id.DEDUCTION)
@JsonSubTypes(
JsonSubTypes.Type(LibraryItem::class),
JsonSubTypes.Type(LocalLibraryItem::class)
)
open class LibraryItemWrapper()

View file

@ -0,0 +1,12 @@
package com.audiobookshelf.app.data
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true)
data class LibraryCategory(
var id:String,
var label:String,
var type:String,
var entities:List<LibraryItemWrapper>,
var isLocal:Boolean
)

View file

@ -1,6 +1,9 @@
package com.audiobookshelf.app.data
import android.net.Uri
import android.support.v4.media.MediaMetadataCompat
import android.util.Log
import com.audiobookshelf.app.R
import com.audiobookshelf.app.device.DeviceManager
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@ -25,11 +28,19 @@ data class LocalLibraryItem(
var serverAddress:String?,
var serverUserId:String?,
var libraryItemId:String?
) {
) : LibraryItemWrapper() {
@get:JsonIgnore
val title get() = media.metadata.title
@get:JsonIgnore
val authorName get() = media.metadata.getAuthorDisplayName()
@get:JsonIgnore
val isPodcast get() = mediaType == "podcast"
@JsonIgnore
fun getCoverUri(): Uri {
return if (coverContentUrl != null) Uri.parse(coverContentUrl) else Uri.parse("android.resource://com.audiobookshelf.app/" + R.drawable.icon)
}
@JsonIgnore
fun getDuration():Double {
var total = 0.0
@ -80,4 +91,18 @@ data class LocalLibraryItem(
fun removeLocalFile(localFileId:String) {
localFiles.removeIf { it.id == localFileId }
}
@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_ALBUM_ART_URI, getCoverUri().toString())
putString(MediaMetadataCompat.METADATA_KEY_ART_URI, getCoverUri().toString())
putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, authorName)
}.build()
}
}

View file

@ -6,7 +6,8 @@ import com.audiobookshelf.app.data.DeviceData
import com.audiobookshelf.app.data.ServerConnectionConfig
object DeviceManager {
val tag = "DeviceManager"
const val tag = "DeviceManager"
val dbManager:DbManager = DbManager()
var deviceData:DeviceData = dbManager.getDeviceData()
var serverConnectionConfig: ServerConnectionConfig? = null
@ -15,6 +16,8 @@ object DeviceManager {
val serverAddress get() = serverConnectionConfig?.address ?: ""
val serverUserId get() = serverConnectionConfig?.userId ?: ""
val token get() = serverConnectionConfig?.token ?: ""
val isConnectedToServer get() = serverConnectionConfig != null
val hasLastServerConnectionConfig get() = deviceData.getLastServerConnectionConfig() != null
init {
Log.d(tag, "Device Manager Singleton invoked")

View file

@ -1,43 +1,150 @@
package com.audiobookshelf.app.media
import com.audiobookshelf.app.data.LibraryItem
import com.audiobookshelf.app.data.PlaybackSession
import android.bluetooth.BluetoothClass
import android.content.Context
import android.util.Log
import com.audiobookshelf.app.data.*
import com.audiobookshelf.app.device.DeviceManager
import com.audiobookshelf.app.player.PlayerNotificationService
import com.audiobookshelf.app.server.ApiHandler
import java.util.*
import io.paperdb.Paper
class MediaManager(var apiHandler: ApiHandler, var ctx: Context) {
val tag = "MediaManager"
class MediaManager(var apiHandler: ApiHandler) {
var serverLibraryItems = listOf<LibraryItem>()
var serverLibraryCategories = listOf<LibraryCategory>()
var serverLibraries = listOf<Library>()
fun loadLibraryItems(cb: (List<LibraryItem>) -> Unit) {
fun initializeAndroidAuto() {
Log.d(tag, "Android Auto started when MainActivity was never started - initializing Paper")
Paper.init(ctx)
}
fun loadLibraryCategories(libraryId:String, cb: (List<LibraryCategory>) -> Unit) {
if (serverLibraryCategories.isNotEmpty()) {
cb(serverLibraryCategories)
} else {
apiHandler.getLibraryCategories(libraryId) {
serverLibraryCategories = it
cb(it)
}
}
}
fun loadLibraryItems(libraryId:String, cb: (List<LibraryItem>) -> Unit) {
if (serverLibraryItems.isNotEmpty()) {
cb(serverLibraryItems)
} else {
apiHandler.getLibraryItems("main") { libraryItems ->
apiHandler.getLibraryItems(libraryId) { libraryItems ->
serverLibraryItems = libraryItems
cb(libraryItems)
}
}
}
fun getFirstItem() : LibraryItem? {
return if (serverLibraryItems.isNotEmpty()) serverLibraryItems[0] else null
fun loadLibraries(cb: (List<Library>) -> Unit) {
if (serverLibraries.isNotEmpty()) {
cb(serverLibraries)
} else {
apiHandler.getLibraries {
serverLibraries = it
cb(it)
}
}
}
fun getById(id:String) : LibraryItem? {
return serverLibraryItems.find { it.id == id }
// TODO: Load currently listening category for local items
fun loadLocalCategory():List<LibraryCategory> {
var localBooks = DeviceManager.dbManager.getLocalLibraryItems("book")
var localPodcasts = DeviceManager.dbManager.getLocalLibraryItems("podcast")
var 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 getFromSearch(query:String?) : LibraryItem? {
fun loadAndroidAutoItems(libraryId:String, cb: (List<LibraryCategory>) -> Unit) {
Log.d(tag, "Load android auto items for library id $libraryId")
var cats = mutableListOf<LibraryCategory>()
var localCategories = loadLocalCategory()
cats.addAll(localCategories)
// Connected to server and has internet - load other cats
if (apiHandler.isOnline() && (DeviceManager.isConnectedToServer || DeviceManager.hasLastServerConnectionConfig)) {
if (!DeviceManager.isConnectedToServer) {
DeviceManager.serverConnectionConfig = DeviceManager.deviceData.getLastServerConnectionConfig()
Log.d(tag, "Not connected to server, set last server \"${DeviceManager.serverAddress}\"")
}
loadLibraries { libraries ->
var library = libraries.find { it.id == libraryId } ?: libraries[0]
Log.d(tag, "Loading categories for library ${library.name} - ${library.id} - ${library.mediaType}")
loadLibraryCategories(libraryId) { libraryCategories ->
// Only using book or podcast library categories for now
libraryCategories.forEach {
Log.d(tag, "Found library category ${it.label} with type ${it.type}")
if (it.type == library.mediaType) {
Log.d(tag, "Using library category ${it.id}")
cats.add(it)
}
}
loadLibraryItems(libraryId) { libraryItems ->
var mainCat = LibraryCategory("library", "Library", library.mediaType, libraryItems, false)
cats.add(mainCat)
cb(cats)
}
}
}
} else { // Not connected/no internet sent downloaded cats only
cb(cats)
}
}
fun getFirstItem() : LibraryItemWrapper? {
if (serverLibraryItems.isNotEmpty()) {
return serverLibraryItems[0]
} else {
var localBooks = DeviceManager.dbManager.getLocalLibraryItems("book")
return if (localBooks.isNotEmpty()) return localBooks[0] else null
}
}
fun getById(id:String) : LibraryItemWrapper? {
if (id.startsWith("local")) {
return DeviceManager.dbManager.getLocalLibraryItem(id)
} else {
return serverLibraryItems.find { it.id == id }
}
}
fun getFromSearch(query:String?) : LibraryItemWrapper? {
if (query.isNullOrEmpty()) return getFirstItem()
return serverLibraryItems.find {
it.title.lowercase(Locale.getDefault()).contains(query.lowercase(Locale.getDefault()))
}
}
fun play(libraryItem:LibraryItem, mediaPlayer:String, cb: (PlaybackSession) -> Unit) {
apiHandler.playLibraryItem(libraryItem.id,"",false, mediaPlayer) {
cb(it)
}
fun play(libraryItemWrapper:LibraryItemWrapper, mediaPlayer:String, cb: (PlaybackSession) -> Unit) {
if (libraryItemWrapper is LocalLibraryItem) {
var localLibraryItem = libraryItemWrapper as LocalLibraryItem
cb(localLibraryItem.getPlaybackSession(null))
} else {
var libraryItem = libraryItemWrapper as LibraryItem
apiHandler.playLibraryItem(libraryItem.id,"",false, mediaPlayer) {
cb(it)
}
}
}
private fun levenshtein(lhs : CharSequence, rhs : CharSequence) : Int {

View file

@ -7,14 +7,14 @@ import android.support.v4.media.MediaMetadataCompat
import android.util.Log
import androidx.annotation.AnyRes
import com.audiobookshelf.app.R
import com.audiobookshelf.app.data.LibraryCategory
import com.audiobookshelf.app.data.LibraryItem
import com.audiobookshelf.app.data.LocalLibraryItem
class BrowseTree(
val context: Context,
itemsInProgress: List<LibraryItem>,
itemsMetadata: List<MediaMetadataCompat>,
downloadedMetadata: List<MediaMetadataCompat>
libraryCategories: List<LibraryCategory>
) {
private val mediaIdToChildren = mutableMapOf<String, MutableList<MediaMetadataCompat>>()
@ -37,17 +37,14 @@ class BrowseTree(
val continueReadingMetadata = MediaMetadataCompat.Builder().apply {
putString(MediaMetadataCompat.METADATA_KEY_MEDIA_ID, CONTINUE_ROOT)
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Reading")
putString(MediaMetadataCompat.METADATA_KEY_TITLE, "Listening")
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, "Library Items")
var resource = getUriToDrawable(context, R.drawable.exo_icon_books).toString()
Log.d("BrowseTree", "RESOURCE $resource")
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, resource)
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(context, R.drawable.exo_icon_books).toString())
}.build()
val downloadsMetadata = MediaMetadataCompat.Builder().apply {
@ -56,31 +53,51 @@ class BrowseTree(
putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(context, R.drawable.exo_icon_downloaddone).toString())
}.build()
if (itemsInProgress.isNotEmpty()) {
rootList += continueReadingMetadata
// Server continue Listening cat
libraryCategories.find { it.id == "continue-listening" }?.let { continueListeningCategory ->
var continueListeningMediaMetadata = continueListeningCategory.entities.map { liw ->
var 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
}
}
rootList += allMetadata
rootList += downloadsMetadata
// rootList += localsMetadata
// Server library cat
libraryCategories.find { it.id == "library" }?.let { libraryCategory ->
var libraryMediaMetadata = libraryCategory.entities.map { libc ->
var libraryItem = libc as LibraryItem
libraryItem.getMediaMetadata()
}
libraryMediaMetadata.forEach {
val children = mediaIdToChildren[ALL_ROOT] ?: mutableListOf()
children += it
mediaIdToChildren[ALL_ROOT] = children
}
}
libraryCategories.find { it.id == "local-books" }?.let { localBooksCat ->
var localMediaMetadata = localBooksCat.entities.map { libc ->
var libraryItem = libc as LocalLibraryItem
libraryItem.getMediaMetadata()
}
localMediaMetadata.forEach {
val children = mediaIdToChildren[DOWNLOADS_ROOT] ?: mutableListOf()
children += it
mediaIdToChildren[DOWNLOADS_ROOT] = children
}
}
mediaIdToChildren[AUTO_BROWSE_ROOT] = rootList
itemsInProgress.forEach { libraryItem ->
val children = mediaIdToChildren[CONTINUE_ROOT] ?: mutableListOf()
children += libraryItem.getMediaMetadata()
mediaIdToChildren[CONTINUE_ROOT] = children
}
itemsMetadata.forEach {
val allChildren = mediaIdToChildren[ALL_ROOT] ?: mutableListOf()
allChildren += it
mediaIdToChildren[ALL_ROOT] = allChildren
}
downloadedMetadata.forEach {
val allChildren = mediaIdToChildren[DOWNLOADS_ROOT] ?: mutableListOf()
allChildren += it
mediaIdToChildren[DOWNLOADS_ROOT] = allChildren
}
}
operator fun get(mediaId: String) = mediaIdToChildren[mediaId]
@ -90,4 +107,3 @@ 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__"

View file

@ -10,6 +10,7 @@ import android.support.v4.media.session.MediaSessionCompat
import android.util.Log
import android.view.KeyEvent
import com.audiobookshelf.app.data.LibraryItem
import com.audiobookshelf.app.data.LibraryItemWrapper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
@ -27,7 +28,7 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
Log.d(tag, "ON PREPARE MEDIA SESSION COMPAT")
playerNotificationService.mediaManager.getFirstItem()?.let { li ->
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
Log.d(tag, "About to prepare player with li ${li.title}")
Log.d(tag, "About to prepare player with ${it.displayTitle}")
Handler(Looper.getMainLooper()).post() {
playerNotificationService.preparePlayer(it,true)
}
@ -49,7 +50,7 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
Log.d(tag, "ON PLAY FROM SEARCH $query")
playerNotificationService.mediaManager.getFromSearch(query)?.let { li ->
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
Log.d(tag, "About to prepare player with li ${li.title}")
Log.d(tag, "About to prepare player with ${it.displayTitle}")
Handler(Looper.getMainLooper()).post() {
playerNotificationService.preparePlayer(it,true)
}
@ -88,16 +89,16 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) {
Log.d(tag, "ON PLAY FROM MEDIA ID $mediaId")
var libraryItem: LibraryItem? = null
var libraryItemWrapper: LibraryItemWrapper? = null
if (mediaId.isNullOrEmpty()) {
libraryItem = playerNotificationService.mediaManager.getFirstItem()
libraryItemWrapper = playerNotificationService.mediaManager.getFirstItem()
} else {
libraryItem = playerNotificationService.mediaManager.getById(mediaId)
libraryItemWrapper = playerNotificationService.mediaManager.getById(mediaId)
}
libraryItem?.let { li ->
libraryItemWrapper?.let { li ->
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
Log.d(tag, "About to prepare player with li ${li.title}")
Log.d(tag, "About to prepare player with ${it.displayTitle}")
Handler(Looper.getMainLooper()).post() {
playerNotificationService.preparePlayer(it,true)
}
@ -110,7 +111,7 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
}
fun handleCallMediaButton(intent: Intent): Boolean {
if(Intent.ACTION_MEDIA_BUTTON == intent.getAction()) {
if(Intent.ACTION_MEDIA_BUTTON == intent.action) {
var keyEvent = intent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)
if (keyEvent?.getAction() == KeyEvent.ACTION_UP) {
when (keyEvent?.getKeyCode()) {

View file

@ -8,6 +8,7 @@ import android.os.ResultReceiver
import android.support.v4.media.session.PlaybackStateCompat
import android.util.Log
import com.audiobookshelf.app.data.LibraryItem
import com.audiobookshelf.app.data.LibraryItemWrapper
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
@ -40,10 +41,10 @@ class MediaSessionPlaybackPreparer(var playerNotificationService:PlayerNotificat
override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) {
Log.d(tag, "ON PREPARE FROM MEDIA ID $mediaId $playWhenReady")
var libraryItem: LibraryItem? = playerNotificationService.mediaManager.getById(mediaId)
libraryItem?.let { li ->
var libraryItemWrapper: LibraryItemWrapper? = playerNotificationService.mediaManager.getById(mediaId)
libraryItemWrapper?.let { li ->
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
Log.d(tag, "About to prepare player with li ${li.title}")
Log.d(tag, "About to prepare player with ${it.displayTitle}")
Handler(Looper.getMainLooper()).post() {
playerNotificationService.preparePlayer(it,playWhenReady)
}
@ -55,7 +56,7 @@ class MediaSessionPlaybackPreparer(var playerNotificationService:PlayerNotificat
Log.d(tag, "ON PREPARE FROM SEARCH $query")
playerNotificationService.mediaManager.getFromSearch(query)?.let { li ->
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
Log.d(tag, "About to prepare player with li ${li.title}")
Log.d(tag, "About to prepare player with ${it.displayTitle}")
Handler(Looper.getMainLooper()).post() {
playerNotificationService.preparePlayer(it,playWhenReady)
}

View file

@ -31,6 +31,7 @@ import com.google.android.exoplayer2.source.ProgressiveMediaSource
import com.google.android.exoplayer2.source.hls.HlsMediaSource
import com.google.android.exoplayer2.ui.PlayerNotificationManager
import com.google.android.exoplayer2.upstream.*
import io.paperdb.Paper
import java.util.*
import kotlin.concurrent.schedule
@ -196,7 +197,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
initSensor()
// Initialize media manager
mediaManager = MediaManager(apiHandler)
mediaManager = MediaManager(apiHandler, ctx)
channelId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createNotificationChannel(channelId, channelName)
@ -300,6 +301,13 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
var metadata = playbackSession.getMediaMetadataCompat()
mediaSession.setMetadata(metadata)
var mediaItems = playbackSession.getMediaItems()
if (mediaItems.isEmpty()) {
Log.e(tag, "Invalid playback session no media items to play")
currentPlaybackSession = null
return
}
if (mPlayer == currentPlayer) {
var mediaSource:MediaSource
@ -561,7 +569,12 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
// No further calls will be made to other media browsing methods.
null
} else {
// Flag is used to enable syncing progress natively (normally syncing is handled in webview)
if (!isStarted) {
Log.d(tag, "AA Not yet started")
mediaManager.initializeAndroidAuto()
isStarted = true
}
isAndroidAuto = true
val extras = Bundle()
@ -584,9 +597,9 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
var flag = if (parentMediaId == AUTO_MEDIA_ROOT) MediaBrowserCompat.MediaItem.FLAG_BROWSABLE else MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
result.detach()
mediaManager.loadLibraryItems { libraryItems ->
var itemMediaMetadata:List<MediaMetadataCompat> = libraryItems.map { it.getMediaMetadata() }
browseTree = BrowseTree(this, mutableListOf(), itemMediaMetadata, mutableListOf())
mediaManager.loadAndroidAutoItems("main") { libraryCategories ->
browseTree = BrowseTree(this, libraryCategories)
val children = browseTree[parentMediaId]?.map { item ->
MediaBrowserCompat.MediaItem(item.description, flag)
}
@ -605,9 +618,8 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
override fun onSearch(query: String, extras: Bundle?, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
result.detach()
mediaManager.loadLibraryItems { libraryItems ->
var itemMediaMetadata:List<MediaMetadataCompat> = libraryItems.map { it.getMediaMetadata() }
browseTree = BrowseTree(this, mutableListOf(), itemMediaMetadata, mutableListOf())
mediaManager.loadAndroidAutoItems("main") { libraryCategories ->
browseTree = BrowseTree(this, libraryCategories)
val children = browseTree[ALL_ROOT]?.map { item ->
MediaBrowserCompat.MediaItem(item.description, MediaBrowserCompat.MediaItem.FLAG_PLAYABLE)
}

View file

@ -5,11 +5,7 @@ import android.content.SharedPreferences
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
import android.util.Log
import androidx.core.content.ContextCompat.getSystemService
import com.audiobookshelf.app.data.Library
import com.audiobookshelf.app.data.LibraryItem
import com.audiobookshelf.app.data.LocalMediaProgress
import com.audiobookshelf.app.data.PlaybackSession
import com.audiobookshelf.app.data.*
import com.audiobookshelf.app.device.DeviceManager
import com.audiobookshelf.app.player.MediaProgressSyncData
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@ -21,14 +17,15 @@ import com.getcapacitor.JSObject
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import java.io.IOException
class ApiHandler {
class ApiHandler(var ctx:Context) {
val tag = "ApiHandler"
private var client = OkHttpClient()
var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
var ctx: Context
var storageSharedPreferences: SharedPreferences? = null
data class LocalMediaProgressSyncPayload(val localMediaProgress:List<LocalMediaProgress>)
@ -36,10 +33,6 @@ class ApiHandler {
data class MediaProgressSyncResponsePayload(val numServerProgressUpdates:Int, val localProgressUpdates:List<LocalMediaProgress>)
data class LocalMediaProgressSyncResultsPayload(var numLocalMediaProgressForServer:Int, var numServerProgressUpdates:Int, var numLocalProgressUpdates:Int)
constructor(_ctx: Context) {
ctx = _ctx
}
fun getRequest(endpoint:String, cb: (JSObject) -> Unit) {
val request = Request.Builder()
.url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}")
@ -67,19 +60,17 @@ class ApiHandler {
fun isOnline(): Boolean {
val connectivityManager = ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (connectivityManager != null) {
val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
if (capabilities != null) {
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
Log.i("Internet", "NetworkCapabilities.TRANSPORT_CELLULAR")
return true
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
Log.i("Internet", "NetworkCapabilities.TRANSPORT_WIFI")
return true
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
Log.i("Internet", "NetworkCapabilities.TRANSPORT_ETHERNET")
return true
}
val capabilities = connectivityManager.getNetworkCapabilities(connectivityManager.activeNetwork)
if (capabilities != null) {
if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR)) {
Log.i("Internet", "NetworkCapabilities.TRANSPORT_CELLULAR")
return true
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)) {
Log.i("Internet", "NetworkCapabilities.TRANSPORT_WIFI")
return true
} else if (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)) {
Log.i("Internet", "NetworkCapabilities.TRANSPORT_ETHERNET")
return true
}
}
return false
@ -151,6 +142,27 @@ class ApiHandler {
}
}
fun getLibraryCategories(libraryId:String, cb: (List<LibraryCategory>) -> Unit) {
getRequest("/api/libraries/$libraryId/personalized") {
val items = mutableListOf<LibraryCategory>()
if (it.has("value")) {
var array = it.getJSONArray("value")
for (i in 0 until array.length()) {
var jsobj = array.get(i) as JSONObject
var type = jsobj.get("type").toString()
// Only support for podcast and book in android auto
if (type == "podcast" || type == "book") {
jsobj.put("isLocal", false)
val item = jacksonMapper.readValue<LibraryCategory>(jsobj.toString())
items.add(item)
}
}
}
cb(items)
}
}
fun playLibraryItem(libraryItemId:String, episodeId:String?, forceTranscode:Boolean, mediaPlayer:String, cb: (PlaybackSession) -> Unit) {
var payload = JSObject()
payload.put("mediaPlayer", mediaPlayer)