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

View file

@ -76,10 +76,9 @@ class DbManager {
fun getAllLocalFolders():List<LocalFolder> { fun getAllLocalFolders():List<LocalFolder> {
var localFolders:MutableList<LocalFolder> = mutableListOf() var localFolders:MutableList<LocalFolder> = mutableListOf()
Paper.book("localFolders").allKeys.forEach { Paper.book("localFolders").allKeys.forEach { localFolderId ->
var localFolder:LocalFolder? = Paper.book("localFolders").read(it) Paper.book("localFolders").read<LocalFolder>(localFolderId)?.let {
if (localFolder != null) { localFolders.add(it)
localFolders.add(localFolder)
} }
} }
return localFolders return localFolders
@ -103,15 +102,41 @@ class DbManager {
fun getDownloadItems():List<AbsDownloader.DownloadItem> { fun getDownloadItems():List<AbsDownloader.DownloadItem> {
var downloadItems:MutableList<AbsDownloader.DownloadItem> = mutableListOf() var downloadItems:MutableList<AbsDownloader.DownloadItem> = mutableListOf()
Paper.book("downloadItems").allKeys.forEach { Paper.book("downloadItems").allKeys.forEach { downloadItemId ->
var downloadItem:AbsDownloader.DownloadItem? = Paper.book("downloadItems").read(it) Paper.book("downloadItems").read<AbsDownloader.DownloadItem>(downloadItemId)?.let {
if (downloadItem != null) { downloadItems.add(it)
downloadItems.add(downloadItem)
} }
} }
return downloadItems 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) { fun saveObject(db:String, key:String, value:JSONObject) {
Log.d(tag, "Saving Object $key ${value.toString()}") Log.d(tag, "Saving Object $key ${value.toString()}")
Paper.book(db).write(key, value) Paper.book(db).write(key, value)

View file

@ -9,6 +9,7 @@ data class ServerConnectionConfig(
var index:Int, var index:Int,
var name:String, var name:String,
var address:String, var address:String,
var userId:String,
var username:String, var username:String,
var token:String var token:String
) )
@ -16,7 +17,7 @@ data class ServerConnectionConfig(
data class DeviceData( data class DeviceData(
var serverConnectionConfigs:MutableList<ServerConnectionConfig>, var serverConnectionConfigs:MutableList<ServerConnectionConfig>,
var lastServerConnectionConfigId:String?, var lastServerConnectionConfigId:String?,
var localLibraryItemIdPlaying:String? var currentLocalPlaybackSession:PlaybackSession? // Stored to open up where left off for local media
) )
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)

View file

@ -1,5 +1,6 @@
package com.audiobookshelf.app.data package com.audiobookshelf.app.data
import com.audiobookshelf.app.device.DeviceManager
import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import java.util.* import java.util.*
@ -7,8 +8,6 @@ import java.util.*
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
data class LocalLibraryItem( data class LocalLibraryItem(
var id:String, var id:String,
var serverAddress:String?,
var libraryItemId:String?,
var folderId:String, var folderId:String,
var basePath:String, var basePath:String,
var absolutePath:String, var absolutePath:String,
@ -19,8 +18,14 @@ data class LocalLibraryItem(
var localFiles:MutableList<LocalFile>, var localFiles:MutableList<LocalFile>,
var coverContentUrl:String?, var coverContentUrl:String?,
var coverAbsolutePath: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 @JsonIgnore
fun getDuration():Double { fun getDuration():Double {
var total = 0.0 var total = 0.0
@ -45,9 +50,14 @@ data class LocalLibraryItem(
} }
@JsonIgnore @JsonIgnore
fun getPlaybackSession():PlaybackSession { fun getPlaybackSession(episodeId:String):PlaybackSession {
var sessionId = "play-${UUID.randomUUID()}" 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 mediaMetadata = media.metadata
var chapters = if (mediaType == "book") (media as Book).chapters else mutableListOf() var chapters = if (mediaType == "book") (media as Book).chapters else mutableListOf()
var authorName = "Unknown" var authorName = "Unknown"
@ -55,7 +65,10 @@ data class LocalLibraryItem(
var bookMetadata = mediaMetadata as BookMetadata var bookMetadata = mediaMetadata as BookMetadata
authorName = bookMetadata?.authorName ?: "Unknown" 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 @JsonIgnore

View file

@ -10,7 +10,6 @@ import com.fasterxml.jackson.annotation.JsonIgnoreProperties
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
data class LocalMediaItem( data class LocalMediaItem(
var id:String, var id:String,
var serverAddress:String?,
var name: String, var name: String,
var mediaType:String, var mediaType:String,
var folderId:String, var folderId:String,
@ -63,10 +62,10 @@ data class LocalMediaItem(
if (mediaType == "book") { if (mediaType == "book") {
var chapters = getAudiobookChapters() var chapters = getAudiobookChapters()
var book = Book(mediaMetadata as BookMetadata, coverAbsolutePath, mutableListOf(), mutableListOf(), chapters,audioTracks,getTotalSize(),getDuration()) 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 { } else {
var podcast = Podcast(mediaMetadata as PodcastMetadata, coverAbsolutePath, mutableListOf(), mutableListOf(), false) 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)
} }
} }
} }

View file

@ -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?
)

View file

@ -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?
)

View file

@ -2,8 +2,9 @@ package com.audiobookshelf.app.data
import android.net.Uri import android.net.Uri
import android.support.v4.media.MediaMetadataCompat import android.support.v4.media.MediaMetadataCompat
import android.util.Log
import com.audiobookshelf.app.R 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.JsonIgnore
import com.fasterxml.jackson.annotation.JsonIgnoreProperties import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.google.android.exoplayer2.MediaItem import com.google.android.exoplayer2.MediaItem
@ -29,17 +30,29 @@ class PlaybackSession(
var coverPath:String?, var coverPath:String?,
var duration:Double, var duration:Double,
var playMethod:Int, var playMethod:Int,
var startedAt:Long,
var updatedAt:Long,
var timeListening:Long,
var audioTracks:MutableList<AudioTrack>, var audioTracks:MutableList<AudioTrack>,
var currentTime:Double, var currentTime:Double,
var libraryItem:LibraryItem?, var libraryItem:LibraryItem?,
var localLibraryItem:LocalLibraryItem?, var localLibraryItem:LocalLibraryItem?,
var serverUrl:String?, var serverConnectionConfigId:String?,
var token:String? var serverAddress:String?
) { ) {
@get:JsonIgnore
val isHLS get() = playMethod == PLAYMETHOD_TRANSCODE val isHLS get() = playMethod == PLAYMETHOD_TRANSCODE
@get:JsonIgnore
val isLocal get() = playMethod == PLAYMETHOD_LOCAL val isLocal get() = playMethod == PLAYMETHOD_LOCAL
@get:JsonIgnore
val currentTimeMs get() = (currentTime * 1000L).toLong() 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 @JsonIgnore
fun getCurrentTrackIndex():Int { 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 (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) 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 @JsonIgnore
fun getContentUri(audioTrack:AudioTrack): Uri { fun getContentUri(audioTrack:AudioTrack): Uri {
if (isLocal) return Uri.parse(audioTrack.contentUrl) // Local content url 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 @JsonIgnore
@ -130,6 +143,19 @@ class PlaybackSession(
@JsonIgnore @JsonIgnore
fun clone():PlaybackSession { 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)
} }
} }

View file

@ -12,6 +12,7 @@ object DeviceManager {
var serverConnectionConfig: ServerConnectionConfig? = null var serverConnectionConfig: ServerConnectionConfig? = null
val serverAddress get() = serverConnectionConfig?.address ?: "" val serverAddress get() = serverConnectionConfig?.address ?: ""
val serverUserId get() = serverConnectionConfig?.userId ?: ""
val token get() = serverConnectionConfig?.token ?: "" val token get() = serverConnectionConfig?.token ?: ""
init { init {

View file

@ -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") Log.d(tag, "Found local media item named $itemFolderName with ${audioTracks.size} tracks and ${localFiles.size} local files")
mediaItemsAdded++ 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() var localLibraryItem = localMediaItem.getLocalLibraryItem()
localLibraryItems.add(localLibraryItem) localLibraryItems.add(localLibraryItem)
} }
@ -236,7 +236,7 @@ class FolderScanner(var ctx: Context) {
var filesFound = df.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*")) var filesFound = df.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
Log.d(tag, "scanDownloadItem ${filesFound.size} files found in ${downloadItem.itemFolderPath}") 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 localFiles:MutableList<LocalFile> = mutableListOf()
var audioTracks:MutableList<AudioTrack> = mutableListOf() var audioTracks:MutableList<AudioTrack> = mutableListOf()

View file

@ -3,11 +3,13 @@ package com.audiobookshelf.app.player
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log 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.data.PlaybackSession
import com.audiobookshelf.app.device.DeviceManager
import com.audiobookshelf.app.server.ApiHandler import com.audiobookshelf.app.server.ApiHandler
import java.util.* import java.util.*
import kotlin.concurrent.schedule import kotlin.concurrent.schedule
import kotlin.math.roundToInt
data class MediaProgressSyncData( data class MediaProgressSyncData(
var timeListened:Long, // seconds var timeListened:Long, // seconds
@ -26,7 +28,7 @@ class MediaProgressSyncer(playerNotificationService:PlayerNotificationService, a
private var lastSyncTime:Long = 0 private var lastSyncTime:Long = 0
var currentPlaybackSession: PlaybackSession? = null // copy of pb session currently syncing 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 currentDisplayTitle get() = currentPlaybackSession?.displayTitle ?: "Unset"
val currentIsLocal get() = currentPlaybackSession?.isLocal == true val currentIsLocal get() = currentPlaybackSession?.isLocal == true
@ -77,8 +79,13 @@ class MediaProgressSyncer(playerNotificationService:PlayerNotificationService, a
var syncData = MediaProgressSyncData(listeningTimeToAdd,currentPlaybackDuration,currentTime) var syncData = MediaProgressSyncData(listeningTimeToAdd,currentPlaybackDuration,currentTime)
currentPlaybackSession?.syncData(syncData)
if (currentIsLocal) { if (currentIsLocal) {
// TODO: Save local progress sync // Save local progress sync
currentPlaybackSession?.let {
DeviceManager.dbManager.saveLocalPlaybackSession(it)
saveLocalProgress(it)
}
} else { } else {
apiHandler.sendProgressSync(currentSessionId,syncData) { apiHandler.sendProgressSync(currentSessionId,syncData) {
Log.d(tag, "Progress sync data sent to server $currentDisplayTitle for time $currentTime") 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() { fun reset() {
listeningTimerTask?.cancel() listeningTimerTask?.cancel()
listeningTimerTask = null listeningTimerTask = null

View file

@ -23,7 +23,9 @@ import androidx.media.MediaBrowserServiceCompat
import androidx.media.utils.MediaConstants import androidx.media.utils.MediaConstants
import com.audiobookshelf.app.Audiobook import com.audiobookshelf.app.Audiobook
import com.audiobookshelf.app.AudiobookManager import com.audiobookshelf.app.AudiobookManager
import com.audiobookshelf.app.data.LocalMediaProgress
import com.audiobookshelf.app.data.PlaybackSession import com.audiobookshelf.app.data.PlaybackSession
import com.audiobookshelf.app.device.DeviceManager
import com.audiobookshelf.app.server.ApiHandler import com.audiobookshelf.app.server.ApiHandler
import com.getcapacitor.Bridge import com.getcapacitor.Bridge
import com.getcapacitor.JSObject import com.getcapacitor.JSObject
@ -57,6 +59,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
fun onPrepare(audiobookId: String, playWhenReady: Boolean) fun onPrepare(audiobookId: String, playWhenReady: Boolean)
fun onSleepTimerEnded(currentPosition: Long) fun onSleepTimerEnded(currentPosition: Long)
fun onSleepTimerSet(sleepTimeRemaining: Int) fun onSleepTimerSet(sleepTimeRemaining: Int)
fun onLocalMediaProgressUpdate(localMediaProgress: LocalMediaProgress)
} }
private val tag = "PlayerService" private val tag = "PlayerService"
@ -648,7 +651,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
Log.d(tag, "Playing HLS Item") Log.d(tag, "Playing HLS Item")
var dataSourceFactory = DefaultHttpDataSource.Factory() var dataSourceFactory = DefaultHttpDataSource.Factory()
dataSourceFactory.setUserAgent(channelId) 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]) mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItems[0])
} }
mPlayer.setMediaSource(mediaSource) mPlayer.setMediaSource(mediaSource)

View file

@ -6,6 +6,7 @@ import android.os.Looper
import android.util.Log import android.util.Log
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.audiobookshelf.app.MainActivity import com.audiobookshelf.app.MainActivity
import com.audiobookshelf.app.data.LocalMediaProgress
import com.audiobookshelf.app.data.PlaybackSession import com.audiobookshelf.app.data.PlaybackSession
import com.audiobookshelf.app.device.DeviceManager import com.audiobookshelf.app.device.DeviceManager
import com.audiobookshelf.app.player.CastManager import com.audiobookshelf.app.player.CastManager
@ -64,6 +65,10 @@ class AbsAudioPlayer : Plugin() {
override fun onSleepTimerSet(sleepTimeRemaining: Int) { override fun onSleepTimerSet(sleepTimeRemaining: Int) {
emit("onSleepTimerSet", sleepTimeRemaining) emit("onSleepTimerSet", sleepTimeRemaining)
} }
override fun onLocalMediaProgressUpdate(localMediaProgress: LocalMediaProgress) {
notifyListeners("onLocalMediaProgressUpdate", JSObject(jacksonObjectMapper().writeValueAsString(localMediaProgress)))
}
}) })
} }
mainActivity.pluginCallback = foregroundServiceReady mainActivity.pluginCallback = foregroundServiceReady
@ -86,6 +91,7 @@ class AbsAudioPlayer : Plugin() {
} }
var libraryItemId = call.getString("libraryItemId", "").toString() var libraryItemId = call.getString("libraryItemId", "").toString()
var episodeId = call.getString("episodeId", "").toString()
var playWhenReady = call.getBoolean("playWhenReady") == true var playWhenReady = call.getBoolean("playWhenReady") == true
if (libraryItemId.isEmpty()) { if (libraryItemId.isEmpty()) {
@ -97,13 +103,13 @@ class AbsAudioPlayer : Plugin() {
DeviceManager.dbManager.getLocalLibraryItem(libraryItemId)?.let { DeviceManager.dbManager.getLocalLibraryItem(libraryItemId)?.let {
Handler(Looper.getMainLooper()).post() { Handler(Looper.getMainLooper()).post() {
Log.d(tag, "Preparing Local Media item ${jacksonObjectMapper().writeValueAsString(it)}") Log.d(tag, "Preparing Local Media item ${jacksonObjectMapper().writeValueAsString(it)}")
var playbackSession = it.getPlaybackSession() var playbackSession = it.getPlaybackSession(episodeId)
playerNotificationService.preparePlayer(playbackSession, playWhenReady) playerNotificationService.preparePlayer(playbackSession, playWhenReady)
} }
return call.resolve(JSObject()) return call.resolve(JSObject())
} }
} else { // Play library item from server } else { // Play library item from server
apiHandler.playLibraryItem(libraryItemId, false) { apiHandler.playLibraryItem(libraryItemId, episodeId, false) {
Handler(Looper.getMainLooper()).post() { Handler(Looper.getMainLooper()).post() {
Log.d(tag, "Preparing Player TEST ${jacksonObjectMapper().writeValueAsString(it)}") Log.d(tag, "Preparing Player TEST ${jacksonObjectMapper().writeValueAsString(it)}")

View file

@ -17,6 +17,10 @@ import org.json.JSONObject
class AbsDatabase : Plugin() { class AbsDatabase : Plugin() {
val tag = "AbsDatabase" 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 @PluginMethod
fun getDeviceData(call:PluginCall) { fun getDeviceData(call:PluginCall) {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
@ -29,10 +33,7 @@ class AbsDatabase : Plugin() {
fun getLocalFolders(call:PluginCall) { fun getLocalFolders(call:PluginCall) {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
var folders = DeviceManager.dbManager.getAllLocalFolders() var folders = DeviceManager.dbManager.getAllLocalFolders()
var folderObjArray = jacksonObjectMapper().writeValueAsString(folders) call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(LocalFoldersPayload(folders))))
var jsobj = JSObject()
jsobj.put("folders", folderObjArray)
call.resolve(jsobj)
} }
} }
@ -80,9 +81,7 @@ class AbsDatabase : Plugin() {
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
var localLibraryItems = DeviceManager.dbManager.getLocalLibraryItems(mediaType) var localLibraryItems = DeviceManager.dbManager.getLocalLibraryItems(mediaType)
var jsobj = JSObject() call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(LocalLibraryItemsPayload(localLibraryItems))))
jsobj.put("localLibraryItems", jacksonObjectMapper().writeValueAsString(localLibraryItems))
call.resolve(jsobj)
} }
} }
@ -90,11 +89,8 @@ class AbsDatabase : Plugin() {
fun getLocalLibraryItemsInFolder(call:PluginCall) { fun getLocalLibraryItemsInFolder(call:PluginCall) {
var folderId = call.getString("folderId", "").toString() var folderId = call.getString("folderId", "").toString()
GlobalScope.launch(Dispatchers.IO) { GlobalScope.launch(Dispatchers.IO) {
var localMediaItems = DeviceManager.dbManager.getLocalLibraryItemsInFolder(folderId) var localLibraryItems = DeviceManager.dbManager.getLocalLibraryItemsInFolder(folderId)
var mediaItemsArray = jacksonObjectMapper().writeValueAsString(localMediaItems) call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(LocalLibraryItemsPayload(localLibraryItems))))
var jsobj = JSObject()
jsobj.put("localLibraryItems", mediaItemsArray)
call.resolve(jsobj)
} }
} }
@ -103,6 +99,7 @@ class AbsDatabase : Plugin() {
var serverConnectionConfigId = call.getString("id", "").toString() var serverConnectionConfigId = call.getString("id", "").toString()
var serverConnectionConfig = DeviceManager.deviceData.serverConnectionConfigs.find { it.id == serverConnectionConfigId } var serverConnectionConfig = DeviceManager.deviceData.serverConnectionConfigs.find { it.id == serverConnectionConfigId }
var userId = call.getString("userId", "").toString()
var username = call.getString("username", "").toString() var username = call.getString("username", "").toString()
var token = call.getString("token", "").toString() var token = call.getString("token", "").toString()
@ -113,7 +110,7 @@ class AbsDatabase : Plugin() {
// Create new server connection config // Create new server connection config
var sscId = DeviceManager.getBase64Id("$serverAddress@$username") var sscId = DeviceManager.getBase64Id("$serverAddress@$username")
var sscIndex = DeviceManager.deviceData.serverConnectionConfigs.size 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 // Add and save
DeviceManager.deviceData.serverConnectionConfigs.add(serverConnectionConfig!!) DeviceManager.deviceData.serverConnectionConfigs.add(serverConnectionConfig!!)
@ -122,6 +119,7 @@ class AbsDatabase : Plugin() {
} else { } else {
var shouldSave = false var shouldSave = false
if (serverConnectionConfig?.username != username || serverConnectionConfig?.token != token) { if (serverConnectionConfig?.username != username || serverConnectionConfig?.token != token) {
serverConnectionConfig?.userId = userId
serverConnectionConfig?.username = username serverConnectionConfig?.username = username
serverConnectionConfig?.name = "${serverConnectionConfig?.address} (${serverConnectionConfig?.username})" serverConnectionConfig?.name = "${serverConnectionConfig?.address} (${serverConnectionConfig?.username})"
serverConnectionConfig?.token = token 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 // Generic Webview calls to db
// //

View file

@ -62,7 +62,9 @@ class AbsDownloader : Plugin() {
data class DownloadItem( data class DownloadItem(
val id: String, val id: String,
val serverConnectionConfigId:String,
val serverAddress:String, val serverAddress:String,
val serverUserId:String,
val mediaType: String, val mediaType: String,
val itemFolderPath:String, val itemFolderPath:String,
val localFolder: LocalFolder, val localFolder: LocalFolder,
@ -143,7 +145,7 @@ class AbsDownloader : Plugin() {
var tracks = libraryItem.media.getAudioTracks() var tracks = libraryItem.media.getAudioTracks()
Log.d(tag, "Starting library item download with ${tracks.size} tracks") Log.d(tag, "Starting library item download with ${tracks.size} tracks")
var itemFolderPath = localFolder.absolutePath + "/" + bookTitle 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 // Create download item part for each audio track
tracks.forEach { audioTrack -> tracks.forEach { audioTrack ->

View file

@ -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() val mapper = jacksonObjectMapper()
var payload = JSObject() var payload = JSObject()
payload.put("mediaPlayer", "exo-player") payload.put("mediaPlayer", "exo-player")
// Only if direct play fails do we force transcode // Only if direct play fails do we force transcode
// TODO: Fallback to transcode
if (!forceTranscode) payload.put("forceDirectPlay", true) if (!forceTranscode) payload.put("forceDirectPlay", true)
else payload.put("forceTranscode", true) else payload.put("forceTranscode", true)
postRequest("/api/items/$libraryItemId/play", payload) { val endpoint = if (episodeId.isNullOrEmpty()) "/api/items/$libraryItemId/play" else "/api/items/$libraryItemId/play/$episodeId"
it.put("serverUrl", DeviceManager.serverAddress) postRequest(endpoint, payload) {
it.put("token", DeviceManager.token) it.put("serverConnectionConfigId", DeviceManager.serverConnectionConfig?.id)
it.put("serverAddress", DeviceManager.serverAddress)
val playbackSession = mapper.readValue<PlaybackSession>(it.toString()) val playbackSession = mapper.readValue<PlaybackSession>(it.toString())
cb(playbackSession) cb(playbackSession)
} }

View file

@ -26,6 +26,7 @@ export default {
isSleepTimerRunning: false, isSleepTimerRunning: false,
sleepTimerEndTime: 0, sleepTimerEndTime: 0,
sleepTimeRemaining: 0, sleepTimeRemaining: 0,
onLocalMediaProgressUpdateListener: null,
onSleepTimerEndedListener: null, onSleepTimerEndedListener: null,
onSleepTimerSetListener: null, onSleepTimerSetListener: null,
sleepInterval: null, sleepInterval: null,
@ -174,9 +175,14 @@ export default {
.catch((error) => { .catch((error) => {
console.error('Failed', error) console.error('Failed', error)
}) })
},
onLocalMediaProgressUpdate(localMediaProgress) {
console.log('Got local media progress update', localMediaProgress.progress, JSON.stringify(localMediaProgress))
this.$store.commit('globals/updateLocalMediaProgress', localMediaProgress)
} }
}, },
mounted() { mounted() {
this.onLocalMediaProgressUpdateListener = AbsAudioPlayer.addListener('onLocalMediaProgressUpdate', this.onLocalMediaProgressUpdate)
this.onSleepTimerEndedListener = AbsAudioPlayer.addListener('onSleepTimerEnded', this.onSleepTimerEnded) this.onSleepTimerEndedListener = AbsAudioPlayer.addListener('onSleepTimerEnded', this.onSleepTimerEnded)
this.onSleepTimerSetListener = AbsAudioPlayer.addListener('onSleepTimerSet', this.onSleepTimerSet) this.onSleepTimerSetListener = AbsAudioPlayer.addListener('onSleepTimerSet', this.onSleepTimerSet)
@ -189,6 +195,7 @@ export default {
this.$store.commit('user/addSettingsListener', { id: 'streamContainer', meth: this.settingsUpdated }) this.$store.commit('user/addSettingsListener', { id: 'streamContainer', meth: this.settingsUpdated })
}, },
beforeDestroy() { beforeDestroy() {
if (this.onLocalMediaProgressUpdateListener) this.onLocalMediaProgressUpdateListener.remove()
if (this.onSleepTimerEndedListener) this.onSleepTimerEndedListener.remove() if (this.onSleepTimerEndedListener) this.onSleepTimerEndedListener.remove()
if (this.onSleepTimerSetListener) this.onSleepTimerSetListener.remove() if (this.onSleepTimerSetListener) this.onSleepTimerSetListener.remove()

View file

@ -207,6 +207,7 @@ export default {
return null return null
}, },
userProgress() { userProgress() {
if (this.isLocal) return this.store.getters['globals/getLocalMediaProgressById'](this.libraryItemId)
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId) return this.store.getters['user/getUserMediaProgress'](this.libraryItemId)
}, },
userProgressPercent() { userProgressPercent() {

View file

@ -123,6 +123,7 @@ export default {
this.error = null this.error = null
this.serverConfig = { this.serverConfig = {
address: null, address: null,
userId: null,
username: null username: null
} }
}, },
@ -160,6 +161,7 @@ export default {
this.deviceData.serverConnectionConfigs = this.deviceData.serverConnectionConfigs.filter((scc) => scc.id != this.serverConfig.id) this.deviceData.serverConnectionConfigs = this.deviceData.serverConnectionConfigs.filter((scc) => scc.id != this.serverConfig.id)
this.serverConfig = { this.serverConfig = {
address: null, address: null,
userId: null,
username: null username: null
} }
this.password = null this.password = null
@ -266,6 +268,7 @@ export default {
this.$store.commit('libraries/setCurrentLibrary', userDefaultLibraryId) this.$store.commit('libraries/setCurrentLibrary', userDefaultLibraryId)
} }
this.serverConfig.userId = user.id
this.serverConfig.token = user.token this.serverConfig.token = user.token
var serverConnectionConfig = await this.$db.setServerConnectionConfig(this.serverConfig) var serverConnectionConfig = await this.$db.setServerConnectionConfig(this.serverConfig)

View file

@ -144,15 +144,6 @@ export default {
// } // }
// }) // })
// }, // },
async initMediaStore() {
// Request and setup listeners for media files on native
// AbsDownloader.addListener('onItemDownloadUpdate', (data) => {
// this.onItemDownloadUpdate(data)
// })
// AbsDownloader.addListener('onItemDownloadComplete', (data) => {
// this.onItemDownloadComplete(data)
// })
},
async loadSavedSettings() { async loadSavedSettings() {
var userSavedServerSettings = await this.$localStore.getServerSettings() var userSavedServerSettings = await this.$localStore.getServerSettings()
if (userSavedServerSettings) { if (userSavedServerSettings) {
@ -266,9 +257,9 @@ export default {
await this.attemptConnection() await this.attemptConnection()
} }
this.$store.dispatch('globals/loadLocalMediaProgress')
this.checkForUpdate() this.checkForUpdate()
this.loadSavedSettings() this.loadSavedSettings()
this.initMediaStore()
} }
}, },
beforeDestroy() { beforeDestroy() {

View file

@ -164,6 +164,7 @@ export default {
return this.$store.getters['user/getToken'] return this.$store.getters['user/getToken']
}, },
userItemProgress() { userItemProgress() {
if (this.isLocal) return this.$store.getters['globals/getLocalMediaProgressById'](this.libraryItemId)
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId) return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId)
}, },
userIsFinished() { userIsFinished() {
@ -238,17 +239,22 @@ export default {
}) })
if (value) { if (value) {
this.resettingProgress = true this.resettingProgress = true
this.$axios if (this.isLocal) {
.$delete(`/api/me/progress/${this.libraryItemId}`) await this.$db.removeLocalMediaProgress(this.libraryItemId)
.then(() => { this.$store.commit('globals/removeLocalMediaProgress', this.libraryItemId)
console.log('Progress reset complete') } else {
this.$toast.success(`Your progress was reset`) await this.$axios
this.resettingProgress = false .$delete(`/api/me/progress/${this.libraryItemId}`)
}) .then(() => {
.catch((error) => { console.log('Progress reset complete')
console.error('Progress reset failed', error) this.$toast.success(`Your progress was reset`)
this.resettingProgress = false })
}) .catch((error) => {
console.error('Progress reset failed', error)
})
}
this.resettingProgress = false
} }
}, },
itemUpdated(libraryItem) { itemUpdated(libraryItem) {

View file

@ -13,7 +13,7 @@ class AbsDatabaseWeb extends WebPlugin {
const deviceData = { const deviceData = {
serverConnectionConfigs: [], serverConnectionConfigs: [],
lastServerConnectionConfigId: null, lastServerConnectionConfigId: null,
localLibraryItemIdPlaying: null currentLocalPlaybackSession: null
} }
return deviceData return deviceData
} }
@ -26,6 +26,7 @@ class AbsDatabaseWeb extends WebPlugin {
deviceData.lastServerConnectionConfigId = ssc.id deviceData.lastServerConnectionConfigId = ssc.id
ssc.name = `${ssc.address} (${serverConnectionConfig.username})` ssc.name = `${ssc.address} (${serverConnectionConfig.username})`
ssc.token = serverConnectionConfig.token ssc.token = serverConnectionConfig.token
ssc.userId = serverConnectionConfig.userId
ssc.username = serverConnectionConfig.username ssc.username = serverConnectionConfig.username
localStorage.setItem('device', JSON.stringify(deviceData)) localStorage.setItem('device', JSON.stringify(deviceData))
} else { } else {
@ -33,6 +34,7 @@ class AbsDatabaseWeb extends WebPlugin {
id: encodeURIComponent(Buffer.from(`${serverConnectionConfig.address}@${serverConnectionConfig.username}`).toString('base64')), id: encodeURIComponent(Buffer.from(`${serverConnectionConfig.address}@${serverConnectionConfig.username}`).toString('base64')),
index: deviceData.serverConnectionConfigs.length, index: deviceData.serverConnectionConfigs.length,
name: `${serverConnectionConfig.address} (${serverConnectionConfig.username})`, name: `${serverConnectionConfig.address} (${serverConnectionConfig.username})`,
userId: serverConnectionConfig.userId,
username: serverConnectionConfig.username, username: serverConnectionConfig.username,
address: serverConnectionConfig.address, address: serverConnectionConfig.address,
token: serverConnectionConfig.token token: serverConnectionConfig.token
@ -62,7 +64,7 @@ class AbsDatabaseWeb extends WebPlugin {
// //
async getLocalFolders() { async getLocalFolders() {
return { return {
folders: [ value: [
{ {
id: 'test1', id: 'test1',
name: 'Audiobooks', name: 'Audiobooks',
@ -76,11 +78,11 @@ class AbsDatabaseWeb extends WebPlugin {
} }
} }
async getLocalFolder({ folderId }) { async getLocalFolder({ folderId }) {
return this.getLocalFolders().then((data) => data.folders[0]) return this.getLocalFolders().then((data) => data.value[0])
} }
async getLocalLibraryItems(payload) { async getLocalLibraryItems(payload) {
return { return {
localLibraryItems: [{ value: [{
id: 'local_test', id: 'local_test',
libraryItemId: 'test34', libraryItemId: 'test34',
folderId: 'test1', folderId: 'test1',
@ -133,10 +135,36 @@ class AbsDatabaseWeb extends WebPlugin {
return this.getLocalLibraryItems() return this.getLocalLibraryItems()
} }
async getLocalLibraryItem({ id }) { async getLocalLibraryItem({ id }) {
return this.getLocalLibraryItems().then((data) => data.localLibraryItems[0]) return this.getLocalLibraryItems().then((data) => data.value[0])
} }
async getLocalLibraryItemByLLId({ libraryItemId }) { async getLocalLibraryItemByLLId({ libraryItemId }) {
return this.getLocalLibraryItems().then((data) => data.localLibraryItems.find(lli => lli.libraryItemId == libraryItemId)) return this.getLocalLibraryItems().then((data) => data.value.find(lli => lli.libraryItemId == libraryItemId))
}
async getAllLocalMediaProgress() {
return {
value: [
{
id: 'local_test',
localLibraryItemId: 'local_test',
episodeId: null,
duration: 100,
progress: 0.5,
currentTime: 50,
isFinished: false,
lastUpdate: 394089090,
startedAt: 239048209,
finishedAt: null,
// For local lib items from server to support server sync
// var serverConnectionConfigId:String?,
// var serverAddress:String?,
// var serverUserId:String?,
// var libraryItemId:String?
}
]
}
}
async removeLocalMediaProgress({ localMediaProgressId }) {
return null
} }
} }

View file

@ -52,13 +52,7 @@ class DbService {
} }
getLocalFolders() { getLocalFolders() {
return AbsDatabase.getLocalFolders().then((data) => { return AbsDatabase.getLocalFolders().then((data) => data.value).catch((error) => {
console.log('Loaded local folders', JSON.stringify(data))
if (data.folders && typeof data.folders == 'string') {
return JSON.parse(data.folders)
}
return data.folders
}).catch((error) => {
console.error('Failed to load', error) console.error('Failed to load', error)
return null return null
}) })
@ -72,23 +66,11 @@ class DbService {
} }
getLocalLibraryItemsInFolder(folderId) { getLocalLibraryItemsInFolder(folderId) {
return AbsDatabase.getLocalLibraryItemsInFolder({ folderId }).then((data) => { return AbsDatabase.getLocalLibraryItemsInFolder({ folderId }).then((data) => data.value)
console.log('Loaded local library items in folder', JSON.stringify(data))
if (data.localLibraryItems && typeof data.localLibraryItems == 'string') {
return JSON.parse(data.localLibraryItems)
}
return data.localLibraryItems
})
} }
getLocalLibraryItems(mediaType = null) { getLocalLibraryItems(mediaType = null) {
return AbsDatabase.getLocalLibraryItems({ mediaType }).then((data) => { return AbsDatabase.getLocalLibraryItems({ mediaType }).then((data) => data.value)
console.log('Loaded all local media items', JSON.stringify(data))
if (data.localLibraryItems && typeof data.localLibraryItems == 'string') {
return JSON.parse(data.localLibraryItems)
}
return data.localLibraryItems
})
} }
getLocalLibraryItem(id) { getLocalLibraryItem(id) {
@ -98,6 +80,14 @@ class DbService {
getLocalLibraryItemByLLId(libraryItemId) { getLocalLibraryItemByLLId(libraryItemId) {
return AbsDatabase.getLocalLibraryItemByLLId({ libraryItemId }) return AbsDatabase.getLocalLibraryItemByLLId({ libraryItemId })
} }
getAllLocalMediaProgress() {
return AbsDatabase.getAllLocalMediaProgress().then((data) => data.value)
}
removeLocalMediaProgress(localMediaProgressId) {
return AbsDatabase.removeLocalMediaProgress({ localMediaProgressId })
}
} }
export default ({ app, store }, inject) => { export default ({ app, store }, inject) => {

View file

@ -1,7 +1,8 @@
export const state = () => ({ export const state = () => ({
itemDownloads: [], itemDownloads: [],
bookshelfListView: false, bookshelfListView: false,
series: null series: null,
localMediaProgress: []
}) })
export const getters = { export const getters = {
@ -25,11 +26,21 @@ export const getters = {
var url = new URL(`/api/items/${libraryItem.id}/cover`, rootGetters['user/getServerAddress']) var url = new URL(`/api/items/${libraryItem.id}/cover`, rootGetters['user/getServerAddress'])
return `${url}?token=${userToken}&ts=${lastUpdate}` return `${url}?token=${userToken}&ts=${lastUpdate}`
},
getLocalMediaProgressById: (state) => (localLibraryItemId, episodeId = null) => {
return state.localMediaProgress.find(lmp => {
if (episodeId != null && lmp.episodeId != episodeId) return false
return lmp.localLibraryItemId == localLibraryItemId
})
} }
} }
export const actions = { export const actions = {
async loadLocalMediaProgress({ state, commit }) {
var mediaProgress = await this.$db.getAllLocalMediaProgress()
console.log('Got all local media progress', JSON.stringify(mediaProgress))
commit('setLocalMediaProgress', mediaProgress)
}
} }
export const mutations = { export const mutations = {
@ -49,5 +60,22 @@ export const mutations = {
}, },
setSeries(state, val) { setSeries(state, val) {
state.series = val state.series = val
},
setLocalMediaProgress(state, val) {
state.localMediaProgress = val
},
updateLocalMediaProgress(state, prog) {
if (!prog || !prog.id) {
return
}
var index = state.localMediaProgress.findIndex(lmp => lmp.id == prog.id)
if (index >= 0) {
state.localMediaProgress.splice(index, 1, prog)
} else {
state.localMediaProgress.push(prog)
}
},
removeLocalMediaProgress(state, id) {
state.localMediaProgress = state.localMediaProgress.filter(lmp => lmp.id != id)
} }
} }