Add keep local media progress and playback sessions, update data models to support syncing local progress and support for podcast episodes

This commit is contained in:
advplyr
2022-04-09 12:03:37 -05:00
parent 526fca98b9
commit d9e4469089
23 changed files with 295 additions and 118 deletions
@@ -76,10 +76,9 @@ class DbManager {
fun getAllLocalFolders():List<LocalFolder> {
var localFolders:MutableList<LocalFolder> = mutableListOf()
Paper.book("localFolders").allKeys.forEach {
var localFolder:LocalFolder? = Paper.book("localFolders").read(it)
if (localFolder != null) {
localFolders.add(localFolder)
Paper.book("localFolders").allKeys.forEach { localFolderId ->
Paper.book("localFolders").read<LocalFolder>(localFolderId)?.let {
localFolders.add(it)
}
}
return localFolders
@@ -103,15 +102,41 @@ class DbManager {
fun getDownloadItems():List<AbsDownloader.DownloadItem> {
var downloadItems:MutableList<AbsDownloader.DownloadItem> = mutableListOf()
Paper.book("downloadItems").allKeys.forEach {
var downloadItem:AbsDownloader.DownloadItem? = Paper.book("downloadItems").read(it)
if (downloadItem != null) {
downloadItems.add(downloadItem)
Paper.book("downloadItems").allKeys.forEach { downloadItemId ->
Paper.book("downloadItems").read<AbsDownloader.DownloadItem>(downloadItemId)?.let {
downloadItems.add(it)
}
}
return downloadItems
}
fun saveLocalMediaProgress(mediaProgress:LocalMediaProgress) {
Paper.book("localMediaProgress").write(mediaProgress.id,mediaProgress)
}
// For books this will just be the localLibraryItemId for podcast episodes this will be "{localLibraryItemId}-{episodeId}"
fun getLocalMediaProgress(localMediaProgressId:String):LocalMediaProgress? {
return Paper.book("localMediaProgress").read(localMediaProgressId)
}
fun getAllLocalMediaProgress():List<LocalMediaProgress> {
var mediaProgress:MutableList<LocalMediaProgress> = mutableListOf()
Paper.book("localMediaProgress").allKeys.forEach { localMediaProgressId ->
Paper.book("localMediaProgress").read<LocalMediaProgress>(localMediaProgressId)?.let {
mediaProgress.add(it)
}
}
return mediaProgress
}
fun removeLocalMediaProgress(localMediaProgressId:String) {
Paper.book("localMediaProgress").delete(localMediaProgressId)
}
fun saveLocalPlaybackSession(playbackSession:PlaybackSession) {
Paper.book("localPlaybackSession").write(playbackSession.id,playbackSession)
}
fun getLocalPlaybackSession(playbackSessionId:String):PlaybackSession? {
return Paper.book("localPlaybackSession").read(playbackSessionId)
}
fun saveObject(db:String, key:String, value:JSONObject) {
Log.d(tag, "Saving Object $key ${value.toString()}")
Paper.book(db).write(key, value)
@@ -9,6 +9,7 @@ data class ServerConnectionConfig(
var index:Int,
var name:String,
var address:String,
var userId:String,
var username:String,
var token:String
)
@@ -16,7 +17,7 @@ data class ServerConnectionConfig(
data class DeviceData(
var serverConnectionConfigs:MutableList<ServerConnectionConfig>,
var lastServerConnectionConfigId:String?,
var localLibraryItemIdPlaying:String?
var currentLocalPlaybackSession:PlaybackSession? // Stored to open up where left off for local media
)
@JsonIgnoreProperties(ignoreUnknown = true)
@@ -1,5 +1,6 @@
package com.audiobookshelf.app.data
import com.audiobookshelf.app.device.DeviceManager
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import java.util.*
@@ -7,8 +8,6 @@ import java.util.*
@JsonIgnoreProperties(ignoreUnknown = true)
data class LocalLibraryItem(
var id:String,
var serverAddress:String?,
var libraryItemId:String?,
var folderId:String,
var basePath:String,
var absolutePath:String,
@@ -19,8 +18,14 @@ data class LocalLibraryItem(
var localFiles:MutableList<LocalFile>,
var coverContentUrl:String?,
var coverAbsolutePath:String?,
var isLocal:Boolean
) {
var isLocal:Boolean,
// If local library item is linked to a server item
var serverConnectionConfigId:String?,
var serverAddress:String?,
var serverUserId:String?,
var libraryItemId:String?
) {
@JsonIgnore
fun getDuration():Double {
var total = 0.0
@@ -45,9 +50,14 @@ data class LocalLibraryItem(
}
@JsonIgnore
fun getPlaybackSession():PlaybackSession {
fun getPlaybackSession(episodeId:String):PlaybackSession {
var sessionId = "play-${UUID.randomUUID()}"
val mediaProgressId = if (episodeId.isNullOrEmpty()) id else "$id-$episodeId"
var mediaProgress = DeviceManager.dbManager.getLocalMediaProgress(mediaProgressId)
var currentTime = mediaProgress?.currentTime ?: 0.0
// TODO: Clean up add mediaType methods for displayTitle and displayAuthor
var mediaMetadata = media.metadata
var chapters = if (mediaType == "book") (media as Book).chapters else mutableListOf()
var authorName = "Unknown"
@@ -55,7 +65,10 @@ data class LocalLibraryItem(
var bookMetadata = mediaMetadata as BookMetadata
authorName = bookMetadata?.authorName ?: "Unknown"
}
return PlaybackSession(sessionId,null,null,null, mediaType, mediaMetadata, chapters, mediaMetadata.title, authorName,null,getDuration(),PLAYMETHOD_LOCAL, media.getAudioTracks() as MutableList<AudioTrack>,0.0,null,this,null,null)
var episodeIdNullable = if (episodeId.isNullOrEmpty()) null else episodeId
var dateNow = System.currentTimeMillis()
return PlaybackSession(sessionId,serverUserId,libraryItemId,episodeIdNullable, mediaType, mediaMetadata, chapters, mediaMetadata.title, authorName,null,getDuration(),PLAYMETHOD_LOCAL,dateNow,0L,0L, media.getAudioTracks() as MutableList<AudioTrack>,currentTime,null,this,serverConnectionConfigId, serverAddress)
}
@JsonIgnore
@@ -10,7 +10,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true)
data class LocalMediaItem(
var id:String,
var serverAddress:String?,
var name: String,
var mediaType:String,
var folderId:String,
@@ -63,10 +62,10 @@ data class LocalMediaItem(
if (mediaType == "book") {
var chapters = getAudiobookChapters()
var book = Book(mediaMetadata as BookMetadata, coverAbsolutePath, mutableListOf(), mutableListOf(), chapters,audioTracks,getTotalSize(),getDuration())
return LocalLibraryItem(id,serverAddress, null, folderId, basePath,absolutePath, contentUrl, false,mediaType, book, localFiles, coverContentUrl, coverAbsolutePath,true)
return LocalLibraryItem(id, folderId, basePath,absolutePath, contentUrl, false,mediaType, book, localFiles, coverContentUrl, coverAbsolutePath,true,null,null,null,null)
} else {
var podcast = Podcast(mediaMetadata as PodcastMetadata, coverAbsolutePath, mutableListOf(), mutableListOf(), false)
return LocalLibraryItem(id,serverAddress, null, folderId, basePath,absolutePath, contentUrl, false, mediaType, podcast,localFiles,coverContentUrl, coverAbsolutePath, true)
return LocalLibraryItem(id, folderId, basePath,absolutePath, contentUrl, false, mediaType, podcast,localFiles,coverContentUrl, coverAbsolutePath, true, null,null,null,null)
}
}
}
@@ -0,0 +1,22 @@
package com.audiobookshelf.app.data
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true)
data class LocalMediaProgress(
var id:String,
var localLibraryItemId:String,
var episodeId:String?,
var duration:Double,
var progress:Double, // 0 to 1
var currentTime:Double,
var isFinished:Boolean,
var lastUpdate:Long,
var startedAt:Long,
var finishedAt:Long?,
// For local lib items from server to support server sync
var serverConnectionConfigId:String?,
var serverAddress:String?,
var serverUserId:String?,
var libraryItemId:String?
)
@@ -1,18 +0,0 @@
package com.audiobookshelf.app.data
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true)
data class MediaProgress(
val id:String,
val libraryItemId:String,
val episodeId:String,
val duration:Double,
val progress:Double, // 0 to 1
val currentTime:Int,
val isFinished:Boolean,
val lastUpdate:Long,
val startedAt:Long,
val finishedAt:Long,
val isLocal:Boolean?
)
@@ -2,8 +2,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.audiobookshelf.app.player.MediaProgressSyncData
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.google.android.exoplayer2.MediaItem
@@ -29,17 +30,29 @@ class PlaybackSession(
var coverPath:String?,
var duration:Double,
var playMethod:Int,
var startedAt:Long,
var updatedAt:Long,
var timeListening:Long,
var audioTracks:MutableList<AudioTrack>,
var currentTime:Double,
var libraryItem:LibraryItem?,
var localLibraryItem:LocalLibraryItem?,
var serverUrl:String?,
var token:String?
var serverConnectionConfigId:String?,
var serverAddress:String?
) {
@get:JsonIgnore
val isHLS get() = playMethod == PLAYMETHOD_TRANSCODE
@get:JsonIgnore
val isLocal get() = playMethod == PLAYMETHOD_LOCAL
@get:JsonIgnore
val currentTimeMs get() = (currentTime * 1000L).toLong()
@get:JsonIgnore
val localLibraryItemId get() = localLibraryItem?.id ?: ""
@get:JsonIgnore
val localMediaProgressId get() = if (episodeId.isNullOrEmpty()) localLibraryItemId else "$localLibraryItemId-$episodeId"
@get:JsonIgnore
val progress get() = currentTime / getTotalDuration()
@JsonIgnore
fun getCurrentTrackIndex():Int {
@@ -77,13 +90,13 @@ class PlaybackSession(
if (localLibraryItem?.coverContentUrl != null) return Uri.parse(localLibraryItem?.coverContentUrl) ?: Uri.parse("android.resource://com.audiobookshelf.app/" + R.drawable.icon)
if (coverPath == null) return Uri.parse("android.resource://com.audiobookshelf.app/" + R.drawable.icon)
return Uri.parse("$serverUrl/api/items/$libraryItemId/cover?token=$token")
return Uri.parse("$serverAddress/api/items/$libraryItemId/cover?token=${DeviceManager.token}")
}
@JsonIgnore
fun getContentUri(audioTrack:AudioTrack): Uri {
if (isLocal) return Uri.parse(audioTrack.contentUrl) // Local content url
return Uri.parse("$serverUrl${audioTrack.contentUrl}?token=$token")
return Uri.parse("$serverAddress${audioTrack.contentUrl}?token=${DeviceManager.token}")
}
@JsonIgnore
@@ -130,6 +143,19 @@ class PlaybackSession(
@JsonIgnore
fun clone():PlaybackSession {
return PlaybackSession(id,userId,libraryItemId,episodeId,mediaType,mediaMetadata,chapters,displayTitle,displayAuthor,coverPath,duration,playMethod,audioTracks,currentTime,libraryItem,localLibraryItem,serverUrl,token)
return PlaybackSession(id,userId,libraryItemId,episodeId,mediaType,mediaMetadata,chapters,displayTitle,displayAuthor,coverPath,duration,playMethod,startedAt,updatedAt,timeListening,audioTracks,currentTime,libraryItem,localLibraryItem,serverConnectionConfigId,serverAddress)
}
@JsonIgnore
fun syncData(syncData:MediaProgressSyncData) {
timeListening += syncData.timeListened
updatedAt = System.currentTimeMillis()
currentTime = syncData.currentTime
}
@JsonIgnore
fun getNewLocalMediaProgress():LocalMediaProgress {
var dateNow = System.currentTimeMillis()
return LocalMediaProgress(localMediaProgressId,localLibraryItemId,episodeId,getTotalDuration(),progress,currentTime,false,dateNow,dateNow,null,serverConnectionConfigId,serverAddress,userId,libraryItemId)
}
}
@@ -12,6 +12,7 @@ object DeviceManager {
var serverConnectionConfig: ServerConnectionConfig? = null
val serverAddress get() = serverConnectionConfig?.address ?: ""
val serverUserId get() = serverConnectionConfig?.userId ?: ""
val token get() = serverConnectionConfig?.token ?: ""
init {
@@ -186,7 +186,7 @@ class FolderScanner(var ctx: Context) {
Log.d(tag, "Found local media item named $itemFolderName with ${audioTracks.size} tracks and ${localFiles.size} local files")
mediaItemsAdded++
var localMediaItem = LocalMediaItem(itemId,null, itemFolderName, localFolder.mediaType, localFolder.id, itemFolder.uri.toString(), itemFolder.getSimplePath(ctx), itemFolder.getBasePath(ctx), itemFolder.getAbsolutePath(ctx),audioTracks,localFiles,coverContentUrl,coverAbsolutePath)
var localMediaItem = LocalMediaItem(itemId, itemFolderName, localFolder.mediaType, localFolder.id, itemFolder.uri.toString(), itemFolder.getSimplePath(ctx), itemFolder.getBasePath(ctx), itemFolder.getAbsolutePath(ctx),audioTracks,localFiles,coverContentUrl,coverAbsolutePath)
var localLibraryItem = localMediaItem.getLocalLibraryItem()
localLibraryItems.add(localLibraryItem)
}
@@ -236,7 +236,7 @@ class FolderScanner(var ctx: Context) {
var filesFound = df.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
Log.d(tag, "scanDownloadItem ${filesFound.size} files found in ${downloadItem.itemFolderPath}")
var localLibraryItem = LocalLibraryItem("local_${downloadItem.id}", downloadItem.serverAddress, downloadItem.id, downloadItem.localFolder.id, itemFolderBasePath, itemFolderAbsolutePath, itemFolderUrl, false, downloadItem.mediaType, downloadItem.media, mutableListOf(), null, null, true)
var localLibraryItem = LocalLibraryItem("local_${downloadItem.id}", downloadItem.localFolder.id, itemFolderBasePath, itemFolderAbsolutePath, itemFolderUrl, false, downloadItem.mediaType, downloadItem.media, mutableListOf(), null, null, true,downloadItem.serverConnectionConfigId,downloadItem.serverAddress,downloadItem.serverUserId,downloadItem.id)
var localFiles:MutableList<LocalFile> = mutableListOf()
var audioTracks:MutableList<AudioTrack> = mutableListOf()
@@ -3,11 +3,13 @@ package com.audiobookshelf.app.player
import android.os.Handler
import android.os.Looper
import android.util.Log
import com.audiobookshelf.app.data.MediaProgress
import com.audiobookshelf.app.data.LocalMediaProgress
import com.audiobookshelf.app.data.PlaybackSession
import com.audiobookshelf.app.device.DeviceManager
import com.audiobookshelf.app.server.ApiHandler
import java.util.*
import kotlin.concurrent.schedule
import kotlin.math.roundToInt
data class MediaProgressSyncData(
var timeListened:Long, // seconds
@@ -26,7 +28,7 @@ class MediaProgressSyncer(playerNotificationService:PlayerNotificationService, a
private var lastSyncTime:Long = 0
var currentPlaybackSession: PlaybackSession? = null // copy of pb session currently syncing
// var currentMediaProgress: MediaProgress? = null
var currentLocalMediaProgress: LocalMediaProgress? = null
val currentDisplayTitle get() = currentPlaybackSession?.displayTitle ?: "Unset"
val currentIsLocal get() = currentPlaybackSession?.isLocal == true
@@ -77,8 +79,13 @@ class MediaProgressSyncer(playerNotificationService:PlayerNotificationService, a
var syncData = MediaProgressSyncData(listeningTimeToAdd,currentPlaybackDuration,currentTime)
currentPlaybackSession?.syncData(syncData)
if (currentIsLocal) {
// TODO: Save local progress sync
// Save local progress sync
currentPlaybackSession?.let {
DeviceManager.dbManager.saveLocalPlaybackSession(it)
saveLocalProgress(it)
}
} else {
apiHandler.sendProgressSync(currentSessionId,syncData) {
Log.d(tag, "Progress sync data sent to server $currentDisplayTitle for time $currentTime")
@@ -86,6 +93,26 @@ class MediaProgressSyncer(playerNotificationService:PlayerNotificationService, a
}
}
private fun saveLocalProgress(playbackSession:PlaybackSession) {
if (currentLocalMediaProgress == null) {
var mediaProgress = DeviceManager.dbManager.getLocalMediaProgress(playbackSession.localMediaProgressId)
if (mediaProgress == null) {
currentLocalMediaProgress = playbackSession.getNewLocalMediaProgress()
} else {
currentLocalMediaProgress = mediaProgress
}
} else {
currentLocalMediaProgress?.currentTime = playbackSession.currentTime
currentLocalMediaProgress?.lastUpdate = System.currentTimeMillis()
currentLocalMediaProgress?.progress = playbackSession.progress
}
currentLocalMediaProgress?.let {
DeviceManager.dbManager.saveLocalMediaProgress(it)
playerNotificationService.clientEventEmitter?.onLocalMediaProgressUpdate(it)
Log.d(tag, "Saved Local Progress Current Time: ${it.currentTime} | Duration ${it.duration} | Progress ${(it.progress * 100).roundToInt()}%")
}
}
fun reset() {
listeningTimerTask?.cancel()
listeningTimerTask = null
@@ -23,7 +23,9 @@ import androidx.media.MediaBrowserServiceCompat
import androidx.media.utils.MediaConstants
import com.audiobookshelf.app.Audiobook
import com.audiobookshelf.app.AudiobookManager
import com.audiobookshelf.app.data.LocalMediaProgress
import com.audiobookshelf.app.data.PlaybackSession
import com.audiobookshelf.app.device.DeviceManager
import com.audiobookshelf.app.server.ApiHandler
import com.getcapacitor.Bridge
import com.getcapacitor.JSObject
@@ -57,6 +59,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
fun onPrepare(audiobookId: String, playWhenReady: Boolean)
fun onSleepTimerEnded(currentPosition: Long)
fun onSleepTimerSet(sleepTimeRemaining: Int)
fun onLocalMediaProgressUpdate(localMediaProgress: LocalMediaProgress)
}
private val tag = "PlayerService"
@@ -648,7 +651,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
Log.d(tag, "Playing HLS Item")
var dataSourceFactory = DefaultHttpDataSource.Factory()
dataSourceFactory.setUserAgent(channelId)
dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${playbackSession.token}"))
dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${DeviceManager.token}"))
mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItems[0])
}
mPlayer.setMediaSource(mediaSource)
@@ -6,6 +6,7 @@ import android.os.Looper
import android.util.Log
import androidx.core.content.ContextCompat
import com.audiobookshelf.app.MainActivity
import com.audiobookshelf.app.data.LocalMediaProgress
import com.audiobookshelf.app.data.PlaybackSession
import com.audiobookshelf.app.device.DeviceManager
import com.audiobookshelf.app.player.CastManager
@@ -64,6 +65,10 @@ class AbsAudioPlayer : Plugin() {
override fun onSleepTimerSet(sleepTimeRemaining: Int) {
emit("onSleepTimerSet", sleepTimeRemaining)
}
override fun onLocalMediaProgressUpdate(localMediaProgress: LocalMediaProgress) {
notifyListeners("onLocalMediaProgressUpdate", JSObject(jacksonObjectMapper().writeValueAsString(localMediaProgress)))
}
})
}
mainActivity.pluginCallback = foregroundServiceReady
@@ -86,6 +91,7 @@ class AbsAudioPlayer : Plugin() {
}
var libraryItemId = call.getString("libraryItemId", "").toString()
var episodeId = call.getString("episodeId", "").toString()
var playWhenReady = call.getBoolean("playWhenReady") == true
if (libraryItemId.isEmpty()) {
@@ -97,13 +103,13 @@ class AbsAudioPlayer : Plugin() {
DeviceManager.dbManager.getLocalLibraryItem(libraryItemId)?.let {
Handler(Looper.getMainLooper()).post() {
Log.d(tag, "Preparing Local Media item ${jacksonObjectMapper().writeValueAsString(it)}")
var playbackSession = it.getPlaybackSession()
var playbackSession = it.getPlaybackSession(episodeId)
playerNotificationService.preparePlayer(playbackSession, playWhenReady)
}
return call.resolve(JSObject())
}
} else { // Play library item from server
apiHandler.playLibraryItem(libraryItemId, false) {
apiHandler.playLibraryItem(libraryItemId, episodeId, false) {
Handler(Looper.getMainLooper()).post() {
Log.d(tag, "Preparing Player TEST ${jacksonObjectMapper().writeValueAsString(it)}")
@@ -17,6 +17,10 @@ import org.json.JSONObject
class AbsDatabase : Plugin() {
val tag = "AbsDatabase"
data class LocalMediaProgressPayload(val value:List<LocalMediaProgress>)
data class LocalLibraryItemsPayload(val value:List<LocalLibraryItem>)
data class LocalFoldersPayload(val value:List<LocalFolder>)
@PluginMethod
fun getDeviceData(call:PluginCall) {
GlobalScope.launch(Dispatchers.IO) {
@@ -29,10 +33,7 @@ class AbsDatabase : Plugin() {
fun getLocalFolders(call:PluginCall) {
GlobalScope.launch(Dispatchers.IO) {
var folders = DeviceManager.dbManager.getAllLocalFolders()
var folderObjArray = jacksonObjectMapper().writeValueAsString(folders)
var jsobj = JSObject()
jsobj.put("folders", folderObjArray)
call.resolve(jsobj)
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(LocalFoldersPayload(folders))))
}
}
@@ -80,9 +81,7 @@ class AbsDatabase : Plugin() {
GlobalScope.launch(Dispatchers.IO) {
var localLibraryItems = DeviceManager.dbManager.getLocalLibraryItems(mediaType)
var jsobj = JSObject()
jsobj.put("localLibraryItems", jacksonObjectMapper().writeValueAsString(localLibraryItems))
call.resolve(jsobj)
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(LocalLibraryItemsPayload(localLibraryItems))))
}
}
@@ -90,11 +89,8 @@ class AbsDatabase : Plugin() {
fun getLocalLibraryItemsInFolder(call:PluginCall) {
var folderId = call.getString("folderId", "").toString()
GlobalScope.launch(Dispatchers.IO) {
var localMediaItems = DeviceManager.dbManager.getLocalLibraryItemsInFolder(folderId)
var mediaItemsArray = jacksonObjectMapper().writeValueAsString(localMediaItems)
var jsobj = JSObject()
jsobj.put("localLibraryItems", mediaItemsArray)
call.resolve(jsobj)
var localLibraryItems = DeviceManager.dbManager.getLocalLibraryItemsInFolder(folderId)
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(LocalLibraryItemsPayload(localLibraryItems))))
}
}
@@ -103,6 +99,7 @@ class AbsDatabase : Plugin() {
var serverConnectionConfigId = call.getString("id", "").toString()
var serverConnectionConfig = DeviceManager.deviceData.serverConnectionConfigs.find { it.id == serverConnectionConfigId }
var userId = call.getString("userId", "").toString()
var username = call.getString("username", "").toString()
var token = call.getString("token", "").toString()
@@ -113,7 +110,7 @@ class AbsDatabase : Plugin() {
// Create new server connection config
var sscId = DeviceManager.getBase64Id("$serverAddress@$username")
var sscIndex = DeviceManager.deviceData.serverConnectionConfigs.size
serverConnectionConfig = ServerConnectionConfig(sscId, sscIndex, "$serverAddress ($username)", serverAddress, username, token)
serverConnectionConfig = ServerConnectionConfig(sscId, sscIndex, "$serverAddress ($username)", serverAddress, userId, username, token)
// Add and save
DeviceManager.deviceData.serverConnectionConfigs.add(serverConnectionConfig!!)
@@ -122,6 +119,7 @@ class AbsDatabase : Plugin() {
} else {
var shouldSave = false
if (serverConnectionConfig?.username != username || serverConnectionConfig?.token != token) {
serverConnectionConfig?.userId = userId
serverConnectionConfig?.username = username
serverConnectionConfig?.name = "${serverConnectionConfig?.address} (${serverConnectionConfig?.username})"
serverConnectionConfig?.token = token
@@ -168,6 +166,22 @@ class AbsDatabase : Plugin() {
}
}
@PluginMethod
fun getAllLocalMediaProgress(call:PluginCall) {
GlobalScope.launch(Dispatchers.IO) {
var localMediaProgress = DeviceManager.dbManager.getAllLocalMediaProgress()
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(LocalMediaProgressPayload(localMediaProgress))))
}
}
@PluginMethod
fun removeLocalMediaProgress(call:PluginCall) {
var localMediaProgressId = call.getString("localMediaProgressId", "").toString()
DeviceManager.dbManager.removeLocalMediaProgress(localMediaProgressId)
call.resolve()
}
//
// Generic Webview calls to db
//
@@ -62,7 +62,9 @@ class AbsDownloader : Plugin() {
data class DownloadItem(
val id: String,
val serverConnectionConfigId:String,
val serverAddress:String,
val serverUserId:String,
val mediaType: String,
val itemFolderPath:String,
val localFolder: LocalFolder,
@@ -143,7 +145,7 @@ class AbsDownloader : Plugin() {
var tracks = libraryItem.media.getAudioTracks()
Log.d(tag, "Starting library item download with ${tracks.size} tracks")
var itemFolderPath = localFolder.absolutePath + "/" + bookTitle
var downloadItem = DownloadItem(libraryItem.id, DeviceManager.serverAddress, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, libraryItem.media, mutableListOf())
var downloadItem = DownloadItem(libraryItem.id, DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, libraryItem.media, mutableListOf())
// Create download item part for each audio track
tracks.forEach { audioTrack ->
@@ -104,18 +104,20 @@ class ApiHandler {
}
}
fun playLibraryItem(libraryItemId:String, forceTranscode:Boolean, cb: (PlaybackSession) -> Unit) {
fun playLibraryItem(libraryItemId:String, episodeId:String, forceTranscode:Boolean, cb: (PlaybackSession) -> Unit) {
val mapper = jacksonObjectMapper()
var payload = JSObject()
payload.put("mediaPlayer", "exo-player")
// Only if direct play fails do we force transcode
// TODO: Fallback to transcode
if (!forceTranscode) payload.put("forceDirectPlay", true)
else payload.put("forceTranscode", true)
postRequest("/api/items/$libraryItemId/play", payload) {
it.put("serverUrl", DeviceManager.serverAddress)
it.put("token", DeviceManager.token)
val endpoint = if (episodeId.isNullOrEmpty()) "/api/items/$libraryItemId/play" else "/api/items/$libraryItemId/play/$episodeId"
postRequest(endpoint, payload) {
it.put("serverConnectionConfigId", DeviceManager.serverConnectionConfig?.id)
it.put("serverAddress", DeviceManager.serverAddress)
val playbackSession = mapper.readValue<PlaybackSession>(it.toString())
cb(playbackSession)
}