mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2026-08-06 20:08:48 +02:00
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:
@@ -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
|
||||
|
||||
+4
-1
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user