diff --git a/android/app/src/main/java/com/audiobookshelf/app/AudioDownloader.kt b/android/app/src/main/java/com/audiobookshelf/app/AudioDownloader.kt index fb89c86a..492d7a8c 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/AudioDownloader.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/AudioDownloader.kt @@ -141,9 +141,9 @@ class AudioDownloader : Plugin() { var downloadItem = DownloadItem(libraryItem.id, localFolder, bookTitle, mutableListOf()) var itemFolderPath = localFolder.absolutePath + "/" + bookTitle tracks.forEach { audioFile -> - var serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioFile.metadata.relPath)}" - var destinationFilename = getFilenameFromRelPath(audioFile.metadata.relPath) - Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioFile.metadata.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}") + var serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioFile.relPath)}" + var destinationFilename = getFilenameFromRelPath(audioFile.relPath) + Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioFile.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}") var destinationFile = File("$itemFolderPath/$destinationFilename") var destinationUri = Uri.fromFile(destinationFile) var downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}") diff --git a/android/app/src/main/java/com/audiobookshelf/app/MyNativeAudio.kt b/android/app/src/main/java/com/audiobookshelf/app/MyNativeAudio.kt index 32798c2a..90024faf 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/MyNativeAudio.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/MyNativeAudio.kt @@ -78,34 +78,26 @@ class MyNativeAudio : Plugin() { var libraryItemId = call.getString("libraryItemId", "").toString() var playWhenReady = call.getBoolean("playWhenReady") == true - apiHandler.playLibraryItem(libraryItemId, false) { - - Handler(Looper.getMainLooper()).post() { - Log.d(tag, "Preparing Player TEST ${jacksonObjectMapper().writeValueAsString(it)}") - playerNotificationService.preparePlayer(it, playWhenReady) + if (libraryItemId.startsWith("local")) { // Play local media item + DeviceManager.dbManager.getLocalMediaItem(libraryItemId)?.let { + Handler(Looper.getMainLooper()).post() { + Log.d(tag, "Preparing Local Media item ${jacksonObjectMapper().writeValueAsString(it)}") + var playbackSession = it.getPlaybackSession() + playerNotificationService.preparePlayer(playbackSession, playWhenReady) + } + return call.resolve(JSObject()) } + } else { // Play library item from server + apiHandler.playLibraryItem(libraryItemId, false) { - call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(it))) - } - } + Handler(Looper.getMainLooper()).post() { + Log.d(tag, "Preparing Player TEST ${jacksonObjectMapper().writeValueAsString(it)}") + playerNotificationService.preparePlayer(it, playWhenReady) + } - @PluginMethod - fun playLocalLibraryItem(call:PluginCall) { - var localMediaItemId = call.getString("localMediaItemId", "").toString() - var playWhenReady = call.getBoolean("playWhenReady") == true - Log.d(tag, "playLocalLibraryItem $playWhenReady") - - DeviceManager.dbManager.loadLocalMediaItem(localMediaItemId)?.let { - Handler(Looper.getMainLooper()).post() { - Log.d(tag, "Preparing Local Media item ${jacksonObjectMapper().writeValueAsString(it)}") - var playbackSession = it.getPlaybackSession() - playerNotificationService.preparePlayer(playbackSession, playWhenReady) + call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(it))) } - return call.resolve(JSObject()) } - var errObj = JSObject() - errObj.put("error", "Item Not Found") - call.resolve(errObj) } @PluginMethod 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 803c2c8c..5187a349 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 @@ -1,6 +1,6 @@ package com.audiobookshelf.app.data -import android.net.Uri +import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnoreProperties @JsonIgnoreProperties(ignoreUnknown = true) @@ -24,8 +24,16 @@ data class AudioProbeChapter( val id:Int, val start:Int, val end:Int, - val tags:AudioProbeChapterTags -) + val tags:AudioProbeChapterTags? +) { + @JsonIgnore + fun getBookChapter():BookChapter { + var startS = start / 1000.0 + var endS = end / 1000.0 + var title = tags?.title ?: "Chapter $id" + return BookChapter(id, startS, endS, title) + } +} @JsonIgnoreProperties(ignoreUnknown = true) data class AudioProbeFormatTags( @@ -57,4 +65,10 @@ class AudioProbeResult ( val size get() = format.size val title get() = format.tags.title ?: format.filename.split("/").last() val artist get() = format.tags.artist ?: "" + + @JsonIgnore + fun getBookChapters(): List { + if (chapters.isEmpty()) return mutableListOf() + return chapters.map { it.getBookChapter() } + } } 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 160df619..ab016e25 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 @@ -45,10 +45,10 @@ data class Podcast( data class Book( var metadata:BookMetadata, var coverPath:String?, - var tags:MutableList, - var audioFiles:MutableList, - var chapters:MutableList, - var tracks:MutableList?, + var tags:List, + var audioFiles:List, + var chapters:List, + var tracks:List?, var size:Long?, var duration:Double? ) : MediaType() @@ -154,9 +154,10 @@ data class AudioTrack( var title:String, var contentUrl:String, var mimeType:String, + var metadata:FileMetadata?, var isLocal:Boolean, var localFileId:String?, - var audioProbeResult:AudioProbeResult? + var audioProbeResult:AudioProbeResult?, ) { @get:JsonIgnore @@ -165,6 +166,13 @@ data class AudioTrack( val durationMs get() = (duration * 1000L).toLong() @get:JsonIgnore val endOffsetMs get() = startOffsetMs + durationMs + @get:JsonIgnore + val relPath get() = metadata?.relPath ?: "" + + @JsonIgnore + fun getBookChapter():BookChapter { + return BookChapter(index + 1,startOffset, startOffset + duration, title) + } } @JsonIgnoreProperties(ignoreUnknown = true) diff --git a/android/app/src/main/java/com/audiobookshelf/app/data/DbManager.kt b/android/app/src/main/java/com/audiobookshelf/app/data/DbManager.kt index 2522ed9f..762cc84e 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/data/DbManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/data/DbManager.kt @@ -56,7 +56,7 @@ class DbManager : Plugin() { } } - fun loadLocalMediaItem(localMediaItemId:String):LocalMediaItem? { + fun getLocalMediaItem(localMediaItemId:String):LocalMediaItem? { return Paper.book("localMediaItems").read(localMediaItemId) } @@ -153,6 +153,32 @@ class DbManager : Plugin() { } } + @PluginMethod + fun getLocalLibraryItems_WV(call:PluginCall) { + GlobalScope.launch(Dispatchers.IO) { + var localLibraryItems = getLocalMediaItems().map { + it.getLocalLibraryItem() + } + var jsobj = JSObject() + jsobj.put("localLibraryItems", jacksonObjectMapper().writeValueAsString(localLibraryItems)) + call.resolve(jsobj) + } + } + + @PluginMethod + fun getLocalLibraryItem_WV(call:PluginCall) { + var id = call.getString("id", "").toString() + GlobalScope.launch(Dispatchers.IO) { + var mediaItem = getLocalMediaItem(id) + var localLibraryItem = mediaItem?.getLocalLibraryItem() + if (localLibraryItem == null) { + call.resolve() + } else { + call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(localLibraryItem))) + } + } + } + @PluginMethod fun setCurrentServerConnectionConfig_WV(call:PluginCall) { var serverConnectionConfigId = call.getString("id", "").toString() diff --git a/android/app/src/main/java/com/audiobookshelf/app/data/DeviceClasses.kt b/android/app/src/main/java/com/audiobookshelf/app/data/DeviceClasses.kt index a2885278..af6f3fad 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/data/DeviceClasses.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/data/DeviceClasses.kt @@ -18,6 +18,18 @@ data class DeviceData( var lastServerConnectionConfigId:String? ) +@JsonIgnoreProperties(ignoreUnknown = true) +data class LocalLibraryItem( + var id:String, + var folderId:String, + var absolutePath:String, + var isInvalid:Boolean, + var mediaType:String, + var media:MediaType, + var localFiles:MutableList, + var isLocal:Boolean +) + @JsonIgnoreProperties(ignoreUnknown = true) data class LocalMediaItem( var id:String, @@ -40,6 +52,13 @@ data class LocalMediaItem( return total } + @JsonIgnore + fun getTotalSize():Long { + var total = 0L + localFiles.forEach { total += it.size } + return total + } + @JsonIgnore fun getMediaMetadata():MediaTypeMetadata { return if (mediaType == "book") { @@ -54,7 +73,36 @@ data class LocalMediaItem( var sessionId = "play-${UUID.randomUUID()}" var mediaMetadata = getMediaMetadata() - return PlaybackSession(sessionId,null,null,null, mediaType, mediaMetadata, mutableListOf(), name, "author name here",null,getDuration(),PLAYMETHOD_LOCAL,audioTracks,0.0,null,this,null,null) + var chapters = getAudiobookChapters() + var authorName = "Unknown" + if (mediaType == "book") { + var bookMetadata = mediaMetadata as BookMetadata + authorName = bookMetadata?.authorName ?: "Unknown" + } + return PlaybackSession(sessionId,null,null,null, mediaType, mediaMetadata, chapters, name, authorName,null,getDuration(),PLAYMETHOD_LOCAL,audioTracks,0.0,null,this,null,null) + } + + @JsonIgnore + fun getAudiobookChapters():List { + if (mediaType != "book" || audioTracks.isEmpty()) return mutableListOf() + if (audioTracks.size == 1) { // Single track audiobook look for chapters from ffprobe + return audioTracks[0].audioProbeResult?.getBookChapters() ?: mutableListOf() + } + // Multi-track make chapters from tracks + return audioTracks.map { it.getBookChapter() } + } + + @JsonIgnore + fun getLocalLibraryItem():LocalLibraryItem { + var mediaMetadata = getMediaMetadata() + if (mediaType == "book") { + var chapters = getAudiobookChapters() + var book = Book(mediaMetadata as BookMetadata, coverContentUrl, mutableListOf(), mutableListOf(), chapters,audioTracks,getTotalSize(),getDuration()) + return LocalLibraryItem(id, folderId, absolutePath, false,mediaType, book, localFiles, true) + } else { + var podcast = Podcast(mediaMetadata as PodcastMetadata, coverContentUrl, mutableListOf(), mutableListOf(), false) + return LocalLibraryItem(id, folderId, absolutePath, false, mediaType, podcast,localFiles,true) + } } } 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 b9d26e20..2dc02cdc 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 @@ -23,7 +23,7 @@ class PlaybackSession( var episodeId:String?, var mediaType:String, var mediaMetadata:MediaTypeMetadata, - var chapters:MutableList, + var chapters:List, var displayTitle: String?, var displayAuthor: String?, var coverPath:String?, 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 b2cdf181..187327ba 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 @@ -11,9 +11,6 @@ import com.arthenica.ffmpegkit.Level import com.audiobookshelf.app.data.* import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.readValue -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch class FolderScanner(var ctx: Context) { private val tag = "FolderScanner" @@ -61,7 +58,7 @@ class FolderScanner(var ctx: Context) { Log.d(tag, "Iterating over Folder Found ${it.name} | ${it.getSimplePath(ctx)} | URI: ${it.uri}") var itemFolderName = it.name ?: "" - var itemId = DeviceManager.getBase64Id(it.id) + var itemId = "local_" + DeviceManager.getBase64Id(it.id) var existingMediaItem = existingMediaItems.find { emi -> emi.id == itemId } var existingLocalFiles = existingMediaItem?.localFiles ?: mutableListOf() @@ -130,7 +127,7 @@ class FolderScanner(var ctx: Context) { audioTrackToAdd = existingAudioTrack } else { // Create new audio track - var track = AudioTrack(index, startOffset, audioProbeResult.duration, filename, localFile.contentUrl, mimeType, true, localFileId, audioProbeResult) + var track = AudioTrack(index, startOffset, audioProbeResult.duration, filename, localFile.contentUrl, mimeType, null, true, localFileId, audioProbeResult) audioTrackToAdd = track } diff --git a/components/app/AudioPlayerContainer.vue b/components/app/AudioPlayerContainer.vue index ca9bb8c1..d613bade 100644 --- a/components/app/AudioPlayerContainer.vue +++ b/components/app/AudioPlayerContainer.vue @@ -175,15 +175,6 @@ export default { .catch((error) => { console.error('TEST failed', error) }) - }, - async playLocalItem(localMediaItemId) { - MyNativeAudio.playLocalLibraryItem({ localMediaItemId, playWhenReady: true }) - .then((data) => { - console.log('TEST library item play response', JSON.stringify(data)) - }) - .catch((error) => { - console.error('TEST failed', error) - }) } }, mounted() { @@ -195,7 +186,6 @@ export default { this.setListeners() this.$eventBus.$on('play-item', this.playLibraryItem) - this.$eventBus.$on('play-local-item', this.playLocalItem) this.$eventBus.$on('close-stream', this.closeStreamOnly) this.$store.commit('user/addSettingsListener', { id: 'streamContainer', meth: this.settingsUpdated }) }, @@ -211,7 +201,6 @@ export default { // this.$server.socket.off('stream_reset', this.streamReset) // } this.$eventBus.$off('play-item', this.playLibraryItem) - this.$eventBus.$off('play-local-item', this.playLocalItem) this.$eventBus.$off('close-stream', this.closeStreamOnly) this.$store.commit('user/removeSettingsListener', 'streamContainer') } diff --git a/components/cards/LazyBookCard.vue b/components/cards/LazyBookCard.vue index e34f187a..a0581928 100644 --- a/components/cards/LazyBookCard.vue +++ b/components/cards/LazyBookCard.vue @@ -17,11 +17,11 @@
{{ booksInSeries }}
-
+

{{ title }}

- +
@@ -52,6 +52,8 @@