mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-06-26 07:08:45 +02:00
Add podcast pages, android download podcast, scan podcast folders
This commit is contained in:
parent
ef65b4c278
commit
c94e57f55e
26 changed files with 789 additions and 397 deletions
|
@ -99,15 +99,44 @@ class Podcast(
|
|||
episodes?.add(newEpisode)
|
||||
}
|
||||
}
|
||||
|
||||
var index = 1
|
||||
episodes?.forEach {
|
||||
it.index = index
|
||||
index++
|
||||
}
|
||||
}
|
||||
@JsonIgnore
|
||||
override fun addAudioTrack(audioTrack:AudioTrack) {
|
||||
var newEpisode = PodcastEpisode("local_" + audioTrack.localFileId,episodes?.size ?: 0 + 1,null,null,audioTrack.title,null,null,null,audioTrack)
|
||||
episodes?.add(newEpisode)
|
||||
|
||||
var index = 1
|
||||
episodes?.forEach {
|
||||
it.index = index
|
||||
index++
|
||||
}
|
||||
}
|
||||
@JsonIgnore
|
||||
override fun removeAudioTrack(localFileId:String) {
|
||||
episodes?.removeIf { it.audioTrack?.localFileId == localFileId }
|
||||
|
||||
var index = 1
|
||||
episodes?.forEach {
|
||||
it.index = index
|
||||
index++
|
||||
}
|
||||
}
|
||||
@JsonIgnore
|
||||
fun addEpisode(audioTrack:AudioTrack, episode:PodcastEpisode) {
|
||||
var newEpisode = PodcastEpisode("local_" + episode.id,episodes?.size ?: 0 + 1,episode.episode,episode.episodeType,episode.title,episode.subtitle,episode.description,null,audioTrack)
|
||||
episodes?.add(newEpisode)
|
||||
|
||||
var index = 1
|
||||
episodes?.forEach {
|
||||
it.index = index
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,10 @@ class FolderScanner(var ctx: Context) {
|
|||
return "local_" + DeviceManager.getBase64Id(mediaItemId)
|
||||
}
|
||||
|
||||
enum class ItemScanResult {
|
||||
ADDED, REMOVED, UPDATED, UPTODATE
|
||||
}
|
||||
|
||||
// TODO: CLEAN this monster! Divide into bite-size methods
|
||||
fun scanForMediaItems(localFolder:LocalFolder, forceAudioProbe:Boolean):FolderScanResult? {
|
||||
FFmpegKitConfig.enableLogCallback { log ->
|
||||
|
@ -57,16 +61,33 @@ class FolderScanner(var ctx: Context) {
|
|||
fileFound != null
|
||||
}
|
||||
|
||||
var localLibraryItems = mutableListOf<LocalLibraryItem>()
|
||||
|
||||
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) }
|
||||
|
||||
var result = scanLibraryItemFolder(itemFolder, localFolder, existingItem, forceAudioProbe)
|
||||
|
||||
if (result == ItemScanResult.REMOVED) mediaItemsRemoved++
|
||||
else if (result == ItemScanResult.UPDATED) mediaItemsUpdated++
|
||||
else if (result == ItemScanResult.ADDED) mediaItemsAdded++
|
||||
else mediaItemsUpToDate++
|
||||
}
|
||||
|
||||
Log.d(tag, "Folder $${localFolder.name} scan Results: $mediaItemsAdded Added | $mediaItemsUpdated Updated | $mediaItemsRemoved Removed | $mediaItemsUpToDate Up-to-date")
|
||||
|
||||
return if (mediaItemsAdded > 0 || mediaItemsUpdated > 0 || mediaItemsRemoved > 0) {
|
||||
var folderLibraryItems = DeviceManager.dbManager.getLocalLibraryItemsInFolder(localFolder.id) // Get all local media items
|
||||
FolderScanResult(mediaItemsAdded, mediaItemsUpdated, mediaItemsRemoved, mediaItemsUpToDate, localFolder, folderLibraryItems)
|
||||
} else {
|
||||
Log.d(tag, "No Media Items to save")
|
||||
FolderScanResult(mediaItemsAdded, mediaItemsUpdated, mediaItemsRemoved, mediaItemsUpToDate, localFolder, mutableListOf())
|
||||
}
|
||||
}
|
||||
|
||||
fun scanLibraryItemFolder(itemFolder:DocumentFile, localFolder:LocalFolder, existingItem:LocalLibraryItem?, forceAudioProbe:Boolean):ItemScanResult {
|
||||
var itemFolderName = itemFolder.name ?: ""
|
||||
var itemId = getLocalLibraryItemId(itemFolder.id)
|
||||
var itemContentUrl = itemFolder.uri.toString()
|
||||
|
||||
var existingItem = existingLocalLibraryItems.find { emi -> emi.id == itemId }
|
||||
var existingLocalFiles = existingItem?.localFiles ?: mutableListOf()
|
||||
var existingAudioTracks = existingItem?.media?.getAudioTracks() ?: mutableListOf()
|
||||
var isNewOrUpdated = existingItem == null
|
||||
|
@ -79,7 +100,7 @@ class FolderScanner(var ctx: Context) {
|
|||
var coverAbsolutePath:String? = null
|
||||
|
||||
var filesInFolder = itemFolder.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
|
||||
|
||||
var isPodcast = localFolder.mediaType == "podcast"
|
||||
|
||||
var existingLocalFilesRemoved = existingLocalFiles.filter { elf ->
|
||||
filesInFolder.find { fif -> DeviceManager.getBase64Id(fif.id) == elf.id } == null // File was not found in media item folder
|
||||
|
@ -172,36 +193,23 @@ class FolderScanner(var ctx: Context) {
|
|||
if (existingItem != null && audioTracks.isEmpty()) {
|
||||
Log.d(tag, "Local library item ${existingItem.media.metadata.title} no longer has audio tracks - removing item")
|
||||
DeviceManager.dbManager.removeLocalLibraryItem(existingItem.id)
|
||||
mediaItemsRemoved++
|
||||
return ItemScanResult.REMOVED
|
||||
} else if (existingItem != null && !isNewOrUpdated) {
|
||||
Log.d(tag, "Local library item ${existingItem.media.metadata.title} has no updates")
|
||||
mediaItemsUpToDate++
|
||||
return ItemScanResult.UPTODATE
|
||||
} else if (existingItem != null) {
|
||||
Log.d(tag, "Updating local library item ${existingItem.media.metadata.title}")
|
||||
mediaItemsUpdated++
|
||||
|
||||
existingItem.updateFromScan(audioTracks,localFiles)
|
||||
localLibraryItems.add(existingItem)
|
||||
DeviceManager.dbManager.saveLocalLibraryItem(existingItem)
|
||||
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")
|
||||
mediaItemsAdded++
|
||||
|
||||
var localMediaItem = LocalMediaItem(itemId, itemFolderName, localFolder.mediaType, localFolder.id, itemFolder.uri.toString(), itemFolder.getSimplePath(ctx), itemFolder.getBasePath(ctx), itemFolder.getAbsolutePath(ctx),audioTracks,localFiles,coverContentUrl,coverAbsolutePath)
|
||||
var localLibraryItem = localMediaItem.getLocalLibraryItem()
|
||||
localLibraryItems.add(localLibraryItem)
|
||||
}
|
||||
}
|
||||
|
||||
Log.d(tag, "Folder $${localFolder.name} scan Results: $mediaItemsAdded Added | $mediaItemsUpdated Updated | $mediaItemsRemoved Removed | $mediaItemsUpToDate Up-to-date")
|
||||
|
||||
return if (localLibraryItems.isNotEmpty()) {
|
||||
DeviceManager.dbManager.saveLocalLibraryItems(localLibraryItems)
|
||||
|
||||
var folderLibraryItems = DeviceManager.dbManager.getLocalLibraryItemsInFolder(localFolder.id) // Get all local media items
|
||||
FolderScanResult(mediaItemsAdded, mediaItemsUpdated, mediaItemsRemoved, mediaItemsUpToDate, localFolder, folderLibraryItems)
|
||||
DeviceManager.dbManager.saveLocalLibraryItem(localLibraryItem)
|
||||
return ItemScanResult.ADDED
|
||||
} else {
|
||||
Log.d(tag, "No Media Items to save")
|
||||
FolderScanResult(mediaItemsAdded, mediaItemsUpdated, mediaItemsRemoved, mediaItemsUpToDate, localFolder, mutableListOf())
|
||||
return ItemScanResult.UPTODATE
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -236,9 +244,18 @@ class FolderScanner(var ctx: Context) {
|
|||
var filesFound = df.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
|
||||
Log.d(tag, "scanDownloadItem ${filesFound.size} files found in ${downloadItem.itemFolderPath}")
|
||||
|
||||
var localLibraryItem = LocalLibraryItem("local_${downloadItem.id}", downloadItem.localFolder.id, itemFolderBasePath, itemFolderAbsolutePath, itemFolderUrl, false, downloadItem.mediaType, downloadItem.media, mutableListOf(), null, null, true,downloadItem.serverConnectionConfigId,downloadItem.serverAddress,downloadItem.serverUserId,downloadItem.id)
|
||||
var localLibraryItem:LocalLibraryItem? = null
|
||||
if (downloadItem.mediaType == "book") {
|
||||
localLibraryItem = LocalLibraryItem("local_${downloadItem.libraryItemId}", downloadItem.localFolder.id, itemFolderBasePath, itemFolderAbsolutePath, itemFolderUrl, false, downloadItem.mediaType, downloadItem.media, mutableListOf(), null, null, true, downloadItem.serverConnectionConfigId, downloadItem.serverAddress, downloadItem.serverUserId, downloadItem.libraryItemId)
|
||||
} else {
|
||||
// Lookup or create podcast local library item
|
||||
localLibraryItem = DeviceManager.dbManager.getLocalLibraryItem("local_${downloadItem.libraryItemId}")
|
||||
if (localLibraryItem == null) {
|
||||
Log.d(tag, "Podcast local library item not created yet for ${downloadItem.media.metadata.title}")
|
||||
localLibraryItem = LocalLibraryItem("local_${downloadItem.libraryItemId}", downloadItem.localFolder.id, itemFolderBasePath, itemFolderAbsolutePath, itemFolderUrl, false, downloadItem.mediaType, downloadItem.media, mutableListOf(), null, null, true,downloadItem.serverConnectionConfigId,downloadItem.serverAddress,downloadItem.serverUserId,downloadItem.libraryItemId)
|
||||
}
|
||||
}
|
||||
|
||||
var localFiles:MutableList<LocalFile> = mutableListOf()
|
||||
var audioTracks:MutableList<AudioTrack> = mutableListOf()
|
||||
|
||||
filesFound.forEach { docFile ->
|
||||
|
@ -246,13 +263,15 @@ class FolderScanner(var ctx: Context) {
|
|||
itemPart.filename == docFile.name
|
||||
}
|
||||
if (itemPart == null) {
|
||||
if (downloadItem.mediaType == "book") { // for books every download item should be a file found
|
||||
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
|
||||
|
||||
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())
|
||||
localFiles.add(localFile)
|
||||
localLibraryItem.localFiles.add(localFile)
|
||||
|
||||
// TODO: Make asynchronous
|
||||
var session = FFprobeKit.execute("-i \"${localFile.absolutePath}\" -print_format json -show_format -show_streams -select_streams a -show_chapters -loglevel quiet")
|
||||
|
@ -263,13 +282,19 @@ class FolderScanner(var ctx: Context) {
|
|||
// 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)
|
||||
audioTracks.add(track)
|
||||
|
||||
// Add podcast episodes to library
|
||||
itemPart.episode?.let { podcastEpisode ->
|
||||
var podcast = localLibraryItem.media as Podcast
|
||||
podcast.addEpisode(track, podcastEpisode)
|
||||
}
|
||||
} 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())
|
||||
localFiles.add(localFile)
|
||||
|
||||
localLibraryItem.coverAbsolutePath = localFile.absolutePath
|
||||
localLibraryItem.coverContentUrl = localFile.contentUrl
|
||||
localLibraryItem.localFiles.add(localFile)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -278,6 +303,8 @@ class FolderScanner(var ctx: Context) {
|
|||
return null
|
||||
}
|
||||
|
||||
// For books sort audio tracks then set
|
||||
if (downloadItem.mediaType == "book") {
|
||||
audioTracks.sortBy { it.index }
|
||||
|
||||
var indexCheck = 1
|
||||
|
@ -292,7 +319,7 @@ class FolderScanner(var ctx: Context) {
|
|||
}
|
||||
|
||||
localLibraryItem.media.setAudioTracks(audioTracks)
|
||||
localLibraryItem.localFiles = localFiles
|
||||
}
|
||||
|
||||
DeviceManager.dbManager.saveLocalLibraryItem(localLibraryItem)
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ import com.audiobookshelf.app.device.DeviceManager
|
|||
import com.audiobookshelf.app.player.CastManager
|
||||
import com.audiobookshelf.app.player.PlayerNotificationService
|
||||
import com.audiobookshelf.app.server.ApiHandler
|
||||
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import com.getcapacitor.*
|
||||
import com.getcapacitor.annotation.CapacitorPlugin
|
||||
|
|
|
@ -43,6 +43,7 @@ class AbsDownloader : Plugin() {
|
|||
val localFolderName: String,
|
||||
val localFolderId: String,
|
||||
val audioTrack: AudioTrack?,
|
||||
val episode:PodcastEpisode?,
|
||||
var completed:Boolean,
|
||||
@JsonIgnore val uri: Uri,
|
||||
@JsonIgnore val destinationUri: Uri,
|
||||
|
@ -62,6 +63,8 @@ class AbsDownloader : Plugin() {
|
|||
|
||||
data class DownloadItem(
|
||||
val id: String,
|
||||
val libraryItemId:String,
|
||||
val episodeId:String?,
|
||||
val serverConnectionConfigId:String,
|
||||
val serverAddress:String,
|
||||
val serverUserId:String,
|
||||
|
@ -96,20 +99,40 @@ class AbsDownloader : Plugin() {
|
|||
@PluginMethod
|
||||
fun downloadLibraryItem(call: PluginCall) {
|
||||
var libraryItemId = call.data.getString("libraryItemId").toString()
|
||||
var episodeId = call.data.getString("episodeId").toString()
|
||||
var localFolderId = call.data.getString("localFolderId").toString()
|
||||
Log.d(tag, "Download library item $libraryItemId to folder $localFolderId")
|
||||
|
||||
if (downloadQueue.find { it.id == libraryItemId } != null) {
|
||||
Log.d(tag, "Download already started for this library item $libraryItemId")
|
||||
return call.resolve(JSObject("{\"error\":\"Download already started for this library item\"}"))
|
||||
var downloadId = if (episodeId.isNullOrEmpty()) libraryItemId else "$libraryItemId-$episodeId"
|
||||
if (downloadQueue.find { it.id == downloadId } != null) {
|
||||
Log.d(tag, "Download already started for this media entity $downloadId")
|
||||
return call.resolve(JSObject("{\"error\":\"Download already started for this media entity\"}"))
|
||||
}
|
||||
|
||||
apiHandler.getLibraryItem(libraryItemId) { libraryItem ->
|
||||
Log.d(tag, "Got library item from server ${libraryItem.id}")
|
||||
|
||||
var localFolder = DeviceManager.dbManager.getLocalFolder(localFolderId)
|
||||
if (localFolder != null) {
|
||||
startLibraryItemDownload(libraryItem, localFolder)
|
||||
|
||||
if (!episodeId.isNullOrEmpty() && libraryItem.mediaType != "podcast") {
|
||||
Log.e(tag, "Library item is not a podcast but episode was requested")
|
||||
call.resolve(JSObject("{\"error\":\"Invalid library item not a podcast\"}"))
|
||||
} else if (!episodeId.isNullOrEmpty()) {
|
||||
var podcast = libraryItem.media as Podcast
|
||||
var episode = podcast.episodes?.find { podcastEpisode ->
|
||||
podcastEpisode.id == episodeId
|
||||
}
|
||||
if (episode == null) {
|
||||
call.resolve(JSObject("{\"error\":\"Invalid podcast episode not found\"}"))
|
||||
} else {
|
||||
startLibraryItemDownload(libraryItem, localFolder, episode)
|
||||
call.resolve()
|
||||
}
|
||||
} else {
|
||||
startLibraryItemDownload(libraryItem, localFolder, null)
|
||||
call.resolve()
|
||||
}
|
||||
} else {
|
||||
call.resolve(JSObject("{\"error\":\"Local Folder Not Found\"}"))
|
||||
}
|
||||
|
@ -139,13 +162,13 @@ class AbsDownloader : Plugin() {
|
|||
return fileString
|
||||
}
|
||||
|
||||
fun startLibraryItemDownload(libraryItem: LibraryItem, localFolder: LocalFolder) {
|
||||
fun startLibraryItemDownload(libraryItem: LibraryItem, localFolder: LocalFolder, episode:PodcastEpisode?) {
|
||||
if (libraryItem.mediaType == "book") {
|
||||
var bookTitle = libraryItem.media.metadata.title
|
||||
var tracks = libraryItem.media.getAudioTracks()
|
||||
Log.d(tag, "Starting library item download with ${tracks.size} tracks")
|
||||
var itemFolderPath = localFolder.absolutePath + "/" + bookTitle
|
||||
var downloadItem = DownloadItem(libraryItem.id, DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, libraryItem.media, mutableListOf())
|
||||
var downloadItem = DownloadItem(libraryItem.id, libraryItem.id, null,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 ->
|
||||
|
@ -162,7 +185,7 @@ class AbsDownloader : Plugin() {
|
|||
var destinationUri = Uri.fromFile(destinationFile)
|
||||
var downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}")
|
||||
Log.d(tag, "Audio File Destination Uri $destinationUri | Download URI $downloadUri")
|
||||
var downloadItemPart = DownloadItemPart(DeviceManager.getBase64Id(destinationFile.absolutePath), destinationFilename, destinationFile.absolutePath, bookTitle, serverPath, localFolder.name, localFolder.id, audioTrack, false, downloadUri, destinationUri, null, 0)
|
||||
var downloadItemPart = DownloadItemPart(DeviceManager.getBase64Id(destinationFile.absolutePath), destinationFilename, destinationFile.absolutePath, bookTitle, serverPath, localFolder.name, localFolder.id, audioTrack, null, false, downloadUri, destinationUri, null, 0)
|
||||
|
||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||
|
||||
|
@ -185,7 +208,7 @@ class AbsDownloader : Plugin() {
|
|||
|
||||
var destinationUri = Uri.fromFile(destinationFile)
|
||||
var downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}&token=${DeviceManager.token}")
|
||||
var downloadItemPart = DownloadItemPart(DeviceManager.getBase64Id(destinationFile.absolutePath), destinationFilename, destinationFile.absolutePath, bookTitle, serverPath, localFolder.name, localFolder.id, null, false, downloadUri, destinationUri, null, 0)
|
||||
var downloadItemPart = DownloadItemPart(DeviceManager.getBase64Id(destinationFile.absolutePath), destinationFilename, destinationFile.absolutePath, bookTitle, serverPath, localFolder.name, localFolder.id, null,null, false, downloadUri, destinationUri, null, 0)
|
||||
|
||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||
|
||||
|
@ -204,7 +227,57 @@ class AbsDownloader : Plugin() {
|
|||
DeviceManager.dbManager.saveDownloadItem(downloadItem)
|
||||
}
|
||||
} else {
|
||||
// TODO: Download podcast episode(s)
|
||||
// Podcast episode download
|
||||
|
||||
var podcastTitle = libraryItem.media.metadata.title
|
||||
var audioTrack = episode?.audioTrack
|
||||
Log.d(tag, "Starting podcast episode download")
|
||||
var itemFolderPath = localFolder.absolutePath + "/" + podcastTitle
|
||||
var downloadItemId = "${libraryItem.id}-${episode?.id}"
|
||||
var downloadItem = DownloadItem(downloadItemId, libraryItem.id, episode?.id, 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 ?: "")
|
||||
Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioTrack?.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}")
|
||||
var destinationFile = File("$itemFolderPath/$destinationFilename")
|
||||
if (destinationFile.exists()) {
|
||||
Log.d(tag, "Audio file already exists, removing it from ${destinationFile.absolutePath}")
|
||||
destinationFile.delete()
|
||||
}
|
||||
|
||||
var destinationUri = Uri.fromFile(destinationFile)
|
||||
var downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}")
|
||||
Log.d(tag, "Audio File Destination Uri $destinationUri | Download URI $downloadUri")
|
||||
var downloadItemPart = DownloadItemPart(DeviceManager.getBase64Id(destinationFile.absolutePath), destinationFilename, destinationFile.absolutePath, podcastTitle, serverPath, localFolder.name, localFolder.id, audioTrack, episode,false, downloadUri, destinationUri, null, 0)
|
||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||
|
||||
var dlRequest = downloadItemPart.getDownloadRequest()
|
||||
var downloadId = downloadManager.enqueue(dlRequest)
|
||||
downloadItemPart.downloadId = downloadId
|
||||
|
||||
if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) {
|
||||
var serverPath = "/api/items/${libraryItem.id}/cover?format=jpeg"
|
||||
var destinationFilename = "cover.jpg"
|
||||
var destinationFile = File("$itemFolderPath/$destinationFilename")
|
||||
|
||||
if (destinationFile.exists()) {
|
||||
Log.d(tag, "Podcast cover already exists - not downloading cover again")
|
||||
} else {
|
||||
var destinationUri = Uri.fromFile(destinationFile)
|
||||
var downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}&token=${DeviceManager.token}")
|
||||
var downloadItemPart = DownloadItemPart(DeviceManager.getBase64Id(destinationFile.absolutePath), destinationFilename, destinationFile.absolutePath, podcastTitle, serverPath, localFolder.name, localFolder.id, null,null, false, downloadUri, destinationUri, null, 0)
|
||||
|
||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||
|
||||
var dlRequest = downloadItemPart.getDownloadRequest()
|
||||
var downloadId = downloadManager.enqueue(dlRequest)
|
||||
downloadItemPart.downloadId = downloadId
|
||||
}
|
||||
}
|
||||
|
||||
downloadQueue.add(downloadItem)
|
||||
startWatchingDownloads(downloadItem)
|
||||
DeviceManager.dbManager.saveDownloadItem(downloadItem)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -105,11 +105,9 @@ export default {
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
// Main
|
||||
playbackSession: null,
|
||||
// Others
|
||||
showChapterModal: false,
|
||||
showCastBtn: true,
|
||||
showCastBtn: false,
|
||||
showFullscreen: false,
|
||||
totalDuration: 0,
|
||||
currentPlaybackRate: 1,
|
||||
|
@ -493,6 +491,7 @@ export default {
|
|||
onPlayingUpdate(data) {
|
||||
console.log('onPlayingUpdate', JSON.stringify(data))
|
||||
this.isPaused = !data.value
|
||||
this.$store.commit('setPlayerPlaying', !this.isPaused)
|
||||
if (!this.isPaused) {
|
||||
this.startPlayInterval()
|
||||
} else {
|
||||
|
@ -519,6 +518,8 @@ export default {
|
|||
console.log('onPlaybackSession received', JSON.stringify(playbackSession))
|
||||
this.playbackSession = playbackSession
|
||||
|
||||
this.$store.commit('setPlayerItem', this.playbackSession)
|
||||
|
||||
// Set track width
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.track) {
|
||||
|
@ -530,6 +531,7 @@ export default {
|
|||
},
|
||||
onPlaybackClosed() {
|
||||
console.log('Received onPlaybackClosed evt')
|
||||
this.$store.commit('setPlayerItem', null)
|
||||
this.showFullscreen = false
|
||||
this.playbackSession = null
|
||||
},
|
||||
|
|
|
@ -166,9 +166,12 @@ export default {
|
|||
this.$refs.audioPlayer.terminateStream()
|
||||
}
|
||||
},
|
||||
async playLibraryItem(libraryItemId) {
|
||||
async playLibraryItem(payload) {
|
||||
var libraryItemId = payload.libraryItemId
|
||||
var episodeId = payload.episodeId
|
||||
|
||||
console.log('Called playLibraryItem', libraryItemId)
|
||||
AbsAudioPlayer.prepareLibraryItem({ libraryItemId, playWhenReady: true })
|
||||
AbsAudioPlayer.prepareLibraryItem({ libraryItemId, episodeId, playWhenReady: true })
|
||||
.then((data) => {
|
||||
console.log('Library item play response', JSON.stringify(data))
|
||||
})
|
||||
|
@ -176,6 +179,11 @@ export default {
|
|||
console.error('Failed', error)
|
||||
})
|
||||
},
|
||||
pauseItem() {
|
||||
if (this.$refs.audioPlayer && !this.$refs.audioPlayer.isPaused) {
|
||||
this.$refs.audioPlayer.pause()
|
||||
}
|
||||
},
|
||||
onLocalMediaProgressUpdate(localMediaProgress) {
|
||||
console.log('Got local media progress update', localMediaProgress.progress, JSON.stringify(localMediaProgress))
|
||||
this.$store.commit('globals/updateLocalMediaProgress', localMediaProgress)
|
||||
|
@ -191,6 +199,7 @@ export default {
|
|||
|
||||
this.setListeners()
|
||||
this.$eventBus.$on('play-item', this.playLibraryItem)
|
||||
this.$eventBus.$on('pause-item', this.pauseItem)
|
||||
this.$eventBus.$on('close-stream', this.closeStreamOnly)
|
||||
this.$store.commit('user/addSettingsListener', { id: 'streamContainer', meth: this.settingsUpdated })
|
||||
},
|
||||
|
@ -207,6 +216,7 @@ export default {
|
|||
// this.$server.socket.off('stream_reset', this.streamReset)
|
||||
// }
|
||||
this.$eventBus.$off('play-item', this.playLibraryItem)
|
||||
this.$eventBus.$off('pause-item', this.pauseItem)
|
||||
this.$eventBus.$off('close-stream', this.closeStreamOnly)
|
||||
this.$store.commit('user/removeSettingsListener', 'streamContainer')
|
||||
}
|
||||
|
|
|
@ -424,7 +424,7 @@ export default {
|
|||
},
|
||||
play() {
|
||||
var eventBus = this.$eventBus || this.$nuxt.$eventBus
|
||||
eventBus.$emit('play-item', this.libraryItemId)
|
||||
eventBus.$emit('play-item', { libraryItemId: this.libraryItemId })
|
||||
},
|
||||
destroy() {
|
||||
// destroy the vue listeners, etc
|
||||
|
|
|
@ -397,7 +397,7 @@ export default {
|
|||
},
|
||||
play() {
|
||||
var eventBus = this.$eventBus || this.$nuxt.$eventBus
|
||||
eventBus.$emit('play-item', this.libraryItemId)
|
||||
eventBus.$emit('play-item', { libraryItemId: this.libraryItemId })
|
||||
},
|
||||
destroy() {
|
||||
// destroy the vue listeners, etc
|
||||
|
|
|
@ -261,10 +261,15 @@ export default {
|
|||
}
|
||||
},
|
||||
async setUserAndConnection(user, userDefaultLibraryId) {
|
||||
if (user) {
|
||||
if (!user) return
|
||||
|
||||
console.log('Successfully logged in', JSON.stringify(user))
|
||||
|
||||
if (userDefaultLibraryId) {
|
||||
// Set library - Use last library if set and available fallback to default user library
|
||||
var lastLibraryId = await this.$localStore.getLastLibraryId()
|
||||
if (lastLibraryId && (!user.librariesAccessible.length || user.librariesAccessible.includes(lastLibraryId))) {
|
||||
this.$store.commit('libraries/setCurrentLibrary', lastLibraryId)
|
||||
} else if (userDefaultLibraryId) {
|
||||
this.$store.commit('libraries/setCurrentLibrary', userDefaultLibraryId)
|
||||
}
|
||||
|
||||
|
@ -278,7 +283,6 @@ export default {
|
|||
|
||||
this.$socket.connect(this.serverConfig.address, this.serverConfig.token)
|
||||
this.$router.replace('/bookshelf')
|
||||
}
|
||||
},
|
||||
async authenticateToken() {
|
||||
if (!this.networkConnected) return
|
||||
|
|
|
@ -55,6 +55,7 @@ export default {
|
|||
this.show = false
|
||||
await this.$store.dispatch('libraries/fetch', lib.id)
|
||||
this.$eventBus.$emit('library-changed', lib.id)
|
||||
this.$localStore.setLastLibraryId(lib.id)
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
|
|
|
@ -65,7 +65,7 @@ export default {
|
|||
return this.book.numTracks
|
||||
},
|
||||
isStreaming() {
|
||||
return this.$store.getters['getAudiobookIdStreaming'] === this.book.id
|
||||
return this.$store.getters['getIsItemStreaming'](this.book.id)
|
||||
},
|
||||
showPlayBtn() {
|
||||
return !this.isMissing && !this.isIncomplete && !this.isStreaming && this.numTracks
|
||||
|
|
183
components/tables/podcast/EpisodeRow.vue
Normal file
183
components/tables/podcast/EpisodeRow.vue
Normal file
|
@ -0,0 +1,183 @@
|
|||
<template>
|
||||
<div class="w-full px-2 py-3 overflow-hidden relative border-b border-white border-opacity-10">
|
||||
<div v-if="episode" class="flex items-center h-24">
|
||||
<!-- <div class="w-12 min-w-12 max-w-16 h-full">
|
||||
<div class="flex h-full items-center justify-center">
|
||||
<span class="material-icons drag-handle text-lg text-white text-opacity-50 hover:text-opacity-100">menu</span>
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="flex-grow px-2">
|
||||
<p class="text-sm font-semibold">
|
||||
{{ title }}
|
||||
</p>
|
||||
<p class="text-sm text-gray-200 episode-subtitle mt-1.5 mb-0.5">
|
||||
{{ description }}
|
||||
</p>
|
||||
<div class="flex items-center pt-2">
|
||||
<div class="h-8 px-4 border border-white border-opacity-20 hover:bg-white hover:bg-opacity-10 rounded-full flex items-center justify-center cursor-pointer" :class="userIsFinished ? 'text-white text-opacity-40' : ''" @click="playClick">
|
||||
<span class="material-icons" :class="streamIsPlaying ? '' : 'text-success'">{{ streamIsPlaying ? 'pause' : 'play_arrow' }}</span>
|
||||
<p class="pl-2 pr-1 text-sm font-semibold">{{ timeRemaining }}</p>
|
||||
</div>
|
||||
|
||||
<span class="material-icons px-2" :class="downloadItem ? 'animate-bounce text-warning text-opacity-75' : ''" @click="downloadClick">{{ downloadItem ? 'downloading' : 'download' }}</span>
|
||||
|
||||
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" borderless class="mx-1 mt-0.5" @click="toggleFinished" />
|
||||
<p v-if="publishedAt" class="px-4 text-sm text-gray-300">Published {{ $formatDate(publishedAt, 'MMM do, yyyy') }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!userIsFinished" class="absolute bottom-0 left-0 h-0.5 bg-warning" :style="{ width: itemProgressPercent * 100 + '%' }" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Dialog } from '@capacitor/dialog'
|
||||
import { AbsDownloader } from '@/plugins/capacitor'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
libraryItemId: String,
|
||||
isLocal: Boolean,
|
||||
episode: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isProcessingReadUpdate: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
mediaType() {
|
||||
return 'podcast'
|
||||
},
|
||||
audioFile() {
|
||||
return this.episode.audioFile
|
||||
},
|
||||
title() {
|
||||
return this.episode.title || ''
|
||||
},
|
||||
description() {
|
||||
if (this.episode.subtitle) return this.episode.subtitle
|
||||
var desc = this.episode.description || ''
|
||||
return desc
|
||||
},
|
||||
duration() {
|
||||
return this.$secondsToTimestamp(this.episode.duration)
|
||||
},
|
||||
isStreaming() {
|
||||
return this.$store.getters['getIsEpisodeStreaming'](this.libraryItemId, this.episode.id)
|
||||
},
|
||||
streamIsPlaying() {
|
||||
return this.$store.state.playerIsPlaying && this.isStreaming
|
||||
},
|
||||
itemProgress() {
|
||||
if (this.isLocal) return this.$store.getters['globals/getLocalMediaProgressById'](this.libraryItemId, this.episode.id)
|
||||
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId, this.episode.id)
|
||||
},
|
||||
itemProgressPercent() {
|
||||
return this.itemProgress ? this.itemProgress.progress : 0
|
||||
},
|
||||
userIsFinished() {
|
||||
return this.itemProgress ? !!this.itemProgress.isFinished : false
|
||||
},
|
||||
timeRemaining() {
|
||||
if (this.streamIsPlaying) return 'Playing'
|
||||
if (!this.itemProgressPercent) return this.$elapsedPretty(this.episode.duration)
|
||||
if (this.userIsFinished) return 'Finished'
|
||||
var remaining = Math.floor(this.itemProgress.duration - this.itemProgress.currentTime)
|
||||
return `${this.$elapsedPretty(remaining)} left`
|
||||
},
|
||||
publishedAt() {
|
||||
return this.episode.publishedAt
|
||||
},
|
||||
downloadItem() {
|
||||
return this.$store.getters['globals/getDownloadItem'](this.libraryItemId, this.episode.id)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectFolder() {
|
||||
this.$toast.error('Folder selector not implemented for podcasts yet')
|
||||
},
|
||||
downloadClick() {
|
||||
if (this.downloadItem) return
|
||||
this.download()
|
||||
},
|
||||
async download(selectedLocalFolder = null) {
|
||||
var localFolder = selectedLocalFolder
|
||||
if (!localFolder) {
|
||||
var localFolders = (await this.$db.getLocalFolders()) || []
|
||||
console.log('Local folders loaded', localFolders.length)
|
||||
var foldersWithMediaType = localFolders.filter((lf) => {
|
||||
console.log('Checking local folder', lf.mediaType)
|
||||
return lf.mediaType == this.mediaType
|
||||
})
|
||||
console.log('Folders with media type', this.mediaType, foldersWithMediaType.length)
|
||||
if (!foldersWithMediaType.length) {
|
||||
// No local folders or no local folders with this media type
|
||||
localFolder = await this.selectFolder()
|
||||
} else if (foldersWithMediaType.length == 1) {
|
||||
console.log('Only 1 local folder with this media type - auto select it')
|
||||
localFolder = foldersWithMediaType[0]
|
||||
} else {
|
||||
console.log('Multiple folders with media type')
|
||||
// this.showSelectLocalFolder = true
|
||||
return
|
||||
}
|
||||
if (!localFolder) {
|
||||
return this.$toast.error('Invalid download folder')
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Local folder', JSON.stringify(localFolder))
|
||||
|
||||
var startDownloadMessage = `Start download for "${this.title}" to folder ${localFolder.name}?`
|
||||
const { value } = await Dialog.confirm({
|
||||
title: 'Confirm',
|
||||
message: startDownloadMessage
|
||||
})
|
||||
if (value) {
|
||||
this.startDownload(localFolder)
|
||||
}
|
||||
},
|
||||
async startDownload(localFolder) {
|
||||
var downloadRes = await AbsDownloader.downloadLibraryItem({ libraryItemId: this.libraryItemId, localFolderId: localFolder.id, episodeId: this.episode.id })
|
||||
if (downloadRes && downloadRes.error) {
|
||||
var errorMsg = downloadRes.error || 'Unknown error'
|
||||
console.error('Download error', errorMsg)
|
||||
this.$toast.error(errorMsg)
|
||||
}
|
||||
},
|
||||
playClick() {
|
||||
if (this.streamIsPlaying) {
|
||||
this.$eventBus.$emit('pause-item')
|
||||
} else {
|
||||
this.$eventBus.$emit('play-item', {
|
||||
libraryItemId: this.libraryItemId,
|
||||
episodeId: this.episode.id
|
||||
})
|
||||
}
|
||||
},
|
||||
toggleFinished() {
|
||||
var updatePayload = {
|
||||
isFinished: !this.userIsFinished
|
||||
}
|
||||
this.isProcessingReadUpdate = true
|
||||
this.$axios
|
||||
.$patch(`/api/me/progress/${this.libraryItemId}/${this.episode.id}`, updatePayload)
|
||||
.then(() => {
|
||||
this.isProcessingReadUpdate = false
|
||||
this.$toast.success(`Item marked as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed', error)
|
||||
this.isProcessingReadUpdate = false
|
||||
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
25
components/tables/podcast/EpisodesTable.vue
Normal file
25
components/tables/podcast/EpisodesTable.vue
Normal file
|
@ -0,0 +1,25 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<template v-for="episode in episodes">
|
||||
<tables-podcast-episode-row :episode="episode" :library-item-id="libraryItemId" :key="episode.id" />
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
libraryItemId: String,
|
||||
episodes: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
methods: {},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
57
components/ui/ReadIconBtn.vue
Normal file
57
components/ui/ReadIconBtn.vue
Normal file
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<button class="icon-btn rounded-md flex items-center justify-center h-9 w-9 relative" :class="borderless ? '' : 'bg-primary border border-gray-600'" @click="clickBtn">
|
||||
<div class="w-5 h-5 text-white relative">
|
||||
<svg v-if="isRead" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgb(63, 181, 68)">
|
||||
<path d="M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-9 15l-5-5 1.41-1.41L10 13.17l7.59-7.59L19 7l-9 9z" />
|
||||
</svg>
|
||||
<svg v-else xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M19 1H5c-1.1 0-1.99.9-1.99 2L3 15.93c0 .69.35 1.3.88 1.66L12 23l8.11-5.41c.53-.36.88-.97.88-1.66L21 3c0-1.1-.9-2-2-2zm-7 19.6l-7-4.66V3h14v12.93l-7 4.67zm-2.01-7.42l-2.58-2.59L6 12l4 4 8-8-1.42-1.42z" />
|
||||
</svg>
|
||||
</div>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
isRead: Boolean,
|
||||
disabled: Boolean,
|
||||
borderless: Boolean
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
clickBtn(e) {
|
||||
if (this.disabled) {
|
||||
e.preventDefault()
|
||||
return
|
||||
}
|
||||
this.$emit('click')
|
||||
e.stopPropagation()
|
||||
}
|
||||
},
|
||||
mounted() {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
button.icon-btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
border-radius: 6px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0);
|
||||
transition: all 0.1s ease-in-out;
|
||||
}
|
||||
button.icon-btn:hover:not(:disabled)::before {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
button.icon-btn:disabled::before {
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
</style>
|
|
@ -62,12 +62,14 @@ export default {
|
|||
|
||||
var update = {
|
||||
id: data.id,
|
||||
libraryItemId: data.libraryItemId,
|
||||
partsRemaining,
|
||||
partsCompleted,
|
||||
totalParts: downloadItemParts.length,
|
||||
itemProgress
|
||||
}
|
||||
data.itemProgress = itemProgress
|
||||
data.episodes = downloadItemParts.filter((dip) => dip.episode).map((dip) => dip.episode)
|
||||
|
||||
console.log('Saving item update download payload', JSON.stringify(update))
|
||||
this.$set(this.itemDownloadingMap, update.id, update)
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
<script>
|
||||
import { AppUpdate } from '@robingenz/capacitor-app-update'
|
||||
import { AbsFileSystem } from '@/plugins/capacitor'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
|
@ -56,7 +55,7 @@ export default {
|
|||
},
|
||||
computed: {
|
||||
playerIsOpen() {
|
||||
return this.$store.getters['playerIsOpen']
|
||||
return this.$store.state.playerLibraryItemId
|
||||
},
|
||||
routeName() {
|
||||
return this.$route.name
|
||||
|
@ -110,58 +109,6 @@ export default {
|
|||
}, 5000)
|
||||
}
|
||||
},
|
||||
async searchFolder(downloadFolder) {
|
||||
try {
|
||||
var response = await AbsFileSystem.searchFolder({ folderUrl: downloadFolder.uri })
|
||||
var searchResults = response
|
||||
searchResults.folders = JSON.parse(searchResults.folders)
|
||||
searchResults.files = JSON.parse(searchResults.files)
|
||||
|
||||
console.log('Search folders results length', searchResults.folders.length)
|
||||
searchResults.folders = searchResults.folders.map((sr) => {
|
||||
if (sr.files) {
|
||||
sr.files = JSON.parse(sr.files)
|
||||
}
|
||||
return sr
|
||||
})
|
||||
|
||||
return searchResults
|
||||
} catch (error) {
|
||||
console.error('Failed', error)
|
||||
this.$toast.error('Failed to search downloads folder')
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// async syncDownloads(downloads, downloadFolder) {
|
||||
// console.log('Syncing downloads ' + downloads.length)
|
||||
// var mediaScanResults = await this.searchFolder(downloadFolder)
|
||||
|
||||
// this.$store.commit('downloads/setMediaScanResults', mediaScanResults)
|
||||
|
||||
// // Filter out media folders without any audio files
|
||||
// var mediaFolders = mediaScanResults.folders.filter((sr) => {
|
||||
// if (!sr.files) return false
|
||||
// var audioFiles = sr.files.filter((mf) => !!mf.isAudio)
|
||||
// return audioFiles.length
|
||||
// })
|
||||
|
||||
// downloads.forEach((download) => {
|
||||
// var mediaFolder = mediaFolders.find((mf) => mf.name === download.folderName)
|
||||
// if (mediaFolder) {
|
||||
// console.log('Found download ' + download.folderName)
|
||||
// if (download.isMissing) {
|
||||
// download.isMissing = false
|
||||
// this.$store.commit('downloads/addUpdateDownload', download)
|
||||
// }
|
||||
// } else {
|
||||
// console.error('Download not found ' + download.folderName)
|
||||
// if (!download.isMissing) {
|
||||
// download.isMissing = true
|
||||
// this.$store.commit('downloads/addUpdateDownload', download)
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// },
|
||||
async loadSavedSettings() {
|
||||
var userSavedServerSettings = await this.$localStore.getServerSettings()
|
||||
if (userSavedServerSettings) {
|
||||
|
@ -209,7 +156,12 @@ export default {
|
|||
}
|
||||
|
||||
const { user, userDefaultLibraryId } = authRes
|
||||
if (userDefaultLibraryId) {
|
||||
|
||||
// Set library - Use last library if set and available fallback to default user library
|
||||
var lastLibraryId = await this.$localStore.getLastLibraryId()
|
||||
if (lastLibraryId && (!user.librariesAccessible.length || user.librariesAccessible.includes(lastLibraryId))) {
|
||||
this.$store.commit('libraries/setCurrentLibrary', lastLibraryId)
|
||||
} else if (userDefaultLibraryId) {
|
||||
this.$store.commit('libraries/setCurrentLibrary', userDefaultLibraryId)
|
||||
}
|
||||
var serverConnectionConfig = await this.$db.setServerConnectionConfig(serverConfig)
|
||||
|
@ -247,7 +199,7 @@ export default {
|
|||
}
|
||||
this.inittingLibraries = true
|
||||
await this.$store.dispatch('libraries/load')
|
||||
console.log(`[default] initLibraries loaded`)
|
||||
console.log(`[default] initLibraries loaded ${this.currentLibraryId}`)
|
||||
this.$eventBus.$emit('library-changed')
|
||||
this.$store.dispatch('libraries/fetch', this.currentLibraryId)
|
||||
this.inittingLibraries = false
|
||||
|
@ -261,7 +213,7 @@ export default {
|
|||
console.log('[default] Calling syncLocalMediaProgress')
|
||||
var response = await this.$db.syncLocalMediaProgressWithServer()
|
||||
if (!response) {
|
||||
this.$toast.error('Failed to sync local media with server')
|
||||
if (this.$platform != 'web') this.$toast.error('Failed to sync local media with server')
|
||||
return
|
||||
}
|
||||
const { numLocalMediaProgressForServer, numServerProgressUpdates, numLocalProgressUpdates } = response
|
||||
|
@ -276,20 +228,12 @@ export default {
|
|||
}
|
||||
},
|
||||
userUpdated(user) {
|
||||
console.log('User updated', user)
|
||||
if (this.user && this.user.id == user.id) {
|
||||
this.$store.commit('user/setUser', user)
|
||||
}
|
||||
}
|
||||
},
|
||||
async mounted() {
|
||||
// this.$server.on('logout', this.userLoggedOut)
|
||||
// this.$server.on('connected', this.connected)
|
||||
// this.$server.on('connectionFailed', this.socketConnectionFailed)
|
||||
// this.$server.on('initialStream', this.initialStream)
|
||||
// this.$server.on('show_error_toast', this.showErrorToast)
|
||||
// this.$server.on('show_success_toast', this.showSuccessToast)
|
||||
|
||||
this.$socket.on('connection-update', this.socketConnectionUpdate)
|
||||
this.$socket.on('initialized', this.socketInit)
|
||||
this.$socket.on('user_updated', this.userUpdated)
|
||||
|
|
|
@ -75,7 +75,7 @@ export default {
|
|||
})
|
||||
},
|
||||
streaming() {
|
||||
return !!this.playableBooks.find((b) => b.id === this.$store.getters['getAudiobookIdStreaming'])
|
||||
return !!this.playableBooks.find((b) => this.$store.getters['getIsItemStreaming'](b.id))
|
||||
},
|
||||
showPlayButton() {
|
||||
return this.playableBooks.length
|
||||
|
@ -88,7 +88,7 @@ export default {
|
|||
return !prog || !prog.isFinished
|
||||
})
|
||||
if (nextBookNotRead) {
|
||||
this.$eventBus.$emit('play-item', nextBookNotRead.id)
|
||||
this.$eventBus.$emit('play-item', { libraryItemId: nextBookNotRead.id })
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -45,6 +45,9 @@ export default {
|
|||
}
|
||||
},
|
||||
mounted() {
|
||||
// Reset data on logouts
|
||||
this.$store.commit('libraries/reset')
|
||||
this.$store.commit('setIsFirstLoad', true)
|
||||
this.init()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<div class="w-32">
|
||||
<div class="relative">
|
||||
<covers-book-cover :library-item="libraryItem" :width="128" :book-cover-aspect-ratio="bookCoverAspectRatio" />
|
||||
<div class="absolute bottom-0 left-0 h-1.5 bg-yellow-400 shadow-sm z-10" :style="{ width: 128 * progressPercent + 'px' }"></div>
|
||||
<div v-if="!isPodcast" class="absolute bottom-0 left-0 h-1.5 bg-yellow-400 shadow-sm z-10" :style="{ width: 128 * progressPercent + 'px' }"></div>
|
||||
</div>
|
||||
<div class="flex my-4">
|
||||
<p v-if="numTracks" class="text-sm">{{ numTracks }} Tracks</p>
|
||||
|
@ -19,7 +19,7 @@
|
|||
<span class="px-4">{{ $bytesPretty(size) }}</span>
|
||||
</p>
|
||||
|
||||
<div v-if="progressPercent > 0" class="px-4 py-2 bg-primary text-sm font-semibold rounded-md text-gray-200 mt-4 relative" :class="resettingProgress ? 'opacity-25' : ''">
|
||||
<div v-if="!isPodcast && progressPercent > 0" class="px-4 py-2 bg-primary text-sm font-semibold rounded-md text-gray-200 mt-4 relative" :class="resettingProgress ? 'opacity-25' : ''">
|
||||
<p class="leading-6">Your Progress: {{ Math.round(progressPercent * 100) }}%</p>
|
||||
<p v-if="progressPercent < 1" class="text-gray-400 text-xs">{{ $elapsedPretty(userTimeRemaining) }} remaining</p>
|
||||
<div v-if="!resettingProgress" class="absolute -top-1.5 -right-1.5 p-1 w-5 h-5 rounded-full bg-bg hover:bg-error border border-primary flex items-center justify-center cursor-pointer" @click.stop="clearProgressClick">
|
||||
|
@ -61,6 +61,8 @@
|
|||
<p>{{ description }}</p>
|
||||
</div>
|
||||
|
||||
<tables-podcast-episodes-table v-if="isPodcast" :library-item-id="libraryItemId" :episodes="episodes" />
|
||||
|
||||
<modals-select-local-folder-modal v-model="showSelectLocalFolder" :media-type="mediaType" @select="selectedLocalFolder" />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -133,6 +135,9 @@ export default {
|
|||
mediaType() {
|
||||
return this.libraryItem.mediaType
|
||||
},
|
||||
isPodcast() {
|
||||
return this.mediaType == 'podcast'
|
||||
},
|
||||
media() {
|
||||
return this.libraryItem.media || {}
|
||||
},
|
||||
|
@ -143,6 +148,7 @@ export default {
|
|||
return this.mediaMetadata.title
|
||||
},
|
||||
author() {
|
||||
if (this.isPodcast) return this.mediaMetadata.author
|
||||
return this.mediaMetadata.authorName
|
||||
},
|
||||
description() {
|
||||
|
@ -185,10 +191,10 @@ export default {
|
|||
return this.userItemProgress ? this.userItemProgress.finishedAt : 0
|
||||
},
|
||||
isStreaming() {
|
||||
return this.$store.getters['isAudiobookStreaming'](this.libraryItemId)
|
||||
return this.isPlaying && !this.$store.state.playerIsLocal
|
||||
},
|
||||
isPlaying() {
|
||||
return this.$store.getters['isAudiobookPlaying'](this.libraryItemId)
|
||||
return this.$store.getters['getIsItemStreaming'](this.libraryItemId)
|
||||
},
|
||||
numTracks() {
|
||||
if (!this.media.tracks) return 0
|
||||
|
@ -219,8 +225,8 @@ export default {
|
|||
downloadItem() {
|
||||
return this.$store.getters['globals/getDownloadItem'](this.libraryItemId)
|
||||
},
|
||||
downloadItems() {
|
||||
return this.$store.state.globals.downloadItems || []
|
||||
episodes() {
|
||||
return this.media.episodes || []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -229,8 +235,8 @@ export default {
|
|||
},
|
||||
playClick() {
|
||||
// Todo: Allow playing local or streaming
|
||||
if (this.hasLocal) return this.$eventBus.$emit('play-item', this.localLibraryItem.id)
|
||||
this.$eventBus.$emit('play-item', this.libraryItem.id)
|
||||
if (this.hasLocal) return this.$eventBus.$emit('play-item', { libraryItemId: this.localLibraryItem.id })
|
||||
this.$eventBus.$emit('play-item', { libraryItemId: this.libraryItemId })
|
||||
},
|
||||
async clearProgressClick() {
|
||||
const { value } = await Dialog.confirm({
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
<div class="flex-grow px-2">
|
||||
<p class="text-sm">{{ mediaItem.media.metadata.title }}</p>
|
||||
<p v-if="mediaItem.mediaType == 'book'" class="text-xs text-gray-300">{{ mediaItem.media.tracks.length }} Track{{ mediaItem.media.tracks.length == 1 ? '' : 's' }}</p>
|
||||
<p v-else-if="mediaItem.mediaType == 'podcast'" class="text-xs text-gray-300">{{ mediaItem.media.episodes.length }} Episode{{ mediaItem.media.tracks.length == 1 ? '' : 's' }}</p>
|
||||
<p v-else-if="mediaItem.mediaType == 'podcast'" class="text-xs text-gray-300">{{ mediaItem.media.episodes.length }} Episode{{ mediaItem.media.episodes.length == 1 ? '' : 's' }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 flex items-center justify-center">
|
||||
<span class="material-icons text-xl text-gray-300">arrow_right</span>
|
||||
|
@ -78,7 +78,7 @@ export default {
|
|||
text: 'Remove',
|
||||
value: 'remove'
|
||||
}
|
||||
].filter((i) => !i.value == 'rescan' || this.localLibraryItems.length) // Filter out rescan if there are no local library items
|
||||
].filter((i) => i.value != 'rescan' || this.localLibraryItems.length) // Filter out rescan if there are no local library items
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -110,7 +110,7 @@ export default {
|
|||
}
|
||||
},
|
||||
play(mediaItem) {
|
||||
this.$eventBus.$emit('play-item', mediaItem.id)
|
||||
this.$eventBus.$emit('play-item', { libraryItemId: mediaItem.id })
|
||||
},
|
||||
async scanFolder(forceAudioProbe = false) {
|
||||
this.isScanning = true
|
||||
|
@ -150,11 +150,13 @@ export default {
|
|||
var items = (await this.$db.getLocalLibraryItemsInFolder(this.folderId)) || []
|
||||
console.log('Init folder', this.folderId, items)
|
||||
this.localLibraryItems = items.map((lmi) => {
|
||||
console.log('Local library item', JSON.stringify(lmi))
|
||||
return {
|
||||
...lmi,
|
||||
coverPathSrc: lmi.coverContentUrl ? Capacitor.convertFileSrc(lmi.coverContentUrl) : null
|
||||
}
|
||||
})
|
||||
|
||||
if (this.shouldScan) {
|
||||
this.scanFolder()
|
||||
}
|
||||
|
|
|
@ -1,32 +1,33 @@
|
|||
<template>
|
||||
<div class="w-full h-full py-6 px-4">
|
||||
<div class="w-full h-full py-6 px-2">
|
||||
<div v-if="localLibraryItem" class="w-full h-full">
|
||||
<div class="flex items-center mb-2">
|
||||
<div class="px-2 flex items-center mb-2">
|
||||
<p class="text-base font-book font-semibold">{{ mediaMetadata.title }}</p>
|
||||
<div class="flex-grow" />
|
||||
|
||||
<button v-if="audioTracks.length" class="shadow-sm text-accent flex items-center justify-center rounded-full mx-2" @click.stop="play">
|
||||
<button v-if="audioTracks.length && !isPodcast" class="shadow-sm text-accent flex items-center justify-center rounded-full mx-2" @click.stop="play">
|
||||
<span class="material-icons" style="font-size: 2rem">play_arrow</span>
|
||||
</button>
|
||||
<span class="material-icons" @click="showItemDialog">more_vert</span>
|
||||
</div>
|
||||
|
||||
<p class="text-sm mb-0.5 text-white text-opacity-75">Folder: {{ folderName }}</p>
|
||||
<p class="px-2 text-sm mb-0.5 text-white text-opacity-75">Folder: {{ folderName }}</p>
|
||||
|
||||
<p class="mb-4 text-xs text-gray-400">{{ libraryItemId ? 'Linked to item on server ' + liServerAddress : 'Not linked to server item' }}</p>
|
||||
<p class="px-2 mb-4 text-xs text-gray-400">{{ libraryItemId ? 'Linked to item on server ' + liServerAddress : 'Not linked to server item' }}</p>
|
||||
|
||||
<div v-if="isScanning" class="w-full text-center p-4">
|
||||
<p>Scanning...</p>
|
||||
</div>
|
||||
<div v-else class="w-full media-item-container overflow-y-auto">
|
||||
<div v-if="!isPodcast" class="w-full">
|
||||
<p class="text-base mb-2">Audio Tracks ({{ audioTracks.length }})</p>
|
||||
<template v-for="track in audioTracks">
|
||||
<div :key="track.localFileId" class="flex items-center my-1">
|
||||
<div class="w-12 h-12 flex items-center justify-center" style="min-width: 48px">
|
||||
<div class="w-10 h-12 flex items-center justify-center" style="min-width: 48px">
|
||||
<p class="font-mono font-bold text-xl">{{ track.index }}</p>
|
||||
</div>
|
||||
<div class="flex-grow px-2">
|
||||
<p class="text-sm">{{ track.title }}</p>
|
||||
<p class="text-xs">{{ track.title }}</p>
|
||||
</div>
|
||||
<div class="w-20 text-center text-gray-300" style="min-width: 80px">
|
||||
<p class="text-xs">{{ track.mimeType }}</p>
|
||||
|
@ -37,6 +38,27 @@
|
|||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
<div v-else class="w-full">
|
||||
<p class="text-base mb-2">Episodes ({{ audioTracks.length }})</p>
|
||||
<template v-for="episode in audioTracks">
|
||||
<div :key="episode.id" class="flex items-center my-1">
|
||||
<div class="w-10 h-12 flex items-center justify-center" style="min-width: 48px">
|
||||
<p class="font-mono font-bold text-xl">{{ episode.index }}</p>
|
||||
</div>
|
||||
<div class="flex-grow px-2">
|
||||
<p class="text-xs">{{ episode.title }}</p>
|
||||
</div>
|
||||
<div class="w-20 text-center text-gray-300" style="min-width: 80px">
|
||||
<p class="text-xs">{{ episode.audioTrack.mimeType }}</p>
|
||||
<p class="text-sm">{{ $elapsedPretty(episode.audioTrack.duration) }}</p>
|
||||
</div>
|
||||
<div class="w-12 h-12 flex items-center justify-center" style="min-width: 48px">
|
||||
<span class="material-icons" @click="showTrackDialog(episode)">more_vert</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<p v-if="otherFiles.length" class="text-lg mb-2 pt-8">Other Files</p>
|
||||
<template v-for="file in otherFiles">
|
||||
|
@ -56,7 +78,7 @@
|
|||
</template>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="w-full h-full">
|
||||
<div v-else class="px-2 w-full h-full">
|
||||
<p class="text-lg text-center px-8">{{ failed ? 'Failed to get local library item ' + localLibraryItemId : 'Loading..' }}</p>
|
||||
</div>
|
||||
|
||||
|
@ -84,7 +106,8 @@ export default {
|
|||
folder: null,
|
||||
isScanning: false,
|
||||
showDialog: false,
|
||||
selectedAudioTrack: null
|
||||
selectedAudioTrack: null,
|
||||
selectedEpisode: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
|
@ -100,6 +123,7 @@ export default {
|
|||
return []
|
||||
}
|
||||
return this.localFiles.filter((lf) => {
|
||||
if (this.isPodcast) return !this.audioTracks.find((episode) => episode.audioTrack.localFileId == lf.id)
|
||||
return !this.audioTracks.find((at) => at.localFileId == lf.id)
|
||||
})
|
||||
},
|
||||
|
@ -109,6 +133,9 @@ export default {
|
|||
mediaType() {
|
||||
return this.localLibraryItem ? this.localLibraryItem.mediaType : null
|
||||
},
|
||||
isPodcast() {
|
||||
return this.mediaType == 'podcast'
|
||||
},
|
||||
libraryItemId() {
|
||||
return this.localLibraryItem ? this.localLibraryItem.libraryItemId : null
|
||||
},
|
||||
|
@ -165,11 +192,17 @@ export default {
|
|||
this.showDialog = true
|
||||
},
|
||||
showTrackDialog(track) {
|
||||
if (this.isPodcast) {
|
||||
this.selectedAudioTrack = null
|
||||
this.selectedEpisode = track
|
||||
} else {
|
||||
this.selectedEpisode = null
|
||||
this.selectedAudioTrack = track
|
||||
}
|
||||
this.showDialog = true
|
||||
},
|
||||
play() {
|
||||
this.$eventBus.$emit('play-item', this.localLibraryItemId)
|
||||
this.$eventBus.$emit('play-item', { libraryItemId: this.localLibraryItemId })
|
||||
},
|
||||
getCapImageSrc(contentUrl) {
|
||||
return Capacitor.convertFileSrc(contentUrl)
|
||||
|
@ -185,13 +218,34 @@ export default {
|
|||
} else if (action == 'delete') {
|
||||
this.deleteItem()
|
||||
} else if (action == 'track-delete') {
|
||||
this.deleteTrack()
|
||||
if (this.isPodcast) this.deleteEpisode()
|
||||
else this.deleteTrack()
|
||||
}
|
||||
this.showDialog = false
|
||||
},
|
||||
getLocalFileForTrack(localFileId) {
|
||||
return this.localFiles.find((lf) => lf.id == localFileId)
|
||||
},
|
||||
async deleteEpisode() {
|
||||
if (!this.selectedEpisode) return
|
||||
var localFile = this.getLocalFileForTrack(this.selectedEpisode.audioTrack.localFileId)
|
||||
if (!localFile) {
|
||||
this.$toast.error('Audio track does not have matching local file..')
|
||||
return
|
||||
}
|
||||
var trackPath = localFile ? localFile.basePath : this.selectedEpisode.title
|
||||
const { value } = await Dialog.confirm({
|
||||
title: 'Confirm',
|
||||
message: `Warning! This will delete the audio file "${trackPath}" from your file system. Are you sure?`
|
||||
})
|
||||
if (value) {
|
||||
var res = await AbsFileSystem.deleteTrackFromItem({ id: this.localLibraryItem.id, trackLocalFileId: localFile.id, trackContentUrl: this.selectedEpisode.audioTrack.contentUrl })
|
||||
if (res && res.id) {
|
||||
this.$toast.success('Deleted track successfully')
|
||||
this.localLibraryItem = res
|
||||
} else this.$toast.error('Failed to delete')
|
||||
}
|
||||
},
|
||||
async deleteTrack() {
|
||||
if (!this.selectedAudioTrack) {
|
||||
return
|
||||
|
|
|
@ -63,7 +63,6 @@ class LocalStorage {
|
|||
async setBookshelfListView(useIt) {
|
||||
try {
|
||||
await Storage.set({ key: 'bookshelfListView', value: useIt ? '1' : '0' })
|
||||
this.getBookshelfListView()
|
||||
} catch (error) {
|
||||
console.error('[LocalStorage] Failed to set bookshelf list view', error)
|
||||
}
|
||||
|
@ -78,6 +77,25 @@ class LocalStorage {
|
|||
return false
|
||||
}
|
||||
}
|
||||
|
||||
async setLastLibraryId(libraryId) {
|
||||
try {
|
||||
await Storage.set({ key: 'lastLibraryId', value: libraryId })
|
||||
console.log('[LocalStorage] Set Last Library Id', libraryId)
|
||||
} catch (error) {
|
||||
console.error('[LocalStorage] Failed to set current library', error)
|
||||
}
|
||||
}
|
||||
|
||||
async getLastLibraryId() {
|
||||
try {
|
||||
var obj = await Storage.get({ key: 'lastLibraryId' }) || {}
|
||||
return obj.value || null
|
||||
} catch (error) {
|
||||
console.error('[LocalStorage] Failed to get last library id', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import Vue from 'vue'
|
||||
import { io } from 'socket.io-client'
|
||||
import EventEmitter from 'events'
|
||||
|
||||
|
@ -58,6 +57,7 @@ class ServerSocket extends EventEmitter {
|
|||
this.connected = true
|
||||
this.$store.commit('setSocketConnected', true)
|
||||
this.emit('connection-update', true)
|
||||
this.socket.emit('auth', this.token) // Required to connect a user with their socket
|
||||
}
|
||||
|
||||
onDisconnect(reason) {
|
||||
|
|
|
@ -6,8 +6,11 @@ export const state = () => ({
|
|||
})
|
||||
|
||||
export const getters = {
|
||||
getDownloadItem: state => libraryItemId => {
|
||||
return state.itemDownloads.find(i => i.id == libraryItemId)
|
||||
getDownloadItem: state => (libraryItemId, episodeId = null) => {
|
||||
return state.itemDownloads.find(i => {
|
||||
if (episodeId && !i.episodes.some(e => e.id == episodeId)) return false
|
||||
return i.id == libraryItemId
|
||||
})
|
||||
},
|
||||
getLibraryItemCoverSrc: (state, getters, rootState, rootGetters) => (libraryItem, placeholder = '/book_placeholder.jpg') => {
|
||||
if (!libraryItem) return placeholder
|
||||
|
|
|
@ -1,16 +1,14 @@
|
|||
import Vue from 'vue'
|
||||
import { Network } from '@capacitor/network'
|
||||
|
||||
export const state = () => ({
|
||||
streamAudiobook: null,
|
||||
playingDownload: null,
|
||||
playOnLoad: false,
|
||||
serverUrl: null,
|
||||
playerLibraryItemId: null,
|
||||
playerEpisodeId: null,
|
||||
playerIsLocal: false,
|
||||
playerIsPlaying: false,
|
||||
appUpdateInfo: null,
|
||||
socketConnected: false,
|
||||
networkConnected: false,
|
||||
networkConnectionType: null,
|
||||
streamListener: null,
|
||||
isFirstLoad: true,
|
||||
hasStoragePermission: false,
|
||||
selectedBook: null,
|
||||
|
@ -22,16 +20,13 @@ export const state = () => ({
|
|||
|
||||
export const getters = {
|
||||
playerIsOpen: (state) => {
|
||||
return state.streamAudiobook || state.playingDownload
|
||||
return state.streamAudiobook
|
||||
},
|
||||
isAudiobookStreaming: (state) => id => {
|
||||
return (state.streamAudiobook && state.streamAudiobook.id === id)
|
||||
getIsItemStreaming: state => libraryItemId => {
|
||||
return state.playerLibraryItemId == libraryItemId
|
||||
},
|
||||
isAudiobookPlaying: (state) => id => {
|
||||
return (state.playingDownload && state.playingDownload.id === id) || (state.streamAudiobook && state.streamAudiobook.id === id)
|
||||
},
|
||||
getAudiobookIdStreaming: state => {
|
||||
return state.streamAudiobook ? state.streamAudiobook.id : null
|
||||
getIsEpisodeStreaming: state => (libraryItemId, episodeId) => {
|
||||
return state.playerLibraryItemId == libraryItemId && state.playerEpisodeId == episodeId
|
||||
},
|
||||
getServerSetting: state => key => {
|
||||
if (!state.serverSettings) return null
|
||||
|
@ -61,6 +56,14 @@ export const actions = {
|
|||
}
|
||||
|
||||
export const mutations = {
|
||||
setPlayerItem(state, playbackSession) {
|
||||
state.playerLibraryItemId = playbackSession ? playbackSession.libraryItemId || null : null
|
||||
state.playerEpisodeId = playbackSession ? playbackSession.episodeId || null : null
|
||||
state.playerIsLocal = playbackSession ? playbackSession.playMethod == this.$constants.PlayMethod.LOCAL : false
|
||||
},
|
||||
setPlayerPlaying(state, val) {
|
||||
state.playerIsPlaying = val
|
||||
},
|
||||
setHasStoragePermission(state, val) {
|
||||
state.hasStoragePermission = val
|
||||
},
|
||||
|
@ -70,36 +73,6 @@ export const mutations = {
|
|||
setAppUpdateInfo(state, info) {
|
||||
state.appUpdateInfo = info
|
||||
},
|
||||
closeStream(state, audiobookId) {
|
||||
if (state.streamAudiobook && state.streamAudiobook.id !== audiobookId) {
|
||||
return
|
||||
}
|
||||
state.streamAudiobook = null
|
||||
},
|
||||
setPlayOnLoad(state, val) {
|
||||
state.playOnLoad = val
|
||||
},
|
||||
setStreamAudiobook(state, audiobook) {
|
||||
if (audiobook) {
|
||||
state.playingDownload = null
|
||||
}
|
||||
Vue.set(state, 'streamAudiobook', audiobook)
|
||||
if (state.streamListener) {
|
||||
state.streamListener('stream', audiobook)
|
||||
}
|
||||
},
|
||||
setPlayingDownload(state, download) {
|
||||
if (download) {
|
||||
state.streamAudiobook = null
|
||||
}
|
||||
Vue.set(state, 'playingDownload', download)
|
||||
if (state.streamListener) {
|
||||
state.streamListener('download', download)
|
||||
}
|
||||
},
|
||||
setServerUrl(state, url) {
|
||||
state.serverUrl = url
|
||||
},
|
||||
setSocketConnected(state, val) {
|
||||
state.socketConnected = val
|
||||
},
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
export const state = () => ({
|
||||
libraries: [],
|
||||
lastLoad: 0,
|
||||
listeners: [],
|
||||
currentLibraryId: '',
|
||||
showModal: false,
|
||||
folders: [],
|
||||
folderLastUpdate: 0,
|
||||
issues: 0,
|
||||
filterData: null
|
||||
})
|
||||
|
@ -66,13 +63,13 @@ export const actions = {
|
|||
return this.$axios
|
||||
.$get(`/api/libraries`)
|
||||
.then((data) => {
|
||||
// Set current library
|
||||
if (data.length) {
|
||||
// Set current library if not already set or was not returned in results
|
||||
if (data.length && (!state.currentLibraryId || !data.find(li => li.id == state.currentLibraryId))) {
|
||||
commit('setCurrentLibrary', data[0].id)
|
||||
}
|
||||
|
||||
commit('set', data)
|
||||
commit('setLastLoad')
|
||||
commit('setLastLoad', Date.now())
|
||||
return true
|
||||
})
|
||||
.catch((error) => {
|
||||
|
@ -85,27 +82,21 @@ export const actions = {
|
|||
}
|
||||
|
||||
export const mutations = {
|
||||
setFolders(state, folders) {
|
||||
state.folders = folders
|
||||
},
|
||||
setFoldersLastUpdate(state) {
|
||||
state.folderLastUpdate = Date.now()
|
||||
},
|
||||
setShowModal(state, val) {
|
||||
state.showModal = val
|
||||
},
|
||||
setLastLoad(state) {
|
||||
state.lastLoad = Date.now()
|
||||
setLastLoad(state, val) {
|
||||
state.lastLoad = val
|
||||
},
|
||||
reset(state) {
|
||||
state.lastLoad = 0
|
||||
state.libraries = []
|
||||
},
|
||||
setCurrentLibrary(state, val) {
|
||||
state.currentLibraryId = val
|
||||
},
|
||||
set(state, libraries) {
|
||||
console.log('set libraries', libraries)
|
||||
state.libraries = libraries
|
||||
state.listeners.forEach((listener) => {
|
||||
listener.meth()
|
||||
})
|
||||
},
|
||||
addUpdate(state, library) {
|
||||
var index = state.libraries.findIndex(a => a.id === library.id)
|
||||
|
@ -114,25 +105,9 @@ export const mutations = {
|
|||
} else {
|
||||
state.libraries.push(library)
|
||||
}
|
||||
|
||||
state.listeners.forEach((listener) => {
|
||||
listener.meth()
|
||||
})
|
||||
},
|
||||
remove(state, library) {
|
||||
state.libraries = state.libraries.filter(a => a.id !== library.id)
|
||||
|
||||
state.listeners.forEach((listener) => {
|
||||
listener.meth()
|
||||
})
|
||||
},
|
||||
addListener(state, listener) {
|
||||
var index = state.listeners.findIndex(l => l.id === listener.id)
|
||||
if (index >= 0) state.listeners.splice(index, 1, listener)
|
||||
else state.listeners.push(listener)
|
||||
},
|
||||
removeListener(state, listenerId) {
|
||||
state.listeners = state.listeners.filter(l => l.id !== listenerId)
|
||||
},
|
||||
setLibraryIssues(state, val) {
|
||||
state.issues = val
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue