mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-13 15:34:50 +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:
parent
526fca98b9
commit
d9e4469089
23 changed files with 295 additions and 118 deletions
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)}")
|
||||||
|
|
|
@ -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
|
||||||
//
|
//
|
||||||
|
|
|
@ -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 ->
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Add a link
Reference in a new issue