diff --git a/android/app/src/main/java/com/audiobookshelf/app/data/AudioProbeResult.kt b/android/app/src/main/java/com/audiobookshelf/app/data/AudioProbeResult.kt index ac3a9548..7e3ed63b 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/data/AudioProbeResult.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/data/AudioProbeResult.kt @@ -52,7 +52,7 @@ data class AudioProbeFormat( val duration:Double, val size:Long, val bit_rate:Double, - val tags:AudioProbeFormatTags + val tags:AudioProbeFormatTags? ) @JsonIgnoreProperties(ignoreUnknown = true) @@ -63,8 +63,8 @@ class AudioProbeResult ( val duration get() = format.duration val size get() = format.size - val title get() = format.tags.title ?: format.filename.split("/").last() - val artist get() = format.tags.artist ?: "" + val title get() = format.tags?.title ?: format.filename.split("/").last() + val artist get() = format.tags?.artist ?: "" @JsonIgnore fun getBookChapters(): List { diff --git a/android/app/src/main/java/com/audiobookshelf/app/data/DataClasses.kt b/android/app/src/main/java/com/audiobookshelf/app/data/DataClasses.kt index b87fa0dd..65be49a0 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/data/DataClasses.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/data/DataClasses.kt @@ -25,7 +25,8 @@ data class LibraryItem( var isInvalid:Boolean, var mediaType:String, var media:MediaType, - var libraryFiles:MutableList? + var libraryFiles:MutableList?, + var userMediaProgress:MediaProgress? // Only included when requesting library item with progress (for downloads) ) : LibraryItemWrapper() { @get:JsonIgnore val title get() = media.metadata.title @@ -131,7 +132,7 @@ class Podcast( } } @JsonIgnore - fun addEpisode(audioTrack:AudioTrack, episode:PodcastEpisode) { + fun addEpisode(audioTrack:AudioTrack, episode:PodcastEpisode):PodcastEpisode { val newEpisode = PodcastEpisode("local_" + episode.id,episodes?.size ?: 0 + 1,episode.episode,episode.episodeType,episode.title,episode.subtitle,episode.description,null,audioTrack,audioTrack.duration,0, episode.id) episodes?.add(newEpisode) @@ -140,6 +141,7 @@ class Podcast( it.index = index index++ } + return newEpisode } // Used for FolderScanner local podcast item to get copy of Podcast excluding episodes @@ -355,3 +357,17 @@ data class BookChapter( var end:Double, var title:String? ) + +@JsonIgnoreProperties(ignoreUnknown = true) +data class MediaProgress( + var id:String, + var libraryItemId:String, + var episodeId:String?, + var duration:Double, // seconds + var progress:Double, // 0 to 1 + var currentTime:Double, + var isFinished:Boolean, + var lastUpdate:Long, + var startedAt:Long, + var finishedAt:Long? +) diff --git a/android/app/src/main/java/com/audiobookshelf/app/data/LocalLibraryItem.kt b/android/app/src/main/java/com/audiobookshelf/app/data/LocalLibraryItem.kt index f44c908e..04624a06 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/data/LocalLibraryItem.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/data/LocalLibraryItem.kt @@ -51,8 +51,8 @@ data class LocalLibraryItem( @JsonIgnore fun updateFromScan(audioTracks:MutableList, _localFiles:MutableList) { - media.setAudioTracks(audioTracks) localFiles = _localFiles + media.setAudioTracks(audioTracks) if (coverContentUrl != null) { if (localFiles.find { it.contentUrl == coverContentUrl } == null) { @@ -66,24 +66,25 @@ data class LocalLibraryItem( @JsonIgnore fun getPlaybackSession(episode:PodcastEpisode?):PlaybackSession { - var localEpisodeId = episode?.id - var sessionId = "play_local_${UUID.randomUUID()}" + val localEpisodeId = episode?.id + val sessionId = "play_local_${UUID.randomUUID()}" + // Get current progress for local media val mediaProgressId = if (localEpisodeId.isNullOrEmpty()) id else "$id-$localEpisodeId" - var mediaProgress = DeviceManager.dbManager.getLocalMediaProgress(mediaProgressId) - var currentTime = mediaProgress?.currentTime ?: 0.0 + val mediaProgress = DeviceManager.dbManager.getLocalMediaProgress(mediaProgressId) + val currentTime = mediaProgress?.currentTime ?: 0.0 // TODO: Clean up add mediaType methods for displayTitle and displayAuthor - var mediaMetadata = media.metadata - var chapters = if (mediaType == "book") (media as Book).chapters else mutableListOf() + val mediaMetadata = media.metadata + val chapters = if (mediaType == "book") (media as Book).chapters else mutableListOf() var audioTracks = media.getAudioTracks() as MutableList - var authorName = mediaMetadata.getAuthorDisplayName() + val authorName = mediaMetadata.getAuthorDisplayName() if (episode != null) { // Get podcast episode audio track episode.audioTrack?.let { at -> mutableListOf(at) }?.let { tracks -> audioTracks = tracks } Log.d("LocalLibraryItem", "getPlaybackSession: Got podcast episode audio track ${audioTracks.size}") } - var dateNow = System.currentTimeMillis() + val dateNow = System.currentTimeMillis() return PlaybackSession(sessionId,serverUserId,libraryItemId,episode?.serverEpisodeId, mediaType, mediaMetadata, chapters ?: mutableListOf(), mediaMetadata.title, authorName,null,getDuration(),PLAYMETHOD_LOCAL,dateNow,0L,0L, audioTracks,currentTime,null,this,localEpisodeId,serverConnectionConfigId, serverAddress, "exo-player") } diff --git a/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt b/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt index 773a5c04..84b919e8 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt @@ -57,6 +57,8 @@ class PlaybackSession( @get:JsonIgnore val currentTimeMs get() = (currentTime * 1000L).toLong() @get:JsonIgnore + val totalDurationMs get() = (getTotalDuration() * 1000L).toLong() + @get:JsonIgnore val localLibraryItemId get() = localLibraryItem?.id ?: "" @get:JsonIgnore val localMediaProgressId get() = if (episodeId.isNullOrEmpty()) localLibraryItemId else "$localLibraryItemId-$localEpisodeId" diff --git a/android/app/src/main/java/com/audiobookshelf/app/device/FolderScanner.kt b/android/app/src/main/java/com/audiobookshelf/app/device/FolderScanner.kt index ec159a43..6142b625 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/device/FolderScanner.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/device/FolderScanner.kt @@ -19,6 +19,7 @@ class FolderScanner(var ctx: Context) { private val tag = "FolderScanner" var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature()) + data class DownloadItemScanResult(val localLibraryItem:LocalLibraryItem, var localMediaProgress:LocalMediaProgress?) private fun getLocalLibraryItemId(mediaItemId:String):String { return "local_" + DeviceManager.getBase64Id(mediaItemId) @@ -36,7 +37,7 @@ class FolderScanner(var ctx: Context) { } } - var df: DocumentFile? = DocumentFileCompat.fromUri(ctx, Uri.parse(localFolder.contentUrl)) + val df: DocumentFile? = DocumentFileCompat.fromUri(ctx, Uri.parse(localFolder.contentUrl)) if (df == null) { Log.e(tag, "Folder Doc File Invalid $localFolder.contentUrl") @@ -49,7 +50,7 @@ class FolderScanner(var ctx: Context) { var mediaItemsUpToDate = 0 // Search for files in media item folder - var foldersFound = df.search(false, DocumentFileType.FOLDER) + val foldersFound = df.search(false, DocumentFileType.FOLDER) // Match folders found with local library items already saved in db var existingLocalLibraryItems = DeviceManager.dbManager.getLocalLibraryItemsInFolder(localFolder.id) @@ -57,7 +58,7 @@ class FolderScanner(var ctx: Context) { // Remove existing items no longer there existingLocalLibraryItems = existingLocalLibraryItems.filter { lli -> Log.d(tag, "scanForMediaItems Checking Existing LLI ${lli.id}") - var fileFound = foldersFound.find { f -> lli.id == getLocalLibraryItemId(f.id) } + val fileFound = foldersFound.find { f -> lli.id == getLocalLibraryItemId(f.id) } if (fileFound == null) { Log.d(tag, "Existing local library item is no longer in file system ${lli.media.metadata.title}") DeviceManager.dbManager.removeLocalLibraryItem(lli.id) @@ -68,9 +69,9 @@ class FolderScanner(var ctx: Context) { foldersFound.forEach { itemFolder -> Log.d(tag, "Iterating over Folder Found ${itemFolder.name} | ${itemFolder.getSimplePath(ctx)} | URI: ${itemFolder.uri}") - var existingItem = existingLocalLibraryItems.find { emi -> emi.id == getLocalLibraryItemId(itemFolder.id) } + val existingItem = existingLocalLibraryItems.find { emi -> emi.id == getLocalLibraryItemId(itemFolder.id) } - var result = scanLibraryItemFolder(itemFolder, localFolder, existingItem, forceAudioProbe) + val result = scanLibraryItemFolder(itemFolder, localFolder, existingItem, forceAudioProbe) if (result == ItemScanResult.REMOVED) mediaItemsRemoved++ else if (result == ItemScanResult.UPDATED) mediaItemsUpdated++ @@ -90,25 +91,23 @@ class FolderScanner(var ctx: Context) { } fun scanLibraryItemFolder(itemFolder:DocumentFile, localFolder:LocalFolder, existingItem:LocalLibraryItem?, forceAudioProbe:Boolean):ItemScanResult { - var itemFolderName = itemFolder.name ?: "" - var itemId = getLocalLibraryItemId(itemFolder.id) + val itemFolderName = itemFolder.name ?: "" + val itemId = getLocalLibraryItemId(itemFolder.id) - var existingLocalFiles = existingItem?.localFiles ?: mutableListOf() - var existingAudioTracks = existingItem?.media?.getAudioTracks() ?: mutableListOf() + val existingLocalFiles = existingItem?.localFiles ?: mutableListOf() + val existingAudioTracks = existingItem?.media?.getAudioTracks() ?: mutableListOf() var isNewOrUpdated = existingItem == null - var audioTracks = mutableListOf() - var localFiles = mutableListOf() + val audioTracks = mutableListOf() + val localFiles = mutableListOf() var index = 1 var startOffset = 0.0 var coverContentUrl:String? = null var coverAbsolutePath:String? = null -// itemFolder.search(false, DocumentFileType.FILE, arrayOf("audio")) + val filesInFolder = itemFolder.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*", "video/mp4")) - var filesInFolder = itemFolder.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*", "video/mp4")) - - var existingLocalFilesRemoved = existingLocalFiles.filter { elf -> + val existingLocalFilesRemoved = existingLocalFiles.filter { elf -> filesInFolder.find { fif -> DeviceManager.getBase64Id(fif.id) == elf.id } == null // File was not found in media item folder } if (existingLocalFilesRemoved.isNotEmpty()) { @@ -117,14 +116,14 @@ class FolderScanner(var ctx: Context) { } filesInFolder.forEach { file -> - var mimeType = file.mimeType ?: "" - var filename = file.name ?: "" - var isAudio = mimeType.startsWith("audio") || mimeType == "video/mp4" + val mimeType = file.mimeType ?: "" + val filename = file.name ?: "" + val isAudio = mimeType.startsWith("audio") || mimeType == "video/mp4" Log.d(tag, "Found $mimeType file $filename in folder $itemFolderName") - var localFileId = DeviceManager.getBase64Id(file.id) + val localFileId = DeviceManager.getBase64Id(file.id) - var localFile = LocalFile(localFileId,filename,file.uri.toString(),file.getBasePath(ctx), file.getAbsolutePath(ctx),file.getSimplePath(ctx),mimeType,file.length()) + val localFile = LocalFile(localFileId,filename,file.uri.toString(),file.getBasePath(ctx), file.getAbsolutePath(ctx),file.getSimplePath(ctx),mimeType,file.length()) localFiles.add(localFile) Log.d(tag, "File attributes Id:${localFileId}|ContentUrl:${localFile.contentUrl}|isDownloadsDocument:${file.isDownloadsDocument}") @@ -132,7 +131,7 @@ class FolderScanner(var ctx: Context) { if (isAudio) { var audioTrackToAdd:AudioTrack? = null - var existingAudioTrack = existingAudioTracks.find { eat -> eat.localFileId == localFileId } + val existingAudioTrack = existingAudioTracks.find { eat -> eat.localFileId == localFileId } if (existingAudioTrack != null) { // Update existing audio track if (existingAudioTrack.index != index) { Log.d(tag, "scanLibraryItemFolder Updating Audio track index from ${existingAudioTrack.index} to $index") @@ -150,7 +149,7 @@ class FolderScanner(var ctx: Context) { Log.d(tag, "scanLibraryItemFolder Scanning Audio File Path ${localFile.absolutePath} | ForceAudioProbe=${forceAudioProbe}") // TODO: Make asynchronous - var audioProbeResult = probeAudioFile(localFile.absolutePath) + val audioProbeResult = probeAudioFile(localFile.absolutePath) if (existingAudioTrack != null) { // Update audio probe data on existing audio track @@ -172,7 +171,7 @@ class FolderScanner(var ctx: Context) { index++ audioTracks.add(audioTrackToAdd) } else { - var existingLocalFile = existingLocalFiles.find { elf -> elf.id == localFileId } + val existingLocalFile = existingLocalFiles.find { elf -> elf.id == localFileId } if (existingLocalFile == null) { Log.d(tag, "scanLibraryItemFolder new local file found ${localFile.absolutePath}") @@ -209,8 +208,8 @@ class FolderScanner(var ctx: Context) { return ItemScanResult.UPDATED } else if (audioTracks.isNotEmpty()) { Log.d(tag, "Found local media item named $itemFolderName with ${audioTracks.size} tracks and ${localFiles.size} local files") - 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() + val localMediaItem = LocalMediaItem(itemId, itemFolderName, localFolder.mediaType, localFolder.id, itemFolder.uri.toString(), itemFolder.getSimplePath(ctx), itemFolder.getBasePath(ctx), itemFolder.getAbsolutePath(ctx),audioTracks,localFiles,coverContentUrl,coverAbsolutePath) + val localLibraryItem = localMediaItem.getLocalLibraryItem() DeviceManager.dbManager.saveLocalLibraryItem(localLibraryItem) return ItemScanResult.ADDED } else { @@ -219,9 +218,9 @@ class FolderScanner(var ctx: Context) { } // Scan item after download and create local library item - fun scanDownloadItem(downloadItem: AbsDownloader.DownloadItem):LocalLibraryItem? { - var folderDf = DocumentFileCompat.fromUri(ctx, Uri.parse(downloadItem.localFolder.contentUrl)) - var foldersFound = folderDf?.search(false, DocumentFileType.FOLDER) ?: mutableListOf() + fun scanDownloadItem(downloadItem: AbsDownloader.DownloadItem):DownloadItemScanResult? { + val folderDf = DocumentFileCompat.fromUri(ctx, Uri.parse(downloadItem.localFolder.contentUrl)) + val foldersFound = folderDf?.search(false, DocumentFileType.FOLDER) ?: mutableListOf() var itemFolderId = "" var itemFolderUrl = "" @@ -240,20 +239,21 @@ class FolderScanner(var ctx: Context) { Log.d(tag, "scanDownloadItem failed to find media folder") return null } - var df: DocumentFile? = DocumentFileCompat.fromUri(ctx, Uri.parse(itemFolderUrl)) + val df: DocumentFile? = DocumentFileCompat.fromUri(ctx, Uri.parse(itemFolderUrl)) if (df == null) { Log.e(tag, "Folder Doc File Invalid ${downloadItem.itemFolderPath}") return null } - var localLibraryItemId = getLocalLibraryItemId(itemFolderId) + val localLibraryItemId = getLocalLibraryItemId(itemFolderId) Log.d(tag, "scanDownloadItem starting for ${downloadItem.itemFolderPath} | ${df.uri} | Item Folder Id:$itemFolderId | LLI Id:$localLibraryItemId") // Search for files in media item folder - var filesFound = df.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*", "video/mp4")) + val filesFound = df.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*", "video/mp4")) Log.d(tag, "scanDownloadItem ${filesFound.size} files found in ${downloadItem.itemFolderPath}") + var localEpisodeId:String? = null var localLibraryItem:LocalLibraryItem? = null if (downloadItem.mediaType == "book") { localLibraryItem = LocalLibraryItem(localLibraryItemId, downloadItem.localFolder.id, itemFolderBasePath, itemFolderAbsolutePath, itemFolderUrl, false, downloadItem.mediaType, downloadItem.media.getLocalCopy(), mutableListOf(), null, null, true, downloadItem.serverConnectionConfigId, downloadItem.serverAddress, downloadItem.serverUserId, downloadItem.libraryItemId) @@ -266,10 +266,10 @@ class FolderScanner(var ctx: Context) { } } - var audioTracks:MutableList = mutableListOf() + val audioTracks:MutableList = mutableListOf() filesFound.forEach { docFile -> - var itemPart = downloadItem.downloadItemParts.find { itemPart -> + val itemPart = downloadItem.downloadItemParts.find { itemPart -> itemPart.filename == docFile.name } if (itemPart == null) { @@ -277,31 +277,32 @@ class FolderScanner(var ctx: Context) { Log.e(tag, "scanDownloadItem: Item part not found for doc file ${docFile.name} | ${docFile.getAbsolutePath(ctx)} | ${docFile.uri}") } } else if (itemPart.audioTrack != null) { // Is audio track - var audioTrackFromServer = itemPart.audioTrack + val audioTrackFromServer = itemPart.audioTrack Log.d(tag, "scanDownloadItem: Audio Track from Server index = ${audioTrackFromServer?.index}") - var localFileId = DeviceManager.getBase64Id(docFile.id) - var localFile = LocalFile(localFileId,docFile.name,docFile.uri.toString(),docFile.getBasePath(ctx),docFile.getAbsolutePath(ctx),docFile.getSimplePath(ctx),docFile.mimeType,docFile.length()) + val localFileId = DeviceManager.getBase64Id(docFile.id) + val localFile = LocalFile(localFileId,docFile.name,docFile.uri.toString(),docFile.getBasePath(ctx),docFile.getAbsolutePath(ctx),docFile.getSimplePath(ctx),docFile.mimeType,docFile.length()) localLibraryItem.localFiles.add(localFile) // TODO: Make asynchronous - var audioProbeResult = probeAudioFile(localFile.absolutePath) + val audioProbeResult = probeAudioFile(localFile.absolutePath) // Create new audio track - var track = AudioTrack(audioTrackFromServer?.index ?: -1, audioTrackFromServer?.startOffset ?: 0.0, audioProbeResult.duration, localFile.filename ?: "", localFile.contentUrl, localFile.mimeType ?: "", null, true, localFileId, audioProbeResult, audioTrackFromServer?.index ?: -1) + val track = AudioTrack(audioTrackFromServer?.index ?: -1, audioTrackFromServer?.startOffset ?: 0.0, audioProbeResult.duration, localFile.filename ?: "", localFile.contentUrl, localFile.mimeType ?: "", null, true, localFileId, audioProbeResult, audioTrackFromServer?.index ?: -1) audioTracks.add(track) Log.d(tag, "scanDownloadItem: Created Audio Track with index ${track.index} from local file ${localFile.absolutePath}") // Add podcast episodes to library itemPart.episode?.let { podcastEpisode -> - var podcast = localLibraryItem.media as Podcast - podcast.addEpisode(track, podcastEpisode) + val podcast = localLibraryItem.media as Podcast + var newEpisode = podcast.addEpisode(track, podcastEpisode) + localEpisodeId = newEpisode.id Log.d(tag, "scanDownloadItem: Added episode to podcast ${podcastEpisode.title} ${track.title} | Track index: ${podcastEpisode.audioTrack?.index}") } } else { // Cover image - var localFileId = DeviceManager.getBase64Id(docFile.id) - var localFile = LocalFile(localFileId,docFile.name,docFile.uri.toString(),docFile.getBasePath(ctx),docFile.getAbsolutePath(ctx),docFile.getSimplePath(ctx),docFile.mimeType,docFile.length()) + val localFileId = DeviceManager.getBase64Id(docFile.id) + val localFile = LocalFile(localFileId,docFile.name,docFile.uri.toString(),docFile.getBasePath(ctx),docFile.getAbsolutePath(ctx),docFile.getSimplePath(ctx),docFile.mimeType,docFile.length()) localLibraryItem.coverAbsolutePath = localFile.absolutePath localLibraryItem.coverContentUrl = localFile.contentUrl @@ -332,9 +333,36 @@ class FolderScanner(var ctx: Context) { localLibraryItem.media.setAudioTracks(audioTracks) } + val downloadItemScanResult = DownloadItemScanResult(localLibraryItem,null) + + // If library item had media progress then make local media progress and save + downloadItem.userMediaProgress?.let { mediaProgress -> + val localMediaProgressId = if (downloadItem.episodeId.isNullOrEmpty()) localLibraryItemId else "$localLibraryItemId-$localEpisodeId" + val newLocalMediaProgress = LocalMediaProgress( + id = localMediaProgressId, + localLibraryItemId = localLibraryItemId, + localEpisodeId = localEpisodeId, + duration = mediaProgress.duration, + progress = mediaProgress.progress, + currentTime = mediaProgress.currentTime, + isFinished = false, + lastUpdate = mediaProgress.lastUpdate, + startedAt = mediaProgress.startedAt, + finishedAt = mediaProgress.finishedAt, + serverConnectionConfigId = downloadItem.serverConnectionConfigId, + serverAddress = downloadItem.serverAddress, + serverUserId = downloadItem.serverUserId, + libraryItemId = downloadItem.libraryItemId, + episodeId = downloadItem.episodeId) + Log.d(tag, "scanLibraryItemFolder: Saving local media progress ${newLocalMediaProgress.id} at progress ${newLocalMediaProgress.progress}") + DeviceManager.dbManager.saveLocalMediaProgress(newLocalMediaProgress) + + downloadItemScanResult.localMediaProgress = newLocalMediaProgress + } + DeviceManager.dbManager.saveLocalLibraryItem(localLibraryItem) - return localLibraryItem + return downloadItemScanResult } fun scanLocalLibraryItem(localLibraryItem:LocalLibraryItem, forceAudioProbe:Boolean):LocalLibraryItemScanResult? { diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt index 875ca690..097cd492 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt @@ -454,7 +454,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { } fun getDuration() : Long { - return currentPlayer.duration + return currentPlaybackSession?.totalDurationMs ?: 0L } fun getCurrentBookTitle() : String? { diff --git a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDownloader.kt b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDownloader.kt index 00ebaba9..75c56a96 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDownloader.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDownloader.kt @@ -63,7 +63,10 @@ class AbsDownloader : Plugin() { fun make(filename:String, destinationFile:File, finalDestinationFile:File, itemTitle:String, serverPath:String, localFolder:LocalFolder, audioTrack:AudioTrack?, episode:PodcastEpisode?) :DownloadItemPart { val destinationUri = Uri.fromFile(destinationFile) val finalDestinationUri = Uri.fromFile(finalDestinationFile) - val downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}") + + var downloadUrl = "${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}" + if (serverPath.endsWith("/cover")) downloadUrl += "&format=jpeg" // For cover images force to jpeg + val downloadUri = Uri.parse(downloadUrl) Log.d("DownloadItemPart", "Audio File Destination Uri: $destinationUri | Final Destination Uri: $finalDestinationUri | Download URI $downloadUri") return DownloadItemPart( id = DeviceManager.getBase64Id(finalDestinationFile.absolutePath), @@ -102,6 +105,7 @@ class AbsDownloader : Plugin() { val id: String, val libraryItemId:String, val episodeId:String?, + val userMediaProgress:MediaProgress?, val serverConnectionConfigId:String, val serverAddress:String, val serverUserId:String, @@ -138,7 +142,7 @@ class AbsDownloader : Plugin() { return call.resolve(JSObject("{\"error\":\"Download already started for this media entity\"}")) } - apiHandler.getLibraryItem(libraryItemId) { libraryItem -> + apiHandler.getLibraryItemWithProgress(libraryItemId, episodeId) { libraryItem -> Log.d(tag, "Got library item from server ${libraryItem.id}") val localFolder = DeviceManager.dbManager.getLocalFolder(localFolderId) @@ -188,7 +192,7 @@ class AbsDownloader : Plugin() { val tracks = libraryItem.media.getAudioTracks() Log.d(tag, "Starting library item download with ${tracks.size} tracks") val itemFolderPath = localFolder.absolutePath + "/" + bookTitle - val downloadItem = DownloadItem(libraryItem.id, libraryItem.id, null,DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, libraryItem.media, mutableListOf()) + val downloadItem = DownloadItem(libraryItem.id, libraryItem.id, null, libraryItem.userMediaProgress,DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, libraryItem.media, mutableListOf()) // Create download item part for each audio track tracks.forEach { audioTrack -> @@ -215,7 +219,7 @@ class AbsDownloader : Plugin() { if (downloadItem.downloadItemParts.isNotEmpty()) { // Add cover download item if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) { - val serverPath = "/api/items/${libraryItem.id}/cover?format=jpeg" + val serverPath = "/api/items/${libraryItem.id}/cover" val destinationFilename = "cover.jpg" val destinationFile = File("$tempFolderPath/$destinationFilename") val finalDestinationFile = File("$itemFolderPath/$destinationFilename") @@ -245,7 +249,7 @@ class AbsDownloader : Plugin() { Log.d(tag, "Starting podcast episode download") val itemFolderPath = localFolder.absolutePath + "/" + podcastTitle val downloadItemId = "${libraryItem.id}-${episode?.id}" - val downloadItem = DownloadItem(downloadItemId, libraryItem.id, episode?.id, DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, podcastTitle, libraryItem.media, mutableListOf()) + val downloadItem = DownloadItem(downloadItemId, libraryItem.id, episode?.id, libraryItem.userMediaProgress, DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, podcastTitle, libraryItem.media, mutableListOf()) var serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioTrack?.relPath ?: "")}" var destinationFilename = getFilenameFromRelPath(audioTrack?.relPath ?: "") @@ -266,7 +270,7 @@ class AbsDownloader : Plugin() { downloadItemPart.downloadId = downloadId if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) { - serverPath = "/api/items/${libraryItem.id}/cover?format=jpeg" + serverPath = "/api/items/${libraryItem.id}/cover" destinationFilename = "cover.jpg" destinationFile = File("$tempFolderPath/$destinationFilename") @@ -290,7 +294,7 @@ class AbsDownloader : Plugin() { } } - fun startWatchingDownloads(downloadItem: DownloadItem) { + private fun startWatchingDownloads(downloadItem: DownloadItem) { GlobalScope.launch(Dispatchers.IO) { while (downloadItem.downloadItemParts.find { !it.moved && !it.failed } != null) { // While some item is not completed val numPartsBefore = downloadItem.downloadItemParts.size @@ -306,18 +310,22 @@ class AbsDownloader : Plugin() { delay(500) } - val localLibraryItem = folderScanner.scanDownloadItem(downloadItem) + val downloadItemScanResult = folderScanner.scanDownloadItem(downloadItem) DeviceManager.dbManager.removeDownloadItem(downloadItem.id) downloadQueue.remove(downloadItem) - Log.d(tag, "Item download complete ${downloadItem.itemTitle} | local library item id: ${localLibraryItem?.id} | Items remaining in Queue ${downloadQueue.size}") + Log.d(tag, "Item download complete ${downloadItem.itemTitle} | local library item id: ${downloadItemScanResult?.localLibraryItem?.id} | Items remaining in Queue ${downloadQueue.size}") val jsobj = JSObject() jsobj.put("libraryItemId", downloadItem.id) jsobj.put("localFolderId", downloadItem.localFolder.id) - if (localLibraryItem != null) { + + downloadItemScanResult?.localLibraryItem?.let { localLibraryItem -> jsobj.put("localLibraryItem", JSObject(jacksonMapper.writeValueAsString(localLibraryItem))) } + downloadItemScanResult?.localMediaProgress?.let { localMediaProgress -> + jsobj.put("localMediaProgress", JSObject(jacksonMapper.writeValueAsString(localMediaProgress))) + } notifyListeners("onItemDownloadComplete", jsobj) } } diff --git a/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt b/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt index 7c69d89a..674e6c39 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/server/ApiHandler.kt @@ -88,13 +88,13 @@ class ApiHandler(var ctx:Context) { response.use { if (!it.isSuccessful) throw IOException("Unexpected code $response") - var bodyString = it.body!!.string() + val bodyString = it.body!!.string() if (bodyString == "OK") { cb(JSObject()) } else { var jsonObj = JSObject() if (bodyString.startsWith("[")) { - var array = JSArray(bodyString) + val array = JSArray(bodyString) jsonObj.put("value", array) } else { jsonObj = JSObject(bodyString) @@ -111,7 +111,7 @@ class ApiHandler(var ctx:Context) { getRequest("/api/libraries") { val libraries = mutableListOf() if (it.has("value")) { - var array = it.getJSONArray("value") + val array = it.getJSONArray("value") for (i in 0 until array.length()) { val library = mapper.readValue(array.get(i).toString()) libraries.add(library) @@ -128,11 +128,20 @@ class ApiHandler(var ctx:Context) { } } + fun getLibraryItemWithProgress(libraryItemId:String, episodeId:String?, cb: (LibraryItem) -> Unit) { + var requestUrl = "/api/items/$libraryItemId?expanded=1&include=progress" + if (!episodeId.isNullOrEmpty()) requestUrl += "&episode=$episodeId" + getRequest(requestUrl) { + val libraryItem = jacksonMapper.readValue(it.toString()) + cb(libraryItem) + } + } + fun getLibraryItems(libraryId:String, cb: (List) -> Unit) { getRequest("/api/libraries/$libraryId/items?limit=100&minified=1") { val items = mutableListOf() if (it.has("results")) { - var array = it.getJSONArray("results") + val array = it.getJSONArray("results") for (i in 0 until array.length()) { val item = jacksonMapper.readValue(array.get(i).toString()) items.add(item) @@ -146,11 +155,11 @@ class ApiHandler(var ctx:Context) { getRequest("/api/libraries/$libraryId/personalized") { val items = mutableListOf() if (it.has("value")) { - var array = it.getJSONArray("value") + val array = it.getJSONArray("value") for (i in 0 until array.length()) { - var jsobj = array.get(i) as JSONObject + val jsobj = array.get(i) as JSONObject - var type = jsobj.get("type").toString() + val type = jsobj.get("type").toString() // Only support for podcast and book in android auto if (type == "podcast" || type == "book") { jsobj.put("isLocal", false) @@ -164,7 +173,7 @@ class ApiHandler(var ctx:Context) { } fun playLibraryItem(libraryItemId:String, episodeId:String?, forceTranscode:Boolean, mediaPlayer:String, cb: (PlaybackSession) -> Unit) { - var payload = JSObject() + val payload = JSObject() payload.put("mediaPlayer", mediaPlayer) // Only if direct play fails do we force transcode @@ -181,7 +190,7 @@ class ApiHandler(var ctx:Context) { } fun sendProgressSync(sessionId:String, syncData: MediaProgressSyncData, cb: () -> Unit) { - var payload = JSObject(jacksonMapper.writeValueAsString(syncData)) + val payload = JSObject(jacksonMapper.writeValueAsString(syncData)) postRequest("/api/session/$sessionId/sync", payload) { cb() @@ -189,7 +198,7 @@ class ApiHandler(var ctx:Context) { } fun sendLocalProgressSync(playbackSession:PlaybackSession, cb: () -> Unit) { - var payload = JSObject(jacksonMapper.writeValueAsString(playbackSession)) + val payload = JSObject(jacksonMapper.writeValueAsString(playbackSession)) postRequest("/api/session/local", payload) { cb() @@ -204,15 +213,15 @@ class ApiHandler(var ctx:Context) { } // Get all local media progress connected to items on the current connected server - var localMediaProgress = DeviceManager.dbManager.getAllLocalMediaProgress().filter { + val localMediaProgress = DeviceManager.dbManager.getAllLocalMediaProgress().filter { it.serverConnectionConfigId == DeviceManager.serverConnectionConfig?.id } - var localSyncResultsPayload = LocalMediaProgressSyncResultsPayload(localMediaProgress.size,0, 0) + val localSyncResultsPayload = LocalMediaProgressSyncResultsPayload(localMediaProgress.size,0, 0) if (localMediaProgress.isNotEmpty()) { Log.d(tag, "Sending sync local progress request with ${localMediaProgress.size} progress items") - var payload = JSObject(jacksonMapper.writeValueAsString(LocalMediaProgressSyncPayload(localMediaProgress))) + val payload = JSObject(jacksonMapper.writeValueAsString(LocalMediaProgressSyncPayload(localMediaProgress))) postRequest("/api/me/sync-local-progress", payload) { Log.d(tag, "Media Progress Sync payload $payload - response ${it.toString()}") @@ -242,7 +251,7 @@ class ApiHandler(var ctx:Context) { fun updateMediaProgress(libraryItemId:String,episodeId:String?,updatePayload:JSObject, cb: () -> Unit) { Log.d(tag, "updateMediaProgress $libraryItemId $episodeId $updatePayload") - var endpoint = if(episodeId.isNullOrEmpty()) "/api/me/progress/$libraryItemId" else "/api/me/progress/$libraryItemId/$episodeId" + val endpoint = if(episodeId.isNullOrEmpty()) "/api/me/progress/$libraryItemId" else "/api/me/progress/$libraryItemId/$episodeId" patchRequest(endpoint,updatePayload) { Log.d(tag, "updateMediaProgress patched progress") cb() diff --git a/components/app/Appbar.vue b/components/app/Appbar.vue index ae8fe427..077f7a5a 100644 --- a/components/app/Appbar.vue +++ b/components/app/Appbar.vue @@ -18,8 +18,8 @@ -
- cast +
+ cast
diff --git a/components/app/AudioPlayer.vue b/components/app/AudioPlayer.vue index b9ac1976..c28ebef4 100644 --- a/components/app/AudioPlayer.vue +++ b/components/app/AudioPlayer.vue @@ -634,6 +634,11 @@ export default { this.$nextTick(this.init) }, beforeDestroy() { + if (this.playbackSession) { + console.log('[AudioPlayer] Before destroy closing playback') + this.closePlayback() + } + this.forceCloseDropdownMenu() document.body.removeEventListener('touchstart', this.touchstart) document.body.removeEventListener('touchend', this.touchend) diff --git a/components/widgets/DownloadProgressIndicator.vue b/components/widgets/DownloadProgressIndicator.vue index 9d4ddb08..43bc6d54 100644 --- a/components/widgets/DownloadProgressIndicator.vue +++ b/components/widgets/DownloadProgressIndicator.vue @@ -97,6 +97,11 @@ export default { this.$eventBus.$emit('new-local-library-item', data.localLibraryItem) } + if (data.localMediaProgress) { + console.log('onItemDownloadComplete updating local media progress', data.localMediaProgress.id) + this.$store.commit('globals/updateLocalMediaProgress', data.localMediaProgress) + } + this.$store.commit('globals/removeItemDownload', data.libraryItemId) } }, diff --git a/ios/App/App.xcodeproj/project.pbxproj b/ios/App/App.xcodeproj/project.pbxproj index f69cd213..818f610b 100644 --- a/ios/App/App.xcodeproj/project.pbxproj +++ b/ios/App/App.xcodeproj/project.pbxproj @@ -30,6 +30,7 @@ 504EC3121FED79650016851F /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 504EC3101FED79650016851F /* LaunchScreen.storyboard */; }; 50B271D11FEDC1A000F3C39B /* public in Resources */ = {isa = PBXBuildFile; fileRef = 50B271D01FEDC1A000F3C39B /* public */; }; A084ECDBA7D38E1E42DFC39D /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; }; + C4D0677528106D0C00B8F875 /* DataClasses.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4D0677428106D0C00B8F875 /* DataClasses.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -60,6 +61,7 @@ 50B271D01FEDC1A000F3C39B /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = ""; }; AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; }; AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = ""; }; + C4D0677428106D0C00B8F875 /* DataClasses.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataClasses.swift; sourceTree = ""; }; FC68EB0AF532CFC21C3344DD /* Pods-App.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.debug.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -118,6 +120,7 @@ children = ( 3AD4FCE828043FD7006DB301 /* ServerConnectionConfig.swift */, 3ABF580828059BAE005DFBE5 /* PlaybackSession.swift */, + C4D0677428106D0C00B8F875 /* DataClasses.swift */, 3A90295E280968E700E1D427 /* PlaybackReport.swift */, ); path = models; @@ -309,6 +312,7 @@ 3AD4FCE528043E50006DB301 /* AbsDatabase.swift in Sources */, 3AF197102806E3DC0096F747 /* AbsAudioPlayer.m in Sources */, 3AF1970C2806E2590096F747 /* ApiClient.swift in Sources */, + C4D0677528106D0C00B8F875 /* DataClasses.swift in Sources */, 3AB34055280832720039308B /* PlayerEvents.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/ios/App/Shared/models/DataClasses.swift b/ios/App/Shared/models/DataClasses.swift new file mode 100644 index 00000000..f3e0b2f5 --- /dev/null +++ b/ios/App/Shared/models/DataClasses.swift @@ -0,0 +1,127 @@ +// +// DataClasses.swift +// App +// +// Created by Benonymity on 4/20/22. +// + +import Foundation +import CoreMedia + +struct LibraryItem: Codable { + var id: String + var ino:String + var libraryId: String + var folderId: String + var path: String + var relPath: String + var mtimeMs: Int64 + var ctimeMs: Int64 + var birthtimeMs: Int64 + var addedAt: Int64 + var updatedAt: Int64 + var lastScan: Int64? + var scanVersion: String? + var isMissing: Bool + var isInvalid: Bool + var mediaType: String + var media: MediaType + var libraryFiles: [LibraryFile] +} +struct MediaType: Codable { + var libraryItemId: String? + var metadata: Metadata + var coverPath: String? + var tags: [String]? + var audioFiles: [AudioTrack]? + var chapters: [Chapter]? + var tracks: [AudioTrack]? + var size: Int64? + var duration: Double? + var episodes: [PodcastEpisode]? + var autoDownloadEpisodes: Bool? +} +struct Metadata: Codable { + var title: String + var subtitle: String? + var authors: [Author]? + var narrators: [String]? + var genres: [String] + var publishedYear: String? + var publishedDate: String? + var publisher: String? + var description: String? + var isbn: String? + var asin: String? + var language: String? + var explicit: Bool + var authorName: String? + var authorNameLF: String? + var narratorName: String? + var seriesName: String? + var feedUrl: String? +} +struct PodcastEpisode: Codable { + var id: String + var index: Int + var episode: String? + var episodeType: String? + var title: String + var subtitle: String? + var description: String? + var audioFile: AudioFile? + var audioTrack: AudioTrack? + var duration: Double + var size: Int64 +// var serverEpisodeId: String? +} +struct AudioFile: Codable { + var index: Int + var ino: String + var metadata: FileMetadata +} +struct Author: Codable { + var id: String + var name: String + var coverPath: String? +} +struct Chapter: Codable { + var id: Int + var start: Double + var end: Double + var title: String? +} +struct AudioTrack: Codable { + var index: Int? + var startOffset: Double? + var duration: Double + var title: String? + var contentUrl: String? + var mimeType: String + var metadata: FileMetadata? + // var isLocal: Bool + // var localFileId: String? + // var audioProbeResult: AudioProbeResult? Needed for local playback + var serverIndex: Int? +} +struct FileMetadata: Codable { + var filename: String + var ext: String + var path: String + var relPath: String +} +struct Library: Codable { + var id: String + var name: String + var folders: [Folder] + var icon: String + var mediaType: String +} +struct Folder: Codable { + var id: String + var fullPath: String +} +struct LibraryFile: Codable { + var ino: String + var metadata: FileMetadata +} diff --git a/ios/App/Shared/models/PlaybackSession.swift b/ios/App/Shared/models/PlaybackSession.swift index bf947c71..483da125 100644 --- a/ios/App/Shared/models/PlaybackSession.swift +++ b/ios/App/Shared/models/PlaybackSession.swift @@ -6,7 +6,7 @@ // import Foundation - + struct PlaybackSession: Decodable, Encodable { var id: String var userId: String? @@ -25,33 +25,8 @@ struct PlaybackSession: Decodable, Encodable { var timeListening: Double var audioTracks: [AudioTrack] var currentTime: Double - // var libraryItem: LibraryItem? + var libraryItem: LibraryItem // var localLibraryItem: LocalLibraryItem? var serverConnectionConfigId: String? var serverAddress: String? } -struct Chapter: Decodable, Encodable { - var id: Int - var start: Double - var end: Double - var title: String? -} -struct AudioTrack: Decodable, Encodable { - var index: Int? - var startOffset: Double - var duration: Double - var title: String - var contentUrl: String - var mimeType: String - var metadata: FileMetadata? - // var isLocal: Bool - // var localFileId: String? - // var audioProbeResult: AudioProbeResult? Needed for local playback - var serverIndex: Int? -} -struct FileMetadata: Decodable, Encodable { - var filename: String - var ext: String - var path: String - var relPath: String -} diff --git a/ios/App/Shared/player/AudioPlayer.swift b/ios/App/Shared/player/AudioPlayer.swift index 0bd7270c..88550c27 100644 --- a/ios/App/Shared/player/AudioPlayer.swift +++ b/ios/App/Shared/player/AudioPlayer.swift @@ -40,7 +40,7 @@ class AudioPlayer: NSObject { if playbackSession.audioTracks.count != 1 || playbackSession.audioTracks[0].mimeType != "application/vnd.apple.mpegurl" { NSLog("The player only support HLS streams right now") - self.activeAudioTrack = AudioTrack(index: 0, startOffset: -1, duration: -1, title: "", contentUrl: "", mimeType: "") + self.activeAudioTrack = AudioTrack(index: 0, startOffset: -1, duration: -1, title: "", contentUrl: nil, mimeType: "", metadata: nil, serverIndex: 0) super.init() return @@ -168,7 +168,7 @@ class AudioPlayer: NSObject { "Authorization": "Bearer \(Store.serverConfig!.token)" ] - return AVURLAsset(url: URL(string: "\(Store.serverConfig!.address)\(activeAudioTrack.contentUrl)")!, options: ["AVURLAssetHTTPHeaderFieldsKey": headers]) + return AVURLAsset(url: URL(string: "\(Store.serverConfig!.address)\(activeAudioTrack.contentUrl ?? "")")!, options: ["AVURLAssetHTTPHeaderFieldsKey": headers]) } private func initAudioSession() { do { diff --git a/pages/item/_id.vue b/pages/item/_id.vue index b2976755..bd5a4b41 100644 --- a/pages/item/_id.vue +++ b/pages/item/_id.vue @@ -13,8 +13,9 @@

by {{ author }}

{{ $elapsedPretty(duration) }} - {{ $bytesPretty(size) }} + {{ $bytesPretty(size) }}

+

{{ numTracks }} Tracks

Your Progress: {{ Math.round(progressPercent * 100) }}%

diff --git a/pages/search.vue b/pages/search.vue index 394d6ac6..6893f0ec 100644 --- a/pages/search.vue +++ b/pages/search.vue @@ -76,7 +76,11 @@ export default { }, methods: { async runSearch(value) { + if (this.isFetching && this.lastSearch === value) return + this.lastSearch = value + this.$store.commit('globals/setLastSearch', value) + if (!this.lastSearch) { this.bookResults = [] this.podcastResults = [] @@ -89,6 +93,10 @@ export default { console.error('Search error', error) return [] }) + if (value !== this.lastSearch) { + console.log(`runSearch: New search was made for ${this.lastSearch} - results are from ${value}`) + return + } console.log('RESULTS', results) this.isFetching = false @@ -113,7 +121,12 @@ export default { } }, mounted() { - this.$nextTick(this.setFocus()) + if (this.$store.state.globals.lastSearch) { + this.search = this.$store.state.globals.lastSearch + this.runSearch(this.search) + } else { + this.$nextTick(this.setFocus()) + } } } diff --git a/store/globals.js b/store/globals.js index c2d286fc..6d4a2d11 100644 --- a/store/globals.js +++ b/store/globals.js @@ -2,7 +2,8 @@ export const state = () => ({ itemDownloads: [], bookshelfListView: false, series: null, - localMediaProgress: [] + localMediaProgress: [], + lastSearch: null }) export const getters = { @@ -82,5 +83,8 @@ export const mutations = { }, removeLocalMediaProgress(state, id) { state.localMediaProgress = state.localMediaProgress.filter(lmp => lmp.id != id) + }, + setLastSearch(state, val) { + state.lastSearch = val } } \ No newline at end of file diff --git a/store/index.js b/store/index.js index 9018b9da..7736312e 100644 --- a/store/index.js +++ b/store/index.js @@ -21,9 +21,6 @@ export const state = () => ({ }) export const getters = { - playerIsOpen: (state) => { - return state.streamAudiobook - }, getIsItemStreaming: state => libraryItemId => { return state.playerLibraryItemId == libraryItemId },