mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-04 11:04:46 +02:00
Merge
This commit is contained in:
parent
ccba8dc3c7
commit
ae195e7b58
32 changed files with 626 additions and 191 deletions
|
@ -12,8 +12,9 @@ class CastOptionsProvider : OptionsProvider {
|
||||||
override fun getCastOptions(context: Context): CastOptions {
|
override fun getCastOptions(context: Context): CastOptions {
|
||||||
Log.d("CastOptionsProvider", "getCastOptions")
|
Log.d("CastOptionsProvider", "getCastOptions")
|
||||||
var appId = "FD1F76C5"
|
var appId = "FD1F76C5"
|
||||||
|
var defaultId =CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID
|
||||||
return CastOptions.Builder()
|
return CastOptions.Builder()
|
||||||
.setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID).setCastMediaOptions(
|
.setReceiverApplicationId(appId).setCastMediaOptions(
|
||||||
CastMediaOptions.Builder()
|
CastMediaOptions.Builder()
|
||||||
// We manage the media session and the notifications ourselves.
|
// We manage the media session and the notifications ourselves.
|
||||||
.setMediaSessionEnabled(false)
|
.setMediaSessionEnabled(false)
|
||||||
|
|
|
@ -2,6 +2,11 @@ package com.audiobookshelf.app.data
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty
|
||||||
|
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||||
|
import com.fasterxml.jackson.databind.jsonschema.JsonSerializableSchema
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
data class AudioProbeStream(
|
data class AudioProbeStream(
|
||||||
|
|
|
@ -71,6 +71,8 @@ open class MediaType(var metadata:MediaTypeMetadata, var coverPath:String?) {
|
||||||
open fun addAudioTrack(audioTrack:AudioTrack) { }
|
open fun addAudioTrack(audioTrack:AudioTrack) { }
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
open fun removeAudioTrack(localFileId:String) { }
|
open fun removeAudioTrack(localFileId:String) { }
|
||||||
|
@JsonIgnore
|
||||||
|
open fun getLocalCopy():MediaType { return MediaType(MediaTypeMetadata(""),null) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
@ -92,10 +94,11 @@ class Podcast(
|
||||||
episodes = episodes?.filter { ep ->
|
episodes = episodes?.filter { ep ->
|
||||||
audioTracks.find { it.localFileId == ep.audioTrack?.localFileId } != null
|
audioTracks.find { it.localFileId == ep.audioTrack?.localFileId } != null
|
||||||
} as MutableList<PodcastEpisode>
|
} as MutableList<PodcastEpisode>
|
||||||
|
|
||||||
// Add new episodes
|
// Add new episodes
|
||||||
audioTracks.forEach { at ->
|
audioTracks.forEach { at ->
|
||||||
if (episodes?.find{ it.audioTrack?.localFileId == at.localFileId } == null) {
|
if (episodes?.find{ it.audioTrack?.localFileId == at.localFileId } == null) {
|
||||||
var newEpisode = PodcastEpisode("local_" + at.localFileId,episodes?.size ?: 0 + 1,null,null,at.title,null,null,null,at)
|
var newEpisode = PodcastEpisode("local_" + at.localFileId,episodes?.size ?: 0 + 1,null,null,at.title,null,null,null,at,at.duration,0, null)
|
||||||
episodes?.add(newEpisode)
|
episodes?.add(newEpisode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -108,7 +111,7 @@ class Podcast(
|
||||||
}
|
}
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
override fun addAudioTrack(audioTrack:AudioTrack) {
|
override fun addAudioTrack(audioTrack:AudioTrack) {
|
||||||
var newEpisode = PodcastEpisode("local_" + audioTrack.localFileId,episodes?.size ?: 0 + 1,null,null,audioTrack.title,null,null,null,audioTrack)
|
var newEpisode = PodcastEpisode("local_" + audioTrack.localFileId,episodes?.size ?: 0 + 1,null,null,audioTrack.title,null,null,null,audioTrack,audioTrack.duration,0, null)
|
||||||
episodes?.add(newEpisode)
|
episodes?.add(newEpisode)
|
||||||
|
|
||||||
var index = 1
|
var index = 1
|
||||||
|
@ -129,7 +132,7 @@ class Podcast(
|
||||||
}
|
}
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun addEpisode(audioTrack:AudioTrack, episode:PodcastEpisode) {
|
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)
|
var 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)
|
episodes?.add(newEpisode)
|
||||||
|
|
||||||
var index = 1
|
var index = 1
|
||||||
|
@ -138,6 +141,12 @@ class Podcast(
|
||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Used for FolderScanner local podcast item to get copy of Podcast excluding episodes
|
||||||
|
@JsonIgnore
|
||||||
|
override fun getLocalCopy(): Podcast {
|
||||||
|
return Podcast(metadata as PodcastMetadata,coverPath,tags, mutableListOf(),autoDownloadEpisodes)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
@ -158,7 +167,7 @@ class Book(
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
override fun setAudioTracks(audioTracks:MutableList<AudioTrack>) {
|
override fun setAudioTracks(audioTracks:MutableList<AudioTrack>) {
|
||||||
tracks = audioTracks
|
tracks = audioTracks
|
||||||
|
tracks?.sortBy { it.index }
|
||||||
// TODO: Is it necessary to calculate this each time? check if can remove safely
|
// TODO: Is it necessary to calculate this each time? check if can remove safely
|
||||||
var totalDuration = 0.0
|
var totalDuration = 0.0
|
||||||
tracks?.forEach {
|
tracks?.forEach {
|
||||||
|
@ -195,6 +204,11 @@ class Book(
|
||||||
}
|
}
|
||||||
duration = totalDuration
|
duration = totalDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
override fun getLocalCopy(): Book {
|
||||||
|
return Book(metadata as BookMetadata,coverPath,tags, mutableListOf(),chapters,mutableListOf(),null,null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This auto-detects whether it is a BookMetadata or PodcastMetadata
|
// This auto-detects whether it is a BookMetadata or PodcastMetadata
|
||||||
|
@ -261,7 +275,10 @@ data class PodcastEpisode(
|
||||||
var subtitle:String?,
|
var subtitle:String?,
|
||||||
var description:String?,
|
var description:String?,
|
||||||
var audioFile:AudioFile?,
|
var audioFile:AudioFile?,
|
||||||
var audioTrack:AudioTrack?
|
var audioTrack:AudioTrack?,
|
||||||
|
var duration:Double?,
|
||||||
|
var size:Long?,
|
||||||
|
var serverEpisodeId:String? // For local podcasts to match with server podcasts
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
@ -275,7 +292,8 @@ data class FileMetadata(
|
||||||
var filename:String,
|
var filename:String,
|
||||||
var ext:String,
|
var ext:String,
|
||||||
var path:String,
|
var path:String,
|
||||||
var relPath:String
|
var relPath:String,
|
||||||
|
var size:Long?
|
||||||
)
|
)
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.util.Log
|
||||||
import com.audiobookshelf.app.plugins.AbsDownloader
|
import com.audiobookshelf.app.plugins.AbsDownloader
|
||||||
import io.paperdb.Paper
|
import io.paperdb.Paper
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
class DbManager {
|
class DbManager {
|
||||||
val tag = "DbManager"
|
val tag = "DbManager"
|
||||||
|
@ -130,6 +131,92 @@ class DbManager {
|
||||||
Paper.book("localMediaProgress").delete(localMediaProgressId)
|
Paper.book("localMediaProgress").delete(localMediaProgressId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeAllLocalMediaProgress() {
|
||||||
|
Paper.book("localMediaProgress").destroy()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure all local file ids still exist
|
||||||
|
fun cleanLocalLibraryItems() {
|
||||||
|
var localLibraryItems = getLocalLibraryItems()
|
||||||
|
|
||||||
|
localLibraryItems.forEach { lli ->
|
||||||
|
var hasUpates = false
|
||||||
|
|
||||||
|
// Check local files
|
||||||
|
lli.localFiles = lli.localFiles.filter { localFile ->
|
||||||
|
var file = File(localFile.absolutePath)
|
||||||
|
if (!file.exists()) {
|
||||||
|
Log.d(tag, "cleanLocalLibraryItems: Local file ${localFile.absolutePath} was removed from library item ${lli.media.metadata.title}")
|
||||||
|
hasUpates = true
|
||||||
|
}
|
||||||
|
file.exists()
|
||||||
|
} as MutableList<LocalFile>
|
||||||
|
|
||||||
|
// Check audio tracks and episodes
|
||||||
|
if (lli.isPodcast) {
|
||||||
|
var podcast = lli.media as Podcast
|
||||||
|
podcast.episodes = podcast.episodes?.filter { ep ->
|
||||||
|
if (lli.localFiles.find { lf -> lf.id == ep.audioTrack?.localFileId } == null) {
|
||||||
|
Log.d(tag, "cleanLocalLibraryItems: Podcast episode ${ep.title} was removed from library item ${lli.media.metadata.title}")
|
||||||
|
hasUpates = true
|
||||||
|
}
|
||||||
|
ep.audioTrack != null && lli.localFiles.find { lf -> lf.id == ep.audioTrack?.localFileId } != null
|
||||||
|
} as MutableList<PodcastEpisode>
|
||||||
|
} else {
|
||||||
|
var book = lli.media as Book
|
||||||
|
book.tracks = book.tracks?.filter { track ->
|
||||||
|
if (lli.localFiles.find { lf -> lf.id == track.localFileId } == null) {
|
||||||
|
Log.d(tag, "cleanLocalLibraryItems: Audio track ${track.title} was removed from library item ${lli.media.metadata.title}")
|
||||||
|
hasUpates = true
|
||||||
|
}
|
||||||
|
lli.localFiles.find { lf -> lf.id == track.localFileId } != null
|
||||||
|
} as MutableList<AudioTrack>
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check cover still there
|
||||||
|
lli.coverAbsolutePath?.let {
|
||||||
|
var coverFile = File(it)
|
||||||
|
|
||||||
|
if (!coverFile.exists()) {
|
||||||
|
Log.d(tag, "cleanLocalLibraryItems: Cover $it was removed from library item ${lli.media.metadata.title}")
|
||||||
|
lli.coverAbsolutePath = null
|
||||||
|
lli.coverContentUrl = null
|
||||||
|
hasUpates = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasUpates) {
|
||||||
|
Log.d(tag, "cleanLocalLibraryItems: Saving local library item ${lli.id}")
|
||||||
|
Paper.book("localLibraryItems").write(lli.id, lli)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove any local media progress where the local media item is not found
|
||||||
|
fun cleanLocalMediaProgress() {
|
||||||
|
var localMediaProgress = getAllLocalMediaProgress()
|
||||||
|
var localLibraryItems = getLocalLibraryItems()
|
||||||
|
localMediaProgress.forEach {
|
||||||
|
var matchingLLI = localLibraryItems.find { lli -> lli.id == it.localLibraryItemId }
|
||||||
|
if (matchingLLI == null) {
|
||||||
|
Log.d(tag, "cleanLocalMediaProgress: No matching local library item for local media progress ${it.id} - removing")
|
||||||
|
Paper.book("localMediaProgress").delete(it.id)
|
||||||
|
} else if (matchingLLI.isPodcast) {
|
||||||
|
if (it.localEpisodeId.isNullOrEmpty()) {
|
||||||
|
Log.d(tag, "cleanLocalMediaProgress: Podcast media progress has no episode id - removing")
|
||||||
|
Paper.book("localMediaProgress").delete(it.id)
|
||||||
|
} else {
|
||||||
|
var podcast = matchingLLI.media as Podcast
|
||||||
|
var matchingLEp = podcast.episodes?.find { ep -> ep.id == it.localEpisodeId }
|
||||||
|
if (matchingLEp == null) {
|
||||||
|
Log.d(tag, "cleanLocalMediaProgress: Podcast media progress for episode ${it.localEpisodeId} not found - removing")
|
||||||
|
Paper.book("localMediaProgress").delete(it.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun saveLocalPlaybackSession(playbackSession:PlaybackSession) {
|
fun saveLocalPlaybackSession(playbackSession:PlaybackSession) {
|
||||||
Paper.book("localPlaybackSession").write(playbackSession.id,playbackSession)
|
Paper.book("localPlaybackSession").write(playbackSession.id,playbackSession)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package com.audiobookshelf.app.data
|
package com.audiobookshelf.app.data
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
@ -26,6 +27,9 @@ data class LocalLibraryItem(
|
||||||
var libraryItemId:String?
|
var libraryItemId:String?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@get:JsonIgnore
|
||||||
|
val isPodcast get() = mediaType == "podcast"
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getDuration():Double {
|
fun getDuration():Double {
|
||||||
var total = 0.0
|
var total = 0.0
|
||||||
|
@ -50,25 +54,26 @@ data class LocalLibraryItem(
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getPlaybackSession(episodeId:String):PlaybackSession {
|
fun getPlaybackSession(episode:PodcastEpisode?):PlaybackSession {
|
||||||
var sessionId = "play-${UUID.randomUUID()}"
|
var localEpisodeId = episode?.id
|
||||||
|
var sessionId = "play_local_${UUID.randomUUID()}"
|
||||||
|
|
||||||
val mediaProgressId = if (episodeId.isNullOrEmpty()) id else "$id-$episodeId"
|
val mediaProgressId = if (localEpisodeId.isNullOrEmpty()) id else "$id-$localEpisodeId"
|
||||||
var mediaProgress = DeviceManager.dbManager.getLocalMediaProgress(mediaProgressId)
|
var mediaProgress = DeviceManager.dbManager.getLocalMediaProgress(mediaProgressId)
|
||||||
var currentTime = mediaProgress?.currentTime ?: 0.0
|
var currentTime = mediaProgress?.currentTime ?: 0.0
|
||||||
|
|
||||||
// TODO: Clean up add mediaType methods for displayTitle and displayAuthor
|
// TODO: Clean up add mediaType methods for displayTitle and displayAuthor
|
||||||
var mediaMetadata = media.metadata
|
var mediaMetadata = media.metadata
|
||||||
var chapters = if (mediaType == "book") (media as Book).chapters else mutableListOf()
|
var chapters = if (mediaType == "book") (media as Book).chapters else mutableListOf()
|
||||||
var authorName = "Unknown"
|
var audioTracks = media.getAudioTracks() as MutableList<AudioTrack>
|
||||||
if (mediaType == "book") {
|
var authorName = mediaMetadata.getAuthorDisplayName()
|
||||||
var bookMetadata = mediaMetadata as BookMetadata
|
if (episode != null) { // Get podcast episode audio track
|
||||||
authorName = bookMetadata?.authorName ?: "Unknown"
|
episode.audioTrack?.let { at -> mutableListOf(at) }?.let { tracks -> audioTracks = tracks }
|
||||||
|
Log.d("LocalLibraryItem", "getPlaybackSession: Got podcast episode audio track ${audioTracks.size}")
|
||||||
}
|
}
|
||||||
|
|
||||||
var episodeIdNullable = if (episodeId.isNullOrEmpty()) null else episodeId
|
|
||||||
var dateNow = System.currentTimeMillis()
|
var dateNow = System.currentTimeMillis()
|
||||||
return PlaybackSession(sessionId,serverUserId,libraryItemId,episodeIdNullable, mediaType, mediaMetadata, chapters ?: mutableListOf(), mediaMetadata.title, authorName,null,getDuration(),PLAYMETHOD_LOCAL,dateNow,0L,0L, media.getAudioTracks() as MutableList<AudioTrack>,currentTime,null,this,serverConnectionConfigId, serverAddress)
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
|
|
|
@ -65,6 +65,7 @@ data class LocalMediaItem(
|
||||||
return LocalLibraryItem(id, folderId, basePath,absolutePath, contentUrl, false,mediaType, book, localFiles, coverContentUrl, coverAbsolutePath,true,null,null,null,null)
|
return LocalLibraryItem(id, folderId, basePath,absolutePath, contentUrl, false,mediaType, book, localFiles, coverContentUrl, coverAbsolutePath,true,null,null,null,null)
|
||||||
} else {
|
} else {
|
||||||
var podcast = Podcast(mediaMetadata as PodcastMetadata, coverAbsolutePath, mutableListOf(), mutableListOf(), false)
|
var podcast = Podcast(mediaMetadata as PodcastMetadata, coverAbsolutePath, mutableListOf(), mutableListOf(), false)
|
||||||
|
podcast.setAudioTracks(audioTracks) // Builds episodes from audio tracks
|
||||||
return LocalLibraryItem(id, folderId, basePath,absolutePath, contentUrl, false, mediaType, podcast,localFiles,coverContentUrl, coverAbsolutePath, true, null,null,null,null)
|
return LocalLibraryItem(id, folderId, basePath,absolutePath, contentUrl, false, mediaType, podcast,localFiles,coverContentUrl, coverAbsolutePath, true, null,null,null,null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
package com.audiobookshelf.app.data
|
package com.audiobookshelf.app.data
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
data class LocalMediaProgress(
|
data class LocalMediaProgress(
|
||||||
var id:String,
|
var id:String,
|
||||||
var localLibraryItemId:String,
|
var localLibraryItemId:String,
|
||||||
var episodeId:String?,
|
var localEpisodeId:String?,
|
||||||
var duration:Double,
|
var duration:Double,
|
||||||
var progress:Double, // 0 to 1
|
var progress:Double, // 0 to 1
|
||||||
var currentTime:Double,
|
var currentTime:Double,
|
||||||
|
@ -18,5 +19,27 @@ data class LocalMediaProgress(
|
||||||
var serverConnectionConfigId:String?,
|
var serverConnectionConfigId:String?,
|
||||||
var serverAddress:String?,
|
var serverAddress:String?,
|
||||||
var serverUserId:String?,
|
var serverUserId:String?,
|
||||||
var libraryItemId:String?
|
var libraryItemId:String?,
|
||||||
)
|
var episodeId:String?
|
||||||
|
) {
|
||||||
|
@JsonIgnore
|
||||||
|
fun updateIsFinished(finished:Boolean) {
|
||||||
|
if (isFinished != finished) { // If finished changed then set progress
|
||||||
|
progress = if (finished) 1.0 else 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
isFinished = finished
|
||||||
|
lastUpdate = System.currentTimeMillis()
|
||||||
|
finishedAt = if (isFinished) lastUpdate else null
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun updateFromPlaybackSession(playbackSession:PlaybackSession) {
|
||||||
|
currentTime = playbackSession.currentTime
|
||||||
|
progress = playbackSession.progress
|
||||||
|
lastUpdate = System.currentTimeMillis()
|
||||||
|
|
||||||
|
isFinished = playbackSession.progress >= 0.99
|
||||||
|
finishedAt = if (isFinished) lastUpdate else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -40,6 +40,7 @@ class PlaybackSession(
|
||||||
var currentTime:Double,
|
var currentTime:Double,
|
||||||
var libraryItem:LibraryItem?,
|
var libraryItem:LibraryItem?,
|
||||||
var localLibraryItem:LocalLibraryItem?,
|
var localLibraryItem:LocalLibraryItem?,
|
||||||
|
var localEpisodeId:String?,
|
||||||
var serverConnectionConfigId:String?,
|
var serverConnectionConfigId:String?,
|
||||||
var serverAddress:String?
|
var serverAddress:String?
|
||||||
) {
|
) {
|
||||||
|
@ -53,7 +54,7 @@ class PlaybackSession(
|
||||||
@get:JsonIgnore
|
@get:JsonIgnore
|
||||||
val localLibraryItemId get() = localLibraryItem?.id ?: ""
|
val localLibraryItemId get() = localLibraryItem?.id ?: ""
|
||||||
@get:JsonIgnore
|
@get:JsonIgnore
|
||||||
val localMediaProgressId get() = if (episodeId.isNullOrEmpty()) localLibraryItemId else "$localLibraryItemId-$episodeId"
|
val localMediaProgressId get() = if (episodeId.isNullOrEmpty()) localLibraryItemId else "$localLibraryItemId-$localEpisodeId"
|
||||||
@get:JsonIgnore
|
@get:JsonIgnore
|
||||||
val progress get() = currentTime / getTotalDuration()
|
val progress get() = currentTime / getTotalDuration()
|
||||||
|
|
||||||
|
@ -175,7 +176,7 @@ class PlaybackSession(
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun clone():PlaybackSession {
|
fun clone():PlaybackSession {
|
||||||
return PlaybackSession(id,userId,libraryItemId,episodeId,mediaType,mediaMetadata,chapters,displayTitle,displayAuthor,coverPath,duration,playMethod,startedAt,updatedAt,timeListening,audioTracks,currentTime,libraryItem,localLibraryItem,serverConnectionConfigId,serverAddress)
|
return PlaybackSession(id,userId,libraryItemId,episodeId,mediaType,mediaMetadata,chapters,displayTitle,displayAuthor,coverPath,duration,playMethod,startedAt,updatedAt,timeListening,audioTracks,currentTime,libraryItem,localLibraryItem,localEpisodeId,serverConnectionConfigId,serverAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
|
@ -187,6 +188,6 @@ class PlaybackSession(
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getNewLocalMediaProgress():LocalMediaProgress {
|
fun getNewLocalMediaProgress():LocalMediaProgress {
|
||||||
return LocalMediaProgress(localMediaProgressId,localLibraryItemId,episodeId,getTotalDuration(),progress,currentTime,false,updatedAt,startedAt,null,serverConnectionConfigId,serverAddress,userId,libraryItemId)
|
return LocalMediaProgress(localMediaProgressId,localLibraryItemId,localEpisodeId,getTotalDuration(),progress,currentTime,false,updatedAt,startedAt,null,serverConnectionConfigId,serverAddress,userId,libraryItemId,episodeId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@ object DeviceManager {
|
||||||
var deviceData:DeviceData = dbManager.getDeviceData()
|
var deviceData:DeviceData = dbManager.getDeviceData()
|
||||||
var serverConnectionConfig: ServerConnectionConfig? = null
|
var serverConnectionConfig: ServerConnectionConfig? = null
|
||||||
|
|
||||||
|
val serverConnectionConfigId get() = serverConnectionConfig?.id ?: ""
|
||||||
val serverAddress get() = serverConnectionConfig?.address ?: ""
|
val serverAddress get() = serverConnectionConfig?.address ?: ""
|
||||||
val serverUserId get() = serverConnectionConfig?.userId ?: ""
|
val serverUserId get() = serverConnectionConfig?.userId ?: ""
|
||||||
val token get() = serverConnectionConfig?.token ?: ""
|
val token get() = serverConnectionConfig?.token ?: ""
|
||||||
|
@ -20,6 +21,6 @@ object DeviceManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getBase64Id(id:String):String {
|
fun getBase64Id(id:String):String {
|
||||||
return android.util.Base64.encodeToString(id.toByteArray(), android.util.Base64.DEFAULT)
|
return android.util.Base64.encodeToString(id.toByteArray(), android.util.Base64.NO_WRAP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,15 @@ import com.arthenica.ffmpegkit.FFprobeKit
|
||||||
import com.arthenica.ffmpegkit.Level
|
import com.arthenica.ffmpegkit.Level
|
||||||
import com.audiobookshelf.app.data.*
|
import com.audiobookshelf.app.data.*
|
||||||
import com.audiobookshelf.app.plugins.AbsDownloader
|
import com.audiobookshelf.app.plugins.AbsDownloader
|
||||||
|
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
|
import com.getcapacitor.JSObject
|
||||||
|
|
||||||
class FolderScanner(var ctx: Context) {
|
class FolderScanner(var ctx: Context) {
|
||||||
private val tag = "FolderScanner"
|
private val tag = "FolderScanner"
|
||||||
|
var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||||
|
|
||||||
|
|
||||||
private fun getLocalLibraryItemId(mediaItemId:String):String {
|
private fun getLocalLibraryItemId(mediaItemId:String):String {
|
||||||
return "local_" + DeviceManager.getBase64Id(mediaItemId)
|
return "local_" + DeviceManager.getBase64Id(mediaItemId)
|
||||||
|
@ -52,6 +56,7 @@ class FolderScanner(var ctx: Context) {
|
||||||
|
|
||||||
// Remove existing items no longer there
|
// Remove existing items no longer there
|
||||||
existingLocalLibraryItems = existingLocalLibraryItems.filter { lli ->
|
existingLocalLibraryItems = existingLocalLibraryItems.filter { lli ->
|
||||||
|
Log.d(tag, "scanForMediaItems Checking Existing LLI ${lli.id}")
|
||||||
var fileFound = foldersFound.find { f -> lli.id == getLocalLibraryItemId(f.id) }
|
var fileFound = foldersFound.find { f -> lli.id == getLocalLibraryItemId(f.id) }
|
||||||
if (fileFound == null) {
|
if (fileFound == null) {
|
||||||
Log.d(tag, "Existing local library item is no longer in file system ${lli.media.metadata.title}")
|
Log.d(tag, "Existing local library item is no longer in file system ${lli.media.metadata.title}")
|
||||||
|
@ -129,25 +134,22 @@ class FolderScanner(var ctx: Context) {
|
||||||
var existingAudioTrack = existingAudioTracks.find { eat -> eat.localFileId == localFileId }
|
var existingAudioTrack = existingAudioTracks.find { eat -> eat.localFileId == localFileId }
|
||||||
if (existingAudioTrack != null) { // Update existing audio track
|
if (existingAudioTrack != null) { // Update existing audio track
|
||||||
if (existingAudioTrack.index != index) {
|
if (existingAudioTrack.index != index) {
|
||||||
Log.d(tag, "Updating Audio track index from ${existingAudioTrack.index} to $index")
|
Log.d(tag, "scanLibraryItemFolder Updating Audio track index from ${existingAudioTrack.index} to $index")
|
||||||
existingAudioTrack.index = index
|
existingAudioTrack.index = index
|
||||||
isNewOrUpdated = true
|
isNewOrUpdated = true
|
||||||
}
|
}
|
||||||
if (existingAudioTrack.startOffset != startOffset) {
|
if (existingAudioTrack.startOffset != startOffset) {
|
||||||
Log.d(tag, "Updating Audio track startOffset ${existingAudioTrack.startOffset} to $startOffset")
|
Log.d(tag, "scanLibraryItemFolder Updating Audio track startOffset ${existingAudioTrack.startOffset} to $startOffset")
|
||||||
existingAudioTrack.startOffset = startOffset
|
existingAudioTrack.startOffset = startOffset
|
||||||
isNewOrUpdated = true
|
isNewOrUpdated = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existingAudioTrack == null || forceAudioProbe) {
|
if (existingAudioTrack == null || forceAudioProbe) {
|
||||||
Log.d(tag, "Scanning Audio File Path ${localFile.absolutePath}")
|
Log.d(tag, "scanLibraryItemFolder Scanning Audio File Path ${localFile.absolutePath} | ForceAudioProbe=${forceAudioProbe}")
|
||||||
|
|
||||||
// TODO: Make asynchronous
|
// TODO: Make asynchronous
|
||||||
var session = FFprobeKit.execute("-i \"${localFile.absolutePath}\" -print_format json -show_format -show_streams -select_streams a -show_chapters -loglevel quiet")
|
var audioProbeResult = probeAudioFile(localFile.absolutePath)
|
||||||
|
|
||||||
val audioProbeResult = jacksonObjectMapper().readValue<AudioProbeResult>(session.output)
|
|
||||||
Log.d(tag, "Probe Result DATA ${audioProbeResult.duration} | ${audioProbeResult.size} | ${audioProbeResult.title} | ${audioProbeResult.artist}")
|
|
||||||
|
|
||||||
if (existingAudioTrack != null) {
|
if (existingAudioTrack != null) {
|
||||||
// Update audio probe data on existing audio track
|
// Update audio probe data on existing audio track
|
||||||
|
@ -172,10 +174,12 @@ class FolderScanner(var ctx: Context) {
|
||||||
var existingLocalFile = existingLocalFiles.find { elf -> elf.id == localFileId }
|
var existingLocalFile = existingLocalFiles.find { elf -> elf.id == localFileId }
|
||||||
|
|
||||||
if (existingLocalFile == null) {
|
if (existingLocalFile == null) {
|
||||||
|
Log.d(tag, "scanLibraryItemFolder new local file found ${localFile.absolutePath}")
|
||||||
isNewOrUpdated = true
|
isNewOrUpdated = true
|
||||||
}
|
}
|
||||||
if (existingItem != null && existingItem.coverContentUrl == null) {
|
if (existingItem != null && existingItem.coverContentUrl == null) {
|
||||||
// Existing media item did not have a cover - cover found on scan
|
// Existing media item did not have a cover - cover found on scan
|
||||||
|
Log.d(tag, "scanLibraryItemFolder setting cover ${localFile.absolutePath}")
|
||||||
isNewOrUpdated = true
|
isNewOrUpdated = true
|
||||||
existingItem.coverAbsolutePath = localFile.absolutePath
|
existingItem.coverAbsolutePath = localFile.absolutePath
|
||||||
existingItem.coverContentUrl = localFile.contentUrl
|
existingItem.coverContentUrl = localFile.contentUrl
|
||||||
|
@ -217,11 +221,14 @@ class FolderScanner(var ctx: Context) {
|
||||||
fun scanDownloadItem(downloadItem: AbsDownloader.DownloadItem):LocalLibraryItem? {
|
fun scanDownloadItem(downloadItem: AbsDownloader.DownloadItem):LocalLibraryItem? {
|
||||||
var folderDf = DocumentFileCompat.fromUri(ctx, Uri.parse(downloadItem.localFolder.contentUrl))
|
var folderDf = DocumentFileCompat.fromUri(ctx, Uri.parse(downloadItem.localFolder.contentUrl))
|
||||||
var foldersFound = folderDf?.search(false, DocumentFileType.FOLDER) ?: mutableListOf()
|
var foldersFound = folderDf?.search(false, DocumentFileType.FOLDER) ?: mutableListOf()
|
||||||
|
|
||||||
|
var itemFolderId = ""
|
||||||
var itemFolderUrl = ""
|
var itemFolderUrl = ""
|
||||||
var itemFolderBasePath = ""
|
var itemFolderBasePath = ""
|
||||||
var itemFolderAbsolutePath = ""
|
var itemFolderAbsolutePath = ""
|
||||||
foldersFound.forEach {
|
foldersFound.forEach {
|
||||||
if (it.name == downloadItem.itemTitle) {
|
if (it.name == downloadItem.itemTitle) {
|
||||||
|
itemFolderId = it.id
|
||||||
itemFolderUrl = it.uri.toString()
|
itemFolderUrl = it.uri.toString()
|
||||||
itemFolderBasePath = it.getBasePath(ctx)
|
itemFolderBasePath = it.getBasePath(ctx)
|
||||||
itemFolderAbsolutePath = it.getAbsolutePath(ctx)
|
itemFolderAbsolutePath = it.getAbsolutePath(ctx)
|
||||||
|
@ -238,7 +245,9 @@ class FolderScanner(var ctx: Context) {
|
||||||
Log.e(tag, "Folder Doc File Invalid ${downloadItem.itemFolderPath}")
|
Log.e(tag, "Folder Doc File Invalid ${downloadItem.itemFolderPath}")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
Log.d(tag, "scanDownloadItem starting for ${downloadItem.itemFolderPath} | ${df.uri}")
|
|
||||||
|
var 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
|
// Search for files in media item folder
|
||||||
var filesFound = df.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
|
var filesFound = df.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
|
||||||
|
@ -246,13 +255,13 @@ class FolderScanner(var ctx: Context) {
|
||||||
|
|
||||||
var localLibraryItem:LocalLibraryItem? = null
|
var localLibraryItem:LocalLibraryItem? = null
|
||||||
if (downloadItem.mediaType == "book") {
|
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)
|
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)
|
||||||
} else {
|
} else {
|
||||||
// Lookup or create podcast local library item
|
// Lookup or create podcast local library item
|
||||||
localLibraryItem = DeviceManager.dbManager.getLocalLibraryItem("local_${downloadItem.libraryItemId}")
|
localLibraryItem = DeviceManager.dbManager.getLocalLibraryItem(localLibraryItemId)
|
||||||
if (localLibraryItem == null) {
|
if (localLibraryItem == null) {
|
||||||
Log.d(tag, "Podcast local library item not created yet for ${downloadItem.media.metadata.title}")
|
Log.d(tag, "[FolderScanner] 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)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,25 +277,26 @@ class FolderScanner(var ctx: Context) {
|
||||||
}
|
}
|
||||||
} else if (itemPart.audioTrack != null) { // Is audio track
|
} else if (itemPart.audioTrack != null) { // Is audio track
|
||||||
var audioTrackFromServer = itemPart.audioTrack
|
var audioTrackFromServer = itemPart.audioTrack
|
||||||
|
Log.d(tag, "scanDownloadItem: Audio Track from Server index = ${audioTrackFromServer?.index}")
|
||||||
|
|
||||||
var localFileId = DeviceManager.getBase64Id(docFile.id)
|
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())
|
var 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)
|
localLibraryItem.localFiles.add(localFile)
|
||||||
|
|
||||||
// TODO: Make asynchronous
|
// TODO: Make asynchronous
|
||||||
var session = FFprobeKit.execute("-i \"${localFile.absolutePath}\" -print_format json -show_format -show_streams -select_streams a -show_chapters -loglevel quiet")
|
var audioProbeResult = probeAudioFile(localFile.absolutePath)
|
||||||
|
|
||||||
val audioProbeResult = jacksonObjectMapper().readValue<AudioProbeResult>(session.output)
|
|
||||||
Log.d(tag, "Probe Result DATA ${audioProbeResult.duration} | ${audioProbeResult.size} | ${audioProbeResult.title} | ${audioProbeResult.artist}")
|
|
||||||
|
|
||||||
// Create new audio track
|
// 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)
|
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)
|
audioTracks.add(track)
|
||||||
|
|
||||||
|
Log.d(tag, "scanDownloadItem: Created Audio Track with index ${track.index} from local file ${localFile.absolutePath}")
|
||||||
|
|
||||||
// Add podcast episodes to library
|
// Add podcast episodes to library
|
||||||
itemPart.episode?.let { podcastEpisode ->
|
itemPart.episode?.let { podcastEpisode ->
|
||||||
var podcast = localLibraryItem.media as Podcast
|
var podcast = localLibraryItem.media as Podcast
|
||||||
podcast.addEpisode(track, podcastEpisode)
|
podcast.addEpisode(track, podcastEpisode)
|
||||||
|
Log.d(tag, "scanDownloadItem: Added episode to podcast ${podcastEpisode.title} ${track.title} | Track index: ${podcastEpisode.audioTrack?.index}")
|
||||||
}
|
}
|
||||||
} else { // Cover image
|
} else { // Cover image
|
||||||
var localFileId = DeviceManager.getBase64Id(docFile.id)
|
var localFileId = DeviceManager.getBase64Id(docFile.id)
|
||||||
|
@ -381,10 +391,7 @@ class FolderScanner(var ctx: Context) {
|
||||||
|
|
||||||
if (localFile.isAudioFile()) {
|
if (localFile.isAudioFile()) {
|
||||||
// TODO: Make asynchronous
|
// TODO: Make asynchronous
|
||||||
var session = FFprobeKit.execute("-i \"${localFile.absolutePath}\" -print_format json -show_format -show_streams -select_streams a -show_chapters -loglevel quiet")
|
var audioProbeResult = probeAudioFile(localFile.absolutePath)
|
||||||
|
|
||||||
val audioProbeResult = jacksonObjectMapper().readValue<AudioProbeResult>(session.output)
|
|
||||||
Log.d(tag, "Probe Result DATA ${audioProbeResult.duration} | ${audioProbeResult.size} | ${audioProbeResult.title} | ${audioProbeResult.artist}")
|
|
||||||
|
|
||||||
var existingTrack = existingAudioTracks.find { audioTrack ->
|
var existingTrack = existingAudioTracks.find { audioTrack ->
|
||||||
audioTrack.localFileId == localFile.id
|
audioTrack.localFileId == localFile.id
|
||||||
|
@ -421,4 +428,13 @@ class FolderScanner(var ctx: Context) {
|
||||||
}
|
}
|
||||||
return LocalLibraryItemScanResult(wasUpdated, localLibraryItem)
|
return LocalLibraryItemScanResult(wasUpdated, localLibraryItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun probeAudioFile(absolutePath:String):AudioProbeResult {
|
||||||
|
var session = FFprobeKit.execute("-i \"${absolutePath}\" -print_format json -show_format -show_streams -select_streams a -show_chapters -loglevel quiet")
|
||||||
|
Log.d(tag, "FFprobe output ${JSObject(session.output)}")
|
||||||
|
|
||||||
|
val audioProbeResult = jacksonMapper.readValue<AudioProbeResult>(session.output)
|
||||||
|
Log.d(tag, "Probe Result DATA ${audioProbeResult.duration} | ${audioProbeResult.size} | ${audioProbeResult.title} | ${audioProbeResult.artist}")
|
||||||
|
return audioProbeResult
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.audiobookshelf.app.player
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.appcompat.R
|
import androidx.appcompat.R
|
||||||
|
@ -13,10 +14,7 @@ import com.google.android.exoplayer2.MediaItem
|
||||||
import com.google.android.exoplayer2.ext.cast.CastPlayer
|
import com.google.android.exoplayer2.ext.cast.CastPlayer
|
||||||
import com.google.android.exoplayer2.ext.cast.MediaItemConverter
|
import com.google.android.exoplayer2.ext.cast.MediaItemConverter
|
||||||
import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener
|
import com.google.android.exoplayer2.ext.cast.SessionAvailabilityListener
|
||||||
import com.google.android.gms.cast.Cast
|
import com.google.android.gms.cast.*
|
||||||
import com.google.android.gms.cast.CastDevice
|
|
||||||
import com.google.android.gms.cast.CastMediaControlIntent
|
|
||||||
import com.google.android.gms.cast.MediaQueueItem
|
|
||||||
import com.google.android.gms.cast.framework.*
|
import com.google.android.gms.cast.framework.*
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
@ -321,12 +319,36 @@ class CastManager constructor(playerNotificationService:PlayerNotificationServic
|
||||||
try {
|
try {
|
||||||
val castContext = CastContext.getSharedInstance(mainActivity)
|
val castContext = CastContext.getSharedInstance(mainActivity)
|
||||||
|
|
||||||
playerNotificationService.castPlayer = CastPlayer(castContext, CustomConverter()).apply {
|
// Work in progress using the cast api
|
||||||
setSessionAvailabilityListener(CastSessionAvailabilityListener())
|
var currentSession = playerNotificationService.getCurrentPlaybackSessionCopy()
|
||||||
addListener(PlayerListener(playerNotificationService))
|
var firstTrack = currentSession?.audioTracks?.get(0)
|
||||||
|
var uri = firstTrack?.let { currentSession?.getContentUri(it) } ?: Uri.EMPTY
|
||||||
|
var url = uri.toString()
|
||||||
|
var mimeType = firstTrack?.mimeType ?: ""
|
||||||
|
var castMediaMetadata = firstTrack?.let { currentSession?.getCastMediaMetadata(it) }
|
||||||
|
Log.d(tag, "CastManager set url $url")
|
||||||
|
var duration = (currentSession?.getTotalDuration() ?: 0L * 1000L).toLong()
|
||||||
|
|
||||||
|
if (castMediaMetadata != null) {
|
||||||
|
Log.d(tag, "CastManager duration $duration got cast media metadata $castMediaMetadata")
|
||||||
|
|
||||||
|
val mediaInfo = MediaInfo.Builder(url)
|
||||||
|
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
|
||||||
|
.setContentType(mimeType)
|
||||||
|
.setMetadata(castMediaMetadata)
|
||||||
|
.setStreamDuration(duration)
|
||||||
|
.build()
|
||||||
|
val remoteMediaClient = castSession?.remoteMediaClient
|
||||||
|
remoteMediaClient?.load(MediaLoadRequestData.Builder().setMediaInfo(mediaInfo).build())
|
||||||
}
|
}
|
||||||
Log.d(tag, "CAST Cast Player Applied")
|
|
||||||
switchToPlayer(true)
|
// Not working using the exo player CastPlayer
|
||||||
|
// playerNotificationService.castPlayer = CastPlayer(castContext, CustomConverter()).apply {
|
||||||
|
// setSessionAvailabilityListener(CastSessionAvailabilityListener())
|
||||||
|
// addListener(PlayerListener(playerNotificationService))
|
||||||
|
// }
|
||||||
|
// Log.d(tag, "CAST Cast Player Applied")
|
||||||
|
// switchToPlayer(true)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.i(tag, "Cast is not available on this device. " +
|
Log.i(tag, "Cast is not available on this device. " +
|
||||||
"Exception thrown when attempting to obtain CastContext. " + e.message)
|
"Exception thrown when attempting to obtain CastContext. " + e.message)
|
||||||
|
|
|
@ -110,14 +110,12 @@ class MediaProgressSyncer(playerNotificationService:PlayerNotificationService, a
|
||||||
currentLocalMediaProgress = mediaProgress
|
currentLocalMediaProgress = mediaProgress
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
currentLocalMediaProgress?.currentTime = playbackSession.currentTime
|
currentLocalMediaProgress?.updateFromPlaybackSession(playbackSession)
|
||||||
currentLocalMediaProgress?.lastUpdate = playbackSession.updatedAt
|
|
||||||
currentLocalMediaProgress?.progress = playbackSession.progress
|
|
||||||
}
|
}
|
||||||
currentLocalMediaProgress?.let {
|
currentLocalMediaProgress?.let {
|
||||||
DeviceManager.dbManager.saveLocalMediaProgress(it)
|
DeviceManager.dbManager.saveLocalMediaProgress(it)
|
||||||
playerNotificationService.clientEventEmitter?.onLocalMediaProgressUpdate(it)
|
playerNotificationService.clientEventEmitter?.onLocalMediaProgressUpdate(it)
|
||||||
Log.d(tag, "Saved Local Progress Current Time: ${it.currentTime} | Duration ${it.duration} | Progress ${(it.progress * 100).roundToInt()}%")
|
Log.d(tag, "Saved Local Progress Current Time: ID ${it.id} | ${it.currentTime} | Duration ${it.duration} | Progress ${(it.progress * 100).roundToInt()}%")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,9 +6,7 @@ import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.audiobookshelf.app.MainActivity
|
import com.audiobookshelf.app.MainActivity
|
||||||
import com.audiobookshelf.app.data.LocalMediaProgress
|
import com.audiobookshelf.app.data.*
|
||||||
import com.audiobookshelf.app.data.PlaybackMetadata
|
|
||||||
import com.audiobookshelf.app.data.PlaybackSession
|
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.player.CastManager
|
import com.audiobookshelf.app.player.CastManager
|
||||||
import com.audiobookshelf.app.player.PlayerNotificationService
|
import com.audiobookshelf.app.player.PlayerNotificationService
|
||||||
|
@ -22,6 +20,7 @@ import org.json.JSONObject
|
||||||
@CapacitorPlugin(name = "AbsAudioPlayer")
|
@CapacitorPlugin(name = "AbsAudioPlayer")
|
||||||
class AbsAudioPlayer : Plugin() {
|
class AbsAudioPlayer : Plugin() {
|
||||||
private val tag = "AbsAudioPlayer"
|
private val tag = "AbsAudioPlayer"
|
||||||
|
var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||||
|
|
||||||
lateinit var mainActivity: MainActivity
|
lateinit var mainActivity: MainActivity
|
||||||
lateinit var apiHandler:ApiHandler
|
lateinit var apiHandler:ApiHandler
|
||||||
|
@ -36,7 +35,7 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
playerNotificationService.clientEventEmitter = (object : PlayerNotificationService.ClientEventEmitter {
|
playerNotificationService.clientEventEmitter = (object : PlayerNotificationService.ClientEventEmitter {
|
||||||
override fun onPlaybackSession(playbackSession: PlaybackSession) {
|
override fun onPlaybackSession(playbackSession: PlaybackSession) {
|
||||||
notifyListeners("onPlaybackSession", JSObject(jacksonObjectMapper().writeValueAsString(playbackSession)))
|
notifyListeners("onPlaybackSession", JSObject(jacksonMapper.writeValueAsString(playbackSession)))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlaybackClosed() {
|
override fun onPlaybackClosed() {
|
||||||
|
@ -48,7 +47,7 @@ class AbsAudioPlayer : Plugin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onMetadata(metadata: PlaybackMetadata) {
|
override fun onMetadata(metadata: PlaybackMetadata) {
|
||||||
notifyListeners("onMetadata", JSObject(jacksonObjectMapper().writeValueAsString(metadata)))
|
notifyListeners("onMetadata", JSObject(jacksonMapper.writeValueAsString(metadata)))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPrepare(audiobookId: String, playWhenReady: Boolean) {
|
override fun onPrepare(audiobookId: String, playWhenReady: Boolean) {
|
||||||
|
@ -67,7 +66,7 @@ class AbsAudioPlayer : Plugin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLocalMediaProgressUpdate(localMediaProgress: LocalMediaProgress) {
|
override fun onLocalMediaProgressUpdate(localMediaProgress: LocalMediaProgress) {
|
||||||
notifyListeners("onLocalMediaProgressUpdate", JSObject(jacksonObjectMapper().writeValueAsString(localMediaProgress)))
|
notifyListeners("onLocalMediaProgressUpdate", JSObject(jacksonMapper.writeValueAsString(localMediaProgress)))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -101,9 +100,19 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
if (libraryItemId.startsWith("local")) { // Play local media item
|
if (libraryItemId.startsWith("local")) { // Play local media item
|
||||||
DeviceManager.dbManager.getLocalLibraryItem(libraryItemId)?.let {
|
DeviceManager.dbManager.getLocalLibraryItem(libraryItemId)?.let {
|
||||||
|
var episode: PodcastEpisode? = null
|
||||||
|
if (!episodeId.isNullOrEmpty()) {
|
||||||
|
var podcastMedia = it.media as Podcast
|
||||||
|
episode = podcastMedia.episodes?.find { ep -> ep.id == episodeId }
|
||||||
|
if (episode == null) {
|
||||||
|
Log.e(tag, "prepareLibraryItem: Podcast episode not found $episodeId")
|
||||||
|
return call.resolve(JSObject())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post() {
|
||||||
Log.d(tag, "Preparing Local Media item ${jacksonObjectMapper().writeValueAsString(it)}")
|
Log.d(tag, "prepareLibraryItem: Preparing Local Media item ${jacksonMapper.writeValueAsString(it)}")
|
||||||
var playbackSession = it.getPlaybackSession(episodeId)
|
var playbackSession = it.getPlaybackSession(episode)
|
||||||
playerNotificationService.preparePlayer(playbackSession, playWhenReady)
|
playerNotificationService.preparePlayer(playbackSession, playWhenReady)
|
||||||
}
|
}
|
||||||
return call.resolve(JSObject())
|
return call.resolve(JSObject())
|
||||||
|
@ -112,11 +121,11 @@ class AbsAudioPlayer : Plugin() {
|
||||||
apiHandler.playLibraryItem(libraryItemId, episodeId, false) {
|
apiHandler.playLibraryItem(libraryItemId, episodeId, false) {
|
||||||
|
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post() {
|
||||||
Log.d(tag, "Preparing Player TEST ${jacksonObjectMapper().writeValueAsString(it)}")
|
Log.d(tag, "Preparing Player TEST ${jacksonMapper.writeValueAsString(it)}")
|
||||||
playerNotificationService.preparePlayer(it, playWhenReady)
|
playerNotificationService.preparePlayer(it, playWhenReady)
|
||||||
}
|
}
|
||||||
|
|
||||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(it)))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(it)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.util.Log
|
||||||
import com.audiobookshelf.app.MainActivity
|
import com.audiobookshelf.app.MainActivity
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.server.ApiHandler
|
import com.audiobookshelf.app.server.ApiHandler
|
||||||
|
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.getcapacitor.*
|
import com.getcapacitor.*
|
||||||
import com.getcapacitor.annotation.CapacitorPlugin
|
import com.getcapacitor.annotation.CapacitorPlugin
|
||||||
|
@ -15,6 +16,7 @@ import org.json.JSONObject
|
||||||
@CapacitorPlugin(name = "AbsDatabase")
|
@CapacitorPlugin(name = "AbsDatabase")
|
||||||
class AbsDatabase : Plugin() {
|
class AbsDatabase : Plugin() {
|
||||||
val tag = "AbsDatabase"
|
val tag = "AbsDatabase"
|
||||||
|
var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||||
|
|
||||||
lateinit var mainActivity: MainActivity
|
lateinit var mainActivity: MainActivity
|
||||||
lateinit var apiHandler: ApiHandler
|
lateinit var apiHandler: ApiHandler
|
||||||
|
@ -26,13 +28,16 @@ class AbsDatabase : Plugin() {
|
||||||
override fun load() {
|
override fun load() {
|
||||||
mainActivity = (activity as MainActivity)
|
mainActivity = (activity as MainActivity)
|
||||||
apiHandler = ApiHandler(mainActivity)
|
apiHandler = ApiHandler(mainActivity)
|
||||||
|
|
||||||
|
DeviceManager.dbManager.cleanLocalMediaProgress()
|
||||||
|
DeviceManager.dbManager.cleanLocalLibraryItems()
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun getDeviceData(call:PluginCall) {
|
fun getDeviceData(call:PluginCall) {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
var deviceData = DeviceManager.dbManager.getDeviceData()
|
var deviceData = DeviceManager.dbManager.getDeviceData()
|
||||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(deviceData)))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(deviceData)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +45,7 @@ class AbsDatabase : Plugin() {
|
||||||
fun getLocalFolders(call:PluginCall) {
|
fun getLocalFolders(call:PluginCall) {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
var folders = DeviceManager.dbManager.getAllLocalFolders()
|
var folders = DeviceManager.dbManager.getAllLocalFolders()
|
||||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(LocalFoldersPayload(folders))))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(LocalFoldersPayload(folders))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,7 +54,7 @@ class AbsDatabase : Plugin() {
|
||||||
var folderId = call.getString("folderId", "").toString()
|
var folderId = call.getString("folderId", "").toString()
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
DeviceManager.dbManager.getLocalFolder(folderId)?.let {
|
DeviceManager.dbManager.getLocalFolder(folderId)?.let {
|
||||||
var folderObj = jacksonObjectMapper().writeValueAsString(it)
|
var folderObj = jacksonMapper.writeValueAsString(it)
|
||||||
call.resolve(JSObject(folderObj))
|
call.resolve(JSObject(folderObj))
|
||||||
} ?: call.resolve()
|
} ?: call.resolve()
|
||||||
}
|
}
|
||||||
|
@ -64,7 +69,7 @@ class AbsDatabase : Plugin() {
|
||||||
if (localLibraryItem == null) {
|
if (localLibraryItem == null) {
|
||||||
call.resolve()
|
call.resolve()
|
||||||
} else {
|
} else {
|
||||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(localLibraryItem)))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(localLibraryItem)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -77,7 +82,7 @@ class AbsDatabase : Plugin() {
|
||||||
if (localLibraryItem == null) {
|
if (localLibraryItem == null) {
|
||||||
call.resolve()
|
call.resolve()
|
||||||
} else {
|
} else {
|
||||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(localLibraryItem)))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(localLibraryItem)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -88,7 +93,7 @@ class AbsDatabase : Plugin() {
|
||||||
|
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
var localLibraryItems = DeviceManager.dbManager.getLocalLibraryItems(mediaType)
|
var localLibraryItems = DeviceManager.dbManager.getLocalLibraryItems(mediaType)
|
||||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(LocalLibraryItemsPayload(localLibraryItems))))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(LocalLibraryItemsPayload(localLibraryItems))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,7 +102,7 @@ class AbsDatabase : Plugin() {
|
||||||
var folderId = call.getString("folderId", "").toString()
|
var folderId = call.getString("folderId", "").toString()
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
var localLibraryItems = DeviceManager.dbManager.getLocalLibraryItemsInFolder(folderId)
|
var localLibraryItems = DeviceManager.dbManager.getLocalLibraryItemsInFolder(folderId)
|
||||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(LocalLibraryItemsPayload(localLibraryItems))))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(LocalLibraryItemsPayload(localLibraryItems))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +148,7 @@ class AbsDatabase : Plugin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
DeviceManager.serverConnectionConfig = serverConnectionConfig
|
DeviceManager.serverConnectionConfig = serverConnectionConfig
|
||||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(DeviceManager.serverConnectionConfig)))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(DeviceManager.serverConnectionConfig)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -177,7 +182,7 @@ class AbsDatabase : Plugin() {
|
||||||
fun getAllLocalMediaProgress(call:PluginCall) {
|
fun getAllLocalMediaProgress(call:PluginCall) {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
var localMediaProgress = DeviceManager.dbManager.getAllLocalMediaProgress()
|
var localMediaProgress = DeviceManager.dbManager.getAllLocalMediaProgress()
|
||||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(LocalMediaProgressPayload(localMediaProgress))))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(LocalMediaProgressPayload(localMediaProgress))))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,7 +200,51 @@ class AbsDatabase : Plugin() {
|
||||||
return call.resolve()
|
return call.resolve()
|
||||||
}
|
}
|
||||||
apiHandler.syncMediaProgress {
|
apiHandler.syncMediaProgress {
|
||||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(it)))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(it)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@PluginMethod
|
||||||
|
fun updateLocalMediaProgressFinished(call:PluginCall) {
|
||||||
|
var localMediaProgressId = call.getString("localMediaProgressId", "").toString()
|
||||||
|
var isFinished = call.getBoolean("isFinished", false) == true
|
||||||
|
Log.d(tag, "updateLocalMediaProgressFinished $localMediaProgressId | Is Finished:$isFinished")
|
||||||
|
var localMediaProgress = DeviceManager.dbManager.getLocalMediaProgress(localMediaProgressId)
|
||||||
|
if (localMediaProgress == null) {
|
||||||
|
Log.e(tag, "updateLocalMediaProgressFinished Local Media Progress not found $localMediaProgressId")
|
||||||
|
call.resolve(JSObject("{\"error\":\"Progress not found\"}"))
|
||||||
|
} else {
|
||||||
|
localMediaProgress.updateIsFinished(isFinished)
|
||||||
|
|
||||||
|
var lmpstring = jacksonMapper.writeValueAsString(localMediaProgress)
|
||||||
|
Log.d(tag, "updateLocalMediaProgressFinished: Local Media Progress String $lmpstring")
|
||||||
|
|
||||||
|
// Send update to server media progress is linked to a server and user is logged into that server
|
||||||
|
localMediaProgress.serverConnectionConfigId?.let { configId ->
|
||||||
|
if (DeviceManager.serverConnectionConfigId == configId) {
|
||||||
|
var libraryItemId = localMediaProgress.libraryItemId ?: ""
|
||||||
|
var episodeId = localMediaProgress.episodeId ?: ""
|
||||||
|
var updatePayload = JSObject()
|
||||||
|
updatePayload.put("isFinished", isFinished)
|
||||||
|
apiHandler.updateMediaProgress(libraryItemId,episodeId,updatePayload) {
|
||||||
|
Log.d(tag, "updateLocalMediaProgressFinished: Updated media progress isFinished on server")
|
||||||
|
var jsobj = JSObject()
|
||||||
|
jsobj.put("local", true)
|
||||||
|
jsobj.put("server", true)
|
||||||
|
jsobj.put("localMediaProgress", JSObject(lmpstring))
|
||||||
|
call.resolve(jsobj)
|
||||||
|
// call.resolve(JSObject("{\"local\":true,\"server\":true,\"localMediaProgress\":$lmpstring}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (localMediaProgress.serverConnectionConfigId == null || DeviceManager.serverConnectionConfigId != localMediaProgress.serverConnectionConfigId) {
|
||||||
|
// call.resolve(JSObject("{\"local\":true,\"localMediaProgress\":$lmpstring}}"))
|
||||||
|
var jsobj = JSObject()
|
||||||
|
jsobj.put("local", true)
|
||||||
|
jsobj.put("server", false)
|
||||||
|
jsobj.put("localMediaProgress", JSObject(lmpstring))
|
||||||
|
call.resolve(jsobj)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,17 +257,37 @@ class AbsDatabase : Plugin() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var audioTracks = localLibraryItem.media.getAudioTracks() as MutableList
|
||||||
|
|
||||||
var tracks:JSArray = call.getArray("tracks") ?: JSArray()
|
var tracks:JSArray = call.getArray("tracks") ?: JSArray()
|
||||||
Log.d(tag, "updateLocalTrackOrder $tracks")
|
Log.d(tag, "updateLocalTrackOrder $tracks")
|
||||||
|
|
||||||
for (i in 0..tracks.length()) {
|
var index = 1
|
||||||
|
var hasUpdates = false
|
||||||
|
for (i in 0 until tracks.length()) {
|
||||||
var track = tracks.getJSONObject(i)
|
var track = tracks.getJSONObject(i)
|
||||||
var localFileId = track.getString("localFileId")
|
var localFileId = track.getString("localFileId")
|
||||||
Log.d(tag, "LOCAL FILE ID $localFileId")
|
|
||||||
|
var existingTrack = audioTracks.find{ it.localFileId == localFileId }
|
||||||
|
if (existingTrack != null) {
|
||||||
|
Log.d(tag, "Found existing track ${existingTrack.localFileId} that has index ${existingTrack.index} should be index $index")
|
||||||
|
if (existingTrack.index != index) hasUpdates = true
|
||||||
|
existingTrack.index = index++
|
||||||
|
} else {
|
||||||
|
Log.e(tag, "Audio track with local file id not found")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasUpdates) {
|
||||||
|
Log.d(tag, "Save library item track orders")
|
||||||
|
localLibraryItem.media.setAudioTracks(audioTracks)
|
||||||
|
DeviceManager.dbManager.saveLocalLibraryItem(localLibraryItem)
|
||||||
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(localLibraryItem)))
|
||||||
|
} else {
|
||||||
|
Log.d(tag, "No tracks need to be updated")
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Generic Webview calls to db
|
// Generic Webview calls to db
|
||||||
|
|
|
@ -11,6 +11,7 @@ import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.device.FolderScanner
|
import com.audiobookshelf.app.device.FolderScanner
|
||||||
import com.audiobookshelf.app.server.ApiHandler
|
import com.audiobookshelf.app.server.ApiHandler
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.getcapacitor.JSObject
|
import com.getcapacitor.JSObject
|
||||||
import com.getcapacitor.Plugin
|
import com.getcapacitor.Plugin
|
||||||
|
@ -28,6 +29,7 @@ import java.util.*
|
||||||
@CapacitorPlugin(name = "AbsDownloader")
|
@CapacitorPlugin(name = "AbsDownloader")
|
||||||
class AbsDownloader : Plugin() {
|
class AbsDownloader : Plugin() {
|
||||||
private val tag = "AbsDownloader"
|
private val tag = "AbsDownloader"
|
||||||
|
var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||||
|
|
||||||
lateinit var mainActivity: MainActivity
|
lateinit var mainActivity: MainActivity
|
||||||
lateinit var downloadManager: DownloadManager
|
lateinit var downloadManager: DownloadManager
|
||||||
|
@ -294,7 +296,7 @@ class AbsDownloader : Plugin() {
|
||||||
DeviceManager.dbManager.saveDownloadItem(downloadItem)
|
DeviceManager.dbManager.saveDownloadItem(downloadItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
notifyListeners("onItemDownloadUpdate", JSObject(jacksonObjectMapper().writeValueAsString(downloadItem)))
|
notifyListeners("onItemDownloadUpdate", JSObject(jacksonMapper.writeValueAsString(downloadItem)))
|
||||||
delay(500)
|
delay(500)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,7 +310,7 @@ class AbsDownloader : Plugin() {
|
||||||
jsobj.put("libraryItemId", downloadItem.id)
|
jsobj.put("libraryItemId", downloadItem.id)
|
||||||
jsobj.put("localFolderId", downloadItem.localFolder.id)
|
jsobj.put("localFolderId", downloadItem.localFolder.id)
|
||||||
if (localLibraryItem != null) {
|
if (localLibraryItem != null) {
|
||||||
jsobj.put("localLibraryItem", JSObject(jacksonObjectMapper().writeValueAsString(localLibraryItem)))
|
jsobj.put("localLibraryItem", JSObject(jacksonMapper.writeValueAsString(localLibraryItem)))
|
||||||
}
|
}
|
||||||
notifyListeners("onItemDownloadComplete", jsobj)
|
notifyListeners("onItemDownloadComplete", jsobj)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ import com.audiobookshelf.app.data.LocalFolder
|
||||||
import com.audiobookshelf.app.data.LocalLibraryItem
|
import com.audiobookshelf.app.data.LocalLibraryItem
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.device.FolderScanner
|
import com.audiobookshelf.app.device.FolderScanner
|
||||||
|
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.getcapacitor.*
|
import com.getcapacitor.*
|
||||||
import com.getcapacitor.annotation.CapacitorPlugin
|
import com.getcapacitor.annotation.CapacitorPlugin
|
||||||
|
@ -26,6 +27,7 @@ import kotlinx.coroutines.launch
|
||||||
class AbsFileSystem : Plugin() {
|
class AbsFileSystem : Plugin() {
|
||||||
private val TAG = "AbsFileSystem"
|
private val TAG = "AbsFileSystem"
|
||||||
private val tag = "AbsFileSystem"
|
private val tag = "AbsFileSystem"
|
||||||
|
var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||||
|
|
||||||
lateinit var mainActivity: MainActivity
|
lateinit var mainActivity: MainActivity
|
||||||
|
|
||||||
|
@ -77,7 +79,7 @@ class AbsFileSystem : Plugin() {
|
||||||
var localFolder = LocalFolder(folderId, folder.name ?: "", folder.uri.toString(),basePath,absolutePath, simplePath, storageType.toString(), mediaType)
|
var localFolder = LocalFolder(folderId, folder.name ?: "", folder.uri.toString(),basePath,absolutePath, simplePath, storageType.toString(), mediaType)
|
||||||
|
|
||||||
DeviceManager.dbManager.saveLocalFolder(localFolder)
|
DeviceManager.dbManager.saveLocalFolder(localFolder)
|
||||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(localFolder)))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(localFolder)))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStorageAccessDenied(requestCode: Int, folder: DocumentFile?, storageType: StorageType) {
|
override fun onStorageAccessDenied(requestCode: Int, folder: DocumentFile?, storageType: StorageType) {
|
||||||
|
@ -148,8 +150,8 @@ class AbsFileSystem : Plugin() {
|
||||||
Log.d(TAG, "NO Scan DATA")
|
Log.d(TAG, "NO Scan DATA")
|
||||||
return call.resolve(JSObject())
|
return call.resolve(JSObject())
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Scan DATA ${jacksonObjectMapper().writeValueAsString(folderScanResult)}")
|
Log.d(TAG, "Scan DATA ${jacksonMapper.writeValueAsString(folderScanResult)}")
|
||||||
return call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(folderScanResult)))
|
return call.resolve(JSObject(jacksonMapper.writeValueAsString(folderScanResult)))
|
||||||
}
|
}
|
||||||
} ?: call.resolve(JSObject())
|
} ?: call.resolve(JSObject())
|
||||||
}
|
}
|
||||||
|
@ -182,8 +184,8 @@ class AbsFileSystem : Plugin() {
|
||||||
Log.d(TAG, "NO Scan DATA")
|
Log.d(TAG, "NO Scan DATA")
|
||||||
call.resolve(JSObject())
|
call.resolve(JSObject())
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "Scan DATA ${jacksonObjectMapper().writeValueAsString(scanResult)}")
|
Log.d(TAG, "Scan DATA ${jacksonMapper.writeValueAsString(scanResult)}")
|
||||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(scanResult)))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(scanResult)))
|
||||||
}
|
}
|
||||||
} ?: call.resolve(JSObject())
|
} ?: call.resolve(JSObject())
|
||||||
}
|
}
|
||||||
|
@ -223,7 +225,7 @@ class AbsFileSystem : Plugin() {
|
||||||
localLibraryItem?.media?.removeAudioTrack(trackLocalFileId)
|
localLibraryItem?.media?.removeAudioTrack(trackLocalFileId)
|
||||||
localLibraryItem?.removeLocalFile(trackLocalFileId)
|
localLibraryItem?.removeLocalFile(trackLocalFileId)
|
||||||
DeviceManager.dbManager.saveLocalLibraryItem(localLibraryItem)
|
DeviceManager.dbManager.saveLocalLibraryItem(localLibraryItem)
|
||||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(localLibraryItem)))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(localLibraryItem)))
|
||||||
} else {
|
} else {
|
||||||
call.resolve(JSObject("{\"success\":false}"))
|
call.resolve(JSObject("{\"success\":false}"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import com.audiobookshelf.app.data.PlaybackSession
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.player.MediaProgressSyncData
|
import com.audiobookshelf.app.player.MediaProgressSyncData
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
|
||||||
|
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import com.getcapacitor.JSArray
|
import com.getcapacitor.JSArray
|
||||||
|
@ -26,6 +27,7 @@ import java.io.IOException
|
||||||
class ApiHandler {
|
class ApiHandler {
|
||||||
val tag = "ApiHandler"
|
val tag = "ApiHandler"
|
||||||
private var client = OkHttpClient()
|
private var client = OkHttpClient()
|
||||||
|
var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||||
var ctx: Context
|
var ctx: Context
|
||||||
var storageSharedPreferences: SharedPreferences? = null
|
var storageSharedPreferences: SharedPreferences? = null
|
||||||
|
|
||||||
|
@ -54,6 +56,15 @@ class ApiHandler {
|
||||||
makeRequest(request, cb)
|
makeRequest(request, cb)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun patchRequest(endpoint:String, payload: JSObject, cb: (JSObject) -> Unit) {
|
||||||
|
val mediaType = "application/json; charset=utf-8".toMediaType()
|
||||||
|
val requestBody = payload.toString().toRequestBody(mediaType)
|
||||||
|
val request = Request.Builder().patch(requestBody)
|
||||||
|
.url("${DeviceManager.serverAddress}$endpoint").addHeader("Authorization", "Bearer ${DeviceManager.token}")
|
||||||
|
.build()
|
||||||
|
makeRequest(request, cb)
|
||||||
|
}
|
||||||
|
|
||||||
fun isOnline(): Boolean {
|
fun isOnline(): Boolean {
|
||||||
val connectivityManager = ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
val connectivityManager = ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
if (connectivityManager != null) {
|
if (connectivityManager != null) {
|
||||||
|
@ -105,7 +116,7 @@ class ApiHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLibraries(cb: (List<Library>) -> Unit) {
|
fun getLibraries(cb: (List<Library>) -> Unit) {
|
||||||
val mapper = jacksonObjectMapper()
|
val mapper = jacksonMapper
|
||||||
getRequest("/api/libraries") {
|
getRequest("/api/libraries") {
|
||||||
val libraries = mutableListOf<Library>()
|
val libraries = mutableListOf<Library>()
|
||||||
if (it.has("value")) {
|
if (it.has("value")) {
|
||||||
|
@ -121,7 +132,7 @@ class ApiHandler {
|
||||||
|
|
||||||
fun getLibraryItem(libraryItemId:String, cb: (LibraryItem) -> Unit) {
|
fun getLibraryItem(libraryItemId:String, cb: (LibraryItem) -> Unit) {
|
||||||
getRequest("/api/items/$libraryItemId?expanded=1") {
|
getRequest("/api/items/$libraryItemId?expanded=1") {
|
||||||
val libraryItem = jacksonObjectMapper().readValue<LibraryItem>(it.toString())
|
val libraryItem = jacksonMapper.readValue<LibraryItem>(it.toString())
|
||||||
cb(libraryItem)
|
cb(libraryItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +143,7 @@ class ApiHandler {
|
||||||
if (it.has("results")) {
|
if (it.has("results")) {
|
||||||
var array = it.getJSONArray("results")
|
var array = it.getJSONArray("results")
|
||||||
for (i in 0 until array.length()) {
|
for (i in 0 until array.length()) {
|
||||||
val item = jacksonObjectMapper().readValue<LibraryItem>(array.get(i).toString())
|
val item = jacksonMapper.readValue<LibraryItem>(array.get(i).toString())
|
||||||
items.add(item)
|
items.add(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,13 +164,13 @@ class ApiHandler {
|
||||||
postRequest(endpoint, payload) {
|
postRequest(endpoint, payload) {
|
||||||
it.put("serverConnectionConfigId", DeviceManager.serverConnectionConfig?.id)
|
it.put("serverConnectionConfigId", DeviceManager.serverConnectionConfig?.id)
|
||||||
it.put("serverAddress", DeviceManager.serverAddress)
|
it.put("serverAddress", DeviceManager.serverAddress)
|
||||||
val playbackSession = jacksonObjectMapper().readValue<PlaybackSession>(it.toString())
|
val playbackSession = jacksonMapper.readValue<PlaybackSession>(it.toString())
|
||||||
cb(playbackSession)
|
cb(playbackSession)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendProgressSync(sessionId:String, syncData: MediaProgressSyncData, cb: () -> Unit) {
|
fun sendProgressSync(sessionId:String, syncData: MediaProgressSyncData, cb: () -> Unit) {
|
||||||
var payload = JSObject(jacksonObjectMapper().writeValueAsString(syncData))
|
var payload = JSObject(jacksonMapper.writeValueAsString(syncData))
|
||||||
|
|
||||||
postRequest("/api/session/$sessionId/sync", payload) {
|
postRequest("/api/session/$sessionId/sync", payload) {
|
||||||
cb()
|
cb()
|
||||||
|
@ -167,7 +178,7 @@ class ApiHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendLocalProgressSync(playbackSession:PlaybackSession, cb: () -> Unit) {
|
fun sendLocalProgressSync(playbackSession:PlaybackSession, cb: () -> Unit) {
|
||||||
var payload = JSObject(jacksonObjectMapper().writeValueAsString(playbackSession))
|
var payload = JSObject(jacksonMapper.writeValueAsString(playbackSession))
|
||||||
|
|
||||||
postRequest("/api/session/local", payload) {
|
postRequest("/api/session/local", payload) {
|
||||||
cb()
|
cb()
|
||||||
|
@ -190,14 +201,14 @@ class ApiHandler {
|
||||||
|
|
||||||
if (localMediaProgress.isNotEmpty()) {
|
if (localMediaProgress.isNotEmpty()) {
|
||||||
Log.d(tag, "Sending sync local progress request with ${localMediaProgress.size} progress items")
|
Log.d(tag, "Sending sync local progress request with ${localMediaProgress.size} progress items")
|
||||||
var payload = JSObject(jacksonObjectMapper().writeValueAsString(LocalMediaProgressSyncPayload(localMediaProgress)))
|
var payload = JSObject(jacksonMapper.writeValueAsString(LocalMediaProgressSyncPayload(localMediaProgress)))
|
||||||
postRequest("/api/me/sync-local-progress", payload) {
|
postRequest("/api/me/sync-local-progress", payload) {
|
||||||
Log.d(tag, "Media Progress Sync payload $payload - response ${it.toString()}")
|
Log.d(tag, "Media Progress Sync payload $payload - response ${it.toString()}")
|
||||||
|
|
||||||
if (it.toString() == "{}") {
|
if (it.toString() == "{}") {
|
||||||
Log.e(tag, "Progress sync received empty object")
|
Log.e(tag, "Progress sync received empty object")
|
||||||
} else {
|
} else {
|
||||||
val progressSyncResponsePayload = jacksonObjectMapper().readValue<MediaProgressSyncResponsePayload>(it.toString())
|
val progressSyncResponsePayload = jacksonMapper.readValue<MediaProgressSyncResponsePayload>(it.toString())
|
||||||
|
|
||||||
localSyncResultsPayload.numLocalProgressUpdates = progressSyncResponsePayload.localProgressUpdates.size
|
localSyncResultsPayload.numLocalProgressUpdates = progressSyncResponsePayload.localProgressUpdates.size
|
||||||
localSyncResultsPayload.numServerProgressUpdates = progressSyncResponsePayload.numServerProgressUpdates
|
localSyncResultsPayload.numServerProgressUpdates = progressSyncResponsePayload.numServerProgressUpdates
|
||||||
|
@ -217,4 +228,13 @@ class ApiHandler {
|
||||||
cb(localSyncResultsPayload)
|
cb(localSyncResultsPayload)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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"
|
||||||
|
patchRequest(endpoint,updatePayload) {
|
||||||
|
Log.d(tag, "updateMediaProgress patched progress")
|
||||||
|
cb()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
|
|
||||||
<widgets-download-progress-indicator />
|
<widgets-download-progress-indicator />
|
||||||
|
|
||||||
<nuxt-link class="h-7 mx-2" to="/search">
|
<nuxt-link v-if="user" class="h-7 mx-2" to="/search">
|
||||||
<span class="material-icons" style="font-size: 1.75rem">search</span>
|
<span class="material-icons" style="font-size: 1.75rem">search</span>
|
||||||
</nuxt-link>
|
</nuxt-link>
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,10 @@
|
||||||
<div id="streamContainer" class="w-full z-20 bg-primary absolute bottom-0 left-0 right-0 p-2 pointer-events-auto transition-all" @click="clickContainer">
|
<div id="streamContainer" class="w-full z-20 bg-primary absolute bottom-0 left-0 right-0 p-2 pointer-events-auto transition-all" @click="clickContainer">
|
||||||
<div v-if="showFullscreen" class="absolute top-0 left-0 right-0 w-full py-3 mx-auto px-3" style="max-width: 380px">
|
<div v-if="showFullscreen" class="absolute top-0 left-0 right-0 w-full py-3 mx-auto px-3" style="max-width: 380px">
|
||||||
<div class="flex items-center justify-between pointer-events-auto">
|
<div class="flex items-center justify-between pointer-events-auto">
|
||||||
<span class="material-icons text-3xl text-white text-opacity-75 cursor-pointer" @click="$emit('showBookmarks')">{{ bookmarks.length ? 'bookmark' : 'bookmark_border' }}</span>
|
<span v-if="!isPodcast" class="material-icons text-3xl text-white text-opacity-75 cursor-pointer" @click="$emit('showBookmarks')">{{ bookmarks.length ? 'bookmark' : 'bookmark_border' }}</span>
|
||||||
|
<!-- hidden for podcasts but still using this as a placeholder -->
|
||||||
|
<span v-else class="material-icons text-3xl text-white text-opacity-0">bookmark</span>
|
||||||
|
|
||||||
<span class="font-mono text-white text-opacity-75 cursor-pointer" style="font-size: 1.35rem" @click="$emit('selectPlaybackSpeed')">{{ currentPlaybackRate }}x</span>
|
<span class="font-mono text-white text-opacity-75 cursor-pointer" style="font-size: 1.35rem" @click="$emit('selectPlaybackSpeed')">{{ currentPlaybackRate }}x</span>
|
||||||
<svg v-if="!sleepTimerRunning" xmlns="http://www.w3.org/2000/svg" class="h-7 w-7 text-white text-opacity-75 cursor-pointer" fill="none" viewBox="0 0 24 24" stroke="currentColor" @click.stop="$emit('showSleepTimer')">
|
<svg v-if="!sleepTimerRunning" xmlns="http://www.w3.org/2000/svg" class="h-7 w-7 text-white text-opacity-75 cursor-pointer" fill="none" viewBox="0 0 24 24" stroke="currentColor" @click.stop="$emit('showSleepTimer')">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
||||||
|
@ -156,6 +159,12 @@ export default {
|
||||||
}
|
}
|
||||||
return this.showFullscreen ? 200 : 60
|
return this.showFullscreen ? 200 : 60
|
||||||
},
|
},
|
||||||
|
mediaType() {
|
||||||
|
return this.playbackSession ? this.playbackSession.mediaType : null
|
||||||
|
},
|
||||||
|
isPodcast() {
|
||||||
|
return this.mediaType === 'podcast'
|
||||||
|
},
|
||||||
mediaMetadata() {
|
mediaMetadata() {
|
||||||
return this.playbackSession ? this.playbackSession.mediaMetadata : null
|
return this.playbackSession ? this.playbackSession.mediaMetadata : null
|
||||||
},
|
},
|
||||||
|
@ -552,6 +561,10 @@ export default {
|
||||||
}
|
}
|
||||||
this.isEnded = data.playerState == this.$constants.PlayerState.ENDED
|
this.isEnded = data.playerState == this.$constants.PlayerState.ENDED
|
||||||
|
|
||||||
|
console.log('received metadata update', data)
|
||||||
|
|
||||||
|
if (data.currentRate && data.currentRate > 0) this.playbackSpeed = data.currentRate
|
||||||
|
|
||||||
this.timeupdate()
|
this.timeupdate()
|
||||||
},
|
},
|
||||||
// When a playback session is started the native android/ios will send the session
|
// When a playback session is started the native android/ios will send the session
|
||||||
|
|
|
@ -101,12 +101,14 @@ export default {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.$platform !== 'ios') {
|
||||||
items.push({
|
items.push({
|
||||||
icon: 'folder',
|
icon: 'folder',
|
||||||
iconOutlined: true,
|
iconOutlined: true,
|
||||||
text: 'Local Media',
|
text: 'Local Media',
|
||||||
to: '/localMedia/folders'
|
to: '/localMedia/folders'
|
||||||
})
|
})
|
||||||
|
}
|
||||||
return items
|
return items
|
||||||
},
|
},
|
||||||
currentRoutePath() {
|
currentRoutePath() {
|
||||||
|
|
|
@ -120,7 +120,7 @@ export default {
|
||||||
return this.media.coverPath || this.placeholderUrl
|
return this.media.coverPath || this.placeholderUrl
|
||||||
},
|
},
|
||||||
hasCover() {
|
hasCover() {
|
||||||
return !!this.media.coverPath || this.localCover
|
return !!this.media.coverPath || this.localCover || this.downloadCover
|
||||||
},
|
},
|
||||||
sizeMultiplier() {
|
sizeMultiplier() {
|
||||||
var baseSize = this.squareAspectRatio ? 192 : 120
|
var baseSize = this.squareAspectRatio ? 192 : 120
|
||||||
|
|
|
@ -1,12 +1,16 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full px-0 py-4 overflow-hidden relative border-b border-white border-opacity-10">
|
<div class="w-full px-0 py-4 overflow-hidden relative border-b border-white border-opacity-10">
|
||||||
<div v-if="episode" class="flex items-center">
|
<div v-if="episode" class="w-full px-1">
|
||||||
<!-- <div class="w-12 min-w-12 max-w-16 h-full">
|
<!-- Help debug for testing -->
|
||||||
<div class="flex h-full items-center justify-center">
|
<!-- <template>
|
||||||
<span class="material-icons drag-handle text-lg text-white text-opacity-50 hover:text-opacity-100">menu</span>
|
<p class="text-xs mb-1">{{ isLocal ? 'LOCAL' : 'NOT LOCAL' }}</p>
|
||||||
</div>
|
<p class="text-xs mb-4">Lid:{{ libraryItemId }}<br />Eid:{{ episode.id }}<br />LLid:{{ localLibraryItemId }}<br />LEid:{{ localEpisodeId }}</p>
|
||||||
</div> -->
|
<p v-if="itemProgress">Server Media Progress {{ Math.round(itemProgress.progress * 100) }}</p>
|
||||||
<div class="flex-grow px-1">
|
<p v-else>No Server Media Progress</p>
|
||||||
|
<p v-if="localMediaProgress">Local Media Progress {{ Math.round(localMediaProgress.progress * 100) }}</p>
|
||||||
|
<p v-else>No Local Media Progress</p>
|
||||||
|
</template> -->
|
||||||
|
|
||||||
<p v-if="publishedAt" class="text-xs text-gray-400 mb-1">Published {{ $formatDate(publishedAt, 'MMM do, yyyy') }}</p>
|
<p v-if="publishedAt" class="text-xs text-gray-400 mb-1">Published {{ $formatDate(publishedAt, 'MMM do, yyyy') }}</p>
|
||||||
|
|
||||||
<p class="text-sm font-semibold">
|
<p class="text-sm font-semibold">
|
||||||
|
@ -22,8 +26,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" borderless class="mx-1 mt-0.5" @click="toggleFinished" />
|
<ui-read-icon-btn :disabled="isProcessingReadUpdate" :is-read="userIsFinished" borderless class="mx-1 mt-0.5" @click="toggleFinished" />
|
||||||
<span class="material-icons px-2" :class="downloadItem ? 'animate-bounce text-warning text-opacity-75 text-xl' : 'text-gray-300 text-xl'" @click="downloadClick">{{ downloadItem ? 'downloading' : 'download' }}</span>
|
|
||||||
</div>
|
<span v-if="isLocal" class="material-icons-outlined px-2 text-success text-lg">audio_file</span>
|
||||||
|
<span v-else-if="!localEpisode" class="material-icons px-2" :class="downloadItem ? 'animate-bounce text-warning text-opacity-75 text-xl' : 'text-gray-300 text-xl'" @click="downloadClick">{{ downloadItem ? 'downloading' : 'download' }}</span>
|
||||||
|
<span v-else class="material-icons px-2 text-success text-xl">download_done</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -38,11 +44,16 @@ import { AbsDownloader } from '@/plugins/capacitor'
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
libraryItemId: String,
|
libraryItemId: String,
|
||||||
isLocal: Boolean,
|
|
||||||
episode: {
|
episode: {
|
||||||
type: Object,
|
type: Object,
|
||||||
default: () => {}
|
default: () => {}
|
||||||
}
|
},
|
||||||
|
localLibraryItemId: String,
|
||||||
|
localEpisode: {
|
||||||
|
type: Object,
|
||||||
|
default: () => {}
|
||||||
|
},
|
||||||
|
isLocal: Boolean
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -68,8 +79,15 @@ export default {
|
||||||
return this.$secondsToTimestamp(this.episode.duration)
|
return this.$secondsToTimestamp(this.episode.duration)
|
||||||
},
|
},
|
||||||
isStreaming() {
|
isStreaming() {
|
||||||
|
if (this.playerIsLocal && this.localLibraryItemId && this.localEpisode) {
|
||||||
|
// Check is streaming local version of this episode
|
||||||
|
return this.$store.getters['getIsEpisodeStreaming'](this.localLibraryItemId, this.localEpisode.id)
|
||||||
|
}
|
||||||
return this.$store.getters['getIsEpisodeStreaming'](this.libraryItemId, this.episode.id)
|
return this.$store.getters['getIsEpisodeStreaming'](this.libraryItemId, this.episode.id)
|
||||||
},
|
},
|
||||||
|
playerIsLocal() {
|
||||||
|
return !!this.$store.state.playerIsLocal
|
||||||
|
},
|
||||||
streamIsPlaying() {
|
streamIsPlaying() {
|
||||||
return this.$store.state.playerIsPlaying && this.isStreaming
|
return this.$store.state.playerIsPlaying && this.isStreaming
|
||||||
},
|
},
|
||||||
|
@ -77,6 +95,14 @@ export default {
|
||||||
if (this.isLocal) return this.$store.getters['globals/getLocalMediaProgressById'](this.libraryItemId, this.episode.id)
|
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)
|
return this.$store.getters['user/getUserMediaProgress'](this.libraryItemId, this.episode.id)
|
||||||
},
|
},
|
||||||
|
localMediaProgress() {
|
||||||
|
if (this.isLocal) return this.$store.getters['globals/getLocalMediaProgressById'](this.libraryItemId, this.episode.id)
|
||||||
|
else if (this.localLibraryItemId && this.localEpisode) {
|
||||||
|
return this.$store.getters['globals/getLocalMediaProgressById'](this.localLibraryItemId, this.localEpisode.id)
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
},
|
||||||
itemProgressPercent() {
|
itemProgressPercent() {
|
||||||
return this.itemProgress ? this.itemProgress.progress : 0
|
return this.itemProgress ? this.itemProgress.progress : 0
|
||||||
},
|
},
|
||||||
|
@ -95,6 +121,9 @@ export default {
|
||||||
},
|
},
|
||||||
downloadItem() {
|
downloadItem() {
|
||||||
return this.$store.getters['globals/getDownloadItem'](this.libraryItemId, this.episode.id)
|
return this.$store.getters['globals/getDownloadItem'](this.libraryItemId, this.episode.id)
|
||||||
|
},
|
||||||
|
localEpisodeId() {
|
||||||
|
return this.localEpisode ? this.localEpisode.id : null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
@ -153,18 +182,57 @@ export default {
|
||||||
playClick() {
|
playClick() {
|
||||||
if (this.streamIsPlaying) {
|
if (this.streamIsPlaying) {
|
||||||
this.$eventBus.$emit('pause-item')
|
this.$eventBus.$emit('pause-item')
|
||||||
|
} else {
|
||||||
|
if (this.localEpisode && this.localLibraryItemId) {
|
||||||
|
console.log('Play local episode', this.localEpisode.id, this.localLibraryItemId)
|
||||||
|
|
||||||
|
this.$eventBus.$emit('play-item', {
|
||||||
|
libraryItemId: this.localLibraryItemId,
|
||||||
|
episodeId: this.localEpisode.id
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
this.$eventBus.$emit('play-item', {
|
this.$eventBus.$emit('play-item', {
|
||||||
libraryItemId: this.libraryItemId,
|
libraryItemId: this.libraryItemId,
|
||||||
episodeId: this.episode.id
|
episodeId: this.episode.id
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
toggleFinished() {
|
async toggleFinished() {
|
||||||
|
this.isProcessingReadUpdate = true
|
||||||
|
if (this.isLocal || this.localEpisode) {
|
||||||
|
var isFinished = !this.userIsFinished
|
||||||
|
var localLibraryItemId = this.isLocal ? this.libraryItemId : this.localLibraryItemId
|
||||||
|
var localEpisodeId = this.isLocal ? this.episode.id : this.localEpisode.id
|
||||||
|
var localMediaProgressId = `${localLibraryItemId}-${localEpisodeId}`
|
||||||
|
console.log('toggleFinished local media progress id', localMediaProgressId, isFinished)
|
||||||
|
var payload = await this.$db.updateLocalMediaProgressFinished({ localMediaProgressId, isFinished })
|
||||||
|
console.log('toggleFinished payload', JSON.stringify(payload))
|
||||||
|
if (!payload || payload.error) {
|
||||||
|
var errorMsg = payload ? payload.error : 'Unknown error'
|
||||||
|
this.$toast.error(errorMsg)
|
||||||
|
} else {
|
||||||
|
var localMediaProgress = payload.localMediaProgress
|
||||||
|
console.log('toggleFinished localMediaProgress', JSON.stringify(localMediaProgress))
|
||||||
|
if (localMediaProgress) {
|
||||||
|
this.$store.commit('globals/updateLocalMediaProgress', localMediaProgress)
|
||||||
|
}
|
||||||
|
|
||||||
|
var lmp = this.$store.getters['globals/getLocalMediaProgressById'](this.libraryItemId, this.episode.id)
|
||||||
|
console.log('toggleFinished Check LMP', this.libraryItemId, this.episode.id, JSON.stringify(lmp))
|
||||||
|
|
||||||
|
var serverUpdated = payload.server
|
||||||
|
if (serverUpdated) {
|
||||||
|
this.$toast.success(`Local & Server Item marked as ${isFinished ? 'Finished' : 'Not Finished'}`)
|
||||||
|
} else {
|
||||||
|
this.$toast.success(`Local Item marked as ${isFinished ? 'Finished' : 'Not Finished'}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.isProcessingReadUpdate = false
|
||||||
|
} else {
|
||||||
var updatePayload = {
|
var updatePayload = {
|
||||||
isFinished: !this.userIsFinished
|
isFinished: !this.userIsFinished
|
||||||
}
|
}
|
||||||
this.isProcessingReadUpdate = true
|
|
||||||
this.$axios
|
this.$axios
|
||||||
.$patch(`/api/me/progress/${this.libraryItemId}/${this.episode.id}`, updatePayload)
|
.$patch(`/api/me/progress/${this.libraryItemId}/${this.episode.id}`, updatePayload)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -177,7 +245,7 @@ export default {
|
||||||
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
this.$toast.error(`Failed to mark as ${updatePayload.isFinished ? 'Finished' : 'Not Finished'}`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
mounted() {}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -3,7 +3,7 @@
|
||||||
<p class="text-lg mb-1 font-semibold">Episodes ({{ episodes.length }})</p>
|
<p class="text-lg mb-1 font-semibold">Episodes ({{ episodes.length }})</p>
|
||||||
|
|
||||||
<template v-for="episode in episodes">
|
<template v-for="episode in episodes">
|
||||||
<tables-podcast-episode-row :episode="episode" :library-item-id="libraryItemId" :key="episode.id" />
|
<tables-podcast-episode-row :episode="episode" :local-episode="localEpisodeMap[episode.id]" :library-item-id="libraryItemId" :local-library-item-id="localLibraryItemId" :is-local="isLocal" :key="episode.id" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -15,12 +15,29 @@ export default {
|
||||||
episodes: {
|
episodes: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => []
|
||||||
}
|
},
|
||||||
|
localLibraryItemId: String,
|
||||||
|
localEpisodes: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
isLocal: Boolean // If is local then episodes and libraryItemId are local, otherwise local is passed in localLibraryItemId and localEpisodes
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {}
|
return {}
|
||||||
},
|
},
|
||||||
computed: {},
|
computed: {
|
||||||
|
// Map of local episodes where server episode id is key
|
||||||
|
localEpisodeMap() {
|
||||||
|
var epmap = {}
|
||||||
|
this.localEpisodes.forEach((localEp) => {
|
||||||
|
if (localEp.serverEpisodeId) {
|
||||||
|
epmap[localEp.serverEpisodeId] = localEp
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return epmap
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {},
|
methods: {},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,7 +71,9 @@ export default {
|
||||||
data.itemProgress = itemProgress
|
data.itemProgress = itemProgress
|
||||||
data.episodes = downloadItemParts.filter((dip) => dip.episode).map((dip) => dip.episode)
|
data.episodes = downloadItemParts.filter((dip) => dip.episode).map((dip) => dip.episode)
|
||||||
|
|
||||||
console.log('Saving item update download payload', JSON.stringify(update))
|
console.log('[download] Saving item update download payload', JSON.stringify(update))
|
||||||
|
console.log('[download] Download Progress indicator data', JSON.stringify(data))
|
||||||
|
|
||||||
this.$set(this.itemDownloadingMap, update.id, update)
|
this.$set(this.itemDownloadingMap, update.id, update)
|
||||||
|
|
||||||
this.$store.commit('globals/addUpdateItemDownload', data)
|
this.$store.commit('globals/addUpdateItemDownload', data)
|
||||||
|
|
|
@ -58,7 +58,7 @@
|
||||||
<p class="text-sm">{{ description }}</p>
|
<p class="text-sm">{{ description }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<tables-podcast-episodes-table v-if="isPodcast" :library-item-id="libraryItemId" :episodes="episodes" />
|
<tables-podcast-episodes-table v-if="isPodcast" :library-item-id="libraryItemId" :local-library-item-id="localLibraryItemId" :episodes="episodes" :local-episodes="localLibraryItemEpisodes" :is-local="isLocal" />
|
||||||
|
|
||||||
<modals-select-local-folder-modal v-model="showSelectLocalFolder" :media-type="mediaType" @select="selectedLocalFolder" />
|
<modals-select-local-folder-modal v-model="showSelectLocalFolder" :media-type="mediaType" @select="selectedLocalFolder" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -120,6 +120,14 @@ export default {
|
||||||
if (this.isLocal) return this.libraryItem
|
if (this.isLocal) return this.libraryItem
|
||||||
return this.libraryItem.localLibraryItem || null
|
return this.libraryItem.localLibraryItem || null
|
||||||
},
|
},
|
||||||
|
localLibraryItemId() {
|
||||||
|
return this.localLibraryItem ? this.localLibraryItem.id : null
|
||||||
|
},
|
||||||
|
localLibraryItemEpisodes() {
|
||||||
|
if (!this.isPodcast || !this.localLibraryItem) return []
|
||||||
|
var podcastMedia = this.localLibraryItem.media
|
||||||
|
return podcastMedia ? podcastMedia.episodes || [] : []
|
||||||
|
},
|
||||||
isConnected() {
|
isConnected() {
|
||||||
return this.$store.state.socketConnected
|
return this.$store.state.socketConnected
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="w-full h-full py-6 px-2">
|
<div class="w-full h-full py-6 px-2">
|
||||||
<div v-if="localLibraryItem" class="w-full h-full">
|
<div v-if="localLibraryItem" class="w-full h-full" :class="orderChanged ? 'pb-20' : ''">
|
||||||
<div class="px-2 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>
|
<p class="text-base font-book font-semibold">{{ mediaMetadata.title }}</p>
|
||||||
<div class="flex-grow" />
|
<div class="flex-grow" />
|
||||||
|
@ -46,28 +46,10 @@
|
||||||
</template>
|
</template>
|
||||||
</transition-group>
|
</transition-group>
|
||||||
</draggable>
|
</draggable>
|
||||||
|
|
||||||
<!-- <template v-for="track in audioTracks">
|
|
||||||
<div :key="track.localFileId" 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">{{ track.index }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow px-2">
|
|
||||||
<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>
|
|
||||||
<p class="text-sm">{{ $elapsedPretty(track.duration) }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="w-12 h-12 flex items-center justify-center" style="min-width: 48px">
|
|
||||||
<span class="material-icons" @click="showTrackDialog(track)">more_vert</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="w-full">
|
<div v-else class="w-full">
|
||||||
<p class="text-base mb-2">Episodes ({{ audioTracks.length }})</p>
|
<p class="text-base mb-2">Episodes ({{ episodes.length }})</p>
|
||||||
<template v-for="episode in audioTracks">
|
<template v-for="episode in episodes">
|
||||||
<div :key="episode.id" class="flex items-center my-1">
|
<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">
|
<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>
|
<p class="font-mono font-bold text-xl">{{ episode.index }}</p>
|
||||||
|
@ -108,6 +90,11 @@
|
||||||
<p class="text-lg text-center px-8">{{ failed ? 'Failed to get local library item ' + localLibraryItemId : 'Loading..' }}</p>
|
<p class="text-lg text-center px-8">{{ failed ? 'Failed to get local library item ' + localLibraryItemId : 'Loading..' }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div v-if="orderChanged" class="fixed bottom-0 left-0 w-full py-4 px-4 bg-bg box-shadow-book flex items-center">
|
||||||
|
<div class="flex-grow" />
|
||||||
|
<ui-btn small color="success" @click="saveTrackOrder">Save Order</ui-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
<modals-dialog v-model="showDialog" :items="dialogItems" @action="dialogAction" />
|
<modals-dialog v-model="showDialog" :items="dialogItems" @action="dialogAction" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -146,7 +133,8 @@ export default {
|
||||||
isScanning: false,
|
isScanning: false,
|
||||||
showDialog: false,
|
showDialog: false,
|
||||||
selectedAudioTrack: null,
|
selectedAudioTrack: null,
|
||||||
selectedEpisode: null
|
selectedEpisode: null,
|
||||||
|
orderChanged: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -162,7 +150,6 @@ export default {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return this.localFiles.filter((lf) => {
|
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)
|
return !this.audioTracks.find((at) => at.localFileId == lf.id)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -187,16 +174,19 @@ export default {
|
||||||
mediaMetadata() {
|
mediaMetadata() {
|
||||||
return this.media ? this.media.metadata || {} : {}
|
return this.media ? this.media.metadata || {} : {}
|
||||||
},
|
},
|
||||||
|
episodes() {
|
||||||
|
return this.media.episodes || []
|
||||||
|
},
|
||||||
audioTracks() {
|
audioTracks() {
|
||||||
if (!this.media) return []
|
if (!this.media) return []
|
||||||
if (this.mediaType == 'book') {
|
if (this.mediaType == 'book') {
|
||||||
return this.media.tracks || []
|
return this.media.tracks || []
|
||||||
} else {
|
} else {
|
||||||
return this.media.episodes || []
|
return (this.media.episodes || []).map((ep) => ep.audioTrack)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
dialogItems() {
|
dialogItems() {
|
||||||
if (this.selectedAudioTrack) {
|
if (this.selectedAudioTrack || this.selectedEpisode) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
text: 'Hard Delete',
|
text: 'Hard Delete',
|
||||||
|
@ -227,13 +217,33 @@ export default {
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
draggableUpdate() {
|
draggableUpdate() {
|
||||||
console.log('Draggable update', this.audioTracksCopy)
|
console.log('Draggable Update')
|
||||||
// var copyOfCopy = this.audioTracksCopy.map((at) => ({ ...at }))
|
for (let i = 0; i < this.audioTracksCopy.length; i++) {
|
||||||
// const payload = {
|
var trackCopy = this.audioTracksCopy[i]
|
||||||
// localLibraryItemId: this.localLibraryItemId,
|
var track = this.audioTracks[i]
|
||||||
// tracks: copyOfCopy
|
if (track.localFileId !== trackCopy.localFileId) {
|
||||||
// }
|
this.orderChanged = true
|
||||||
// this.$db.updateLocalTrackOrder(payload)
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.orderChanged = false
|
||||||
|
},
|
||||||
|
async saveTrackOrder() {
|
||||||
|
var copyOfCopy = this.audioTracksCopy.map((at) => ({ ...at }))
|
||||||
|
const payload = {
|
||||||
|
localLibraryItemId: this.localLibraryItemId,
|
||||||
|
tracks: copyOfCopy
|
||||||
|
}
|
||||||
|
var response = await this.$db.updateLocalTrackOrder(payload)
|
||||||
|
if (response) {
|
||||||
|
this.$toast.success('Library item updated')
|
||||||
|
console.log('updateLocal track order response', JSON.stringify(response))
|
||||||
|
this.localLibraryItem = response
|
||||||
|
this.audioTracksCopy = this.audioTracks.map((at) => ({ ...at }))
|
||||||
|
} else {
|
||||||
|
this.$toast.info(`No updates necessary`)
|
||||||
|
}
|
||||||
|
this.orderChanged = false
|
||||||
},
|
},
|
||||||
showItemDialog() {
|
showItemDialog() {
|
||||||
this.selectedAudioTrack = null
|
this.selectedAudioTrack = null
|
||||||
|
|
|
@ -198,6 +198,10 @@ class AbsDatabaseWeb extends WebPlugin {
|
||||||
async updateLocalTrackOrder({ localLibraryItemId, tracks }) {
|
async updateLocalTrackOrder({ localLibraryItemId, tracks }) {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateLocalMediaProgressFinished({ localMediaProgressId, isFinished }) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const AbsDatabase = registerPlugin('AbsDatabase', {
|
const AbsDatabase = registerPlugin('AbsDatabase', {
|
||||||
|
|
|
@ -96,6 +96,11 @@ class DbService {
|
||||||
updateLocalTrackOrder(payload) {
|
updateLocalTrackOrder(payload) {
|
||||||
return AbsDatabase.updateLocalTrackOrder(payload)
|
return AbsDatabase.updateLocalTrackOrder(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// input: { localMediaProgressId:String, isFinished:Boolean }
|
||||||
|
updateLocalMediaProgressFinished(payload) {
|
||||||
|
return AbsDatabase.updateLocalMediaProgressFinished(payload)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ({ app, store }, inject) => {
|
export default ({ app, store }, inject) => {
|
||||||
|
|
|
@ -46,9 +46,10 @@ class ServerSocket extends EventEmitter {
|
||||||
this.socket.on('disconnect', this.onDisconnect.bind(this))
|
this.socket.on('disconnect', this.onDisconnect.bind(this))
|
||||||
this.socket.on('init', this.onInit.bind(this))
|
this.socket.on('init', this.onInit.bind(this))
|
||||||
this.socket.on('user_updated', this.onUserUpdated.bind(this))
|
this.socket.on('user_updated', this.onUserUpdated.bind(this))
|
||||||
|
this.socket.on('user_item_progress_updated', this.onUserItemProgressUpdated.bind(this))
|
||||||
|
|
||||||
this.socket.onAny((evt, args) => {
|
this.socket.onAny((evt, args) => {
|
||||||
console.log(`[SOCKET] ${this.socket.id}: ${evt} ${JSON.stringify(args)}`)
|
console.log(`[SOCKET] onAny: ${this.socket.id}: ${evt} ${JSON.stringify(args)}`)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +85,12 @@ class ServerSocket extends EventEmitter {
|
||||||
console.log('[SOCKET] User updated', data)
|
console.log('[SOCKET] User updated', data)
|
||||||
this.emit('user_updated', data)
|
this.emit('user_updated', data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onUserItemProgressUpdated(data) {
|
||||||
|
console.log('[SOCKET] User Item Progress Updated', JSON.stringify(data))
|
||||||
|
var progress = data.data
|
||||||
|
this.$store.commit('user/updateUserMediaProgress', progress)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default ({ app, store }, inject) => {
|
export default ({ app, store }, inject) => {
|
||||||
|
|
|
@ -9,7 +9,7 @@ export const getters = {
|
||||||
getDownloadItem: state => (libraryItemId, episodeId = null) => {
|
getDownloadItem: state => (libraryItemId, episodeId = null) => {
|
||||||
return state.itemDownloads.find(i => {
|
return state.itemDownloads.find(i => {
|
||||||
if (episodeId && !i.episodes.some(e => e.id == episodeId)) return false
|
if (episodeId && !i.episodes.some(e => e.id == episodeId)) return false
|
||||||
return i.id == libraryItemId
|
return i.libraryItemId == libraryItemId
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
getLibraryItemCoverSrc: (state, getters, rootState, rootGetters) => (libraryItem, placeholder = '/book_placeholder.jpg') => {
|
getLibraryItemCoverSrc: (state, getters, rootState, rootGetters) => (libraryItem, placeholder = '/book_placeholder.jpg') => {
|
||||||
|
@ -32,7 +32,7 @@ export const getters = {
|
||||||
},
|
},
|
||||||
getLocalMediaProgressById: (state) => (localLibraryItemId, episodeId = null) => {
|
getLocalMediaProgressById: (state) => (localLibraryItemId, episodeId = null) => {
|
||||||
return state.localMediaProgress.find(lmp => {
|
return state.localMediaProgress.find(lmp => {
|
||||||
if (episodeId != null && lmp.episodeId != episodeId) return false
|
if (episodeId != null && lmp.localEpisodeId != episodeId) return false
|
||||||
return lmp.localLibraryItemId == localLibraryItemId
|
return lmp.localLibraryItemId == localLibraryItemId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -73,8 +73,10 @@ export const mutations = {
|
||||||
}
|
}
|
||||||
var index = state.localMediaProgress.findIndex(lmp => lmp.id == prog.id)
|
var index = state.localMediaProgress.findIndex(lmp => lmp.id == prog.id)
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
|
console.log('UpdateLocalMediaProgress updating', prog.id, prog.progress)
|
||||||
state.localMediaProgress.splice(index, 1, prog)
|
state.localMediaProgress.splice(index, 1, prog)
|
||||||
} else {
|
} else {
|
||||||
|
console.log('updateLocalMediaProgress inserting new progress', prog.id, prog.progress)
|
||||||
state.localMediaProgress.push(prog)
|
state.localMediaProgress.push(prog)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -57,9 +57,17 @@ export const actions = {
|
||||||
|
|
||||||
export const mutations = {
|
export const mutations = {
|
||||||
setPlayerItem(state, playbackSession) {
|
setPlayerItem(state, playbackSession) {
|
||||||
|
state.playerIsLocal = playbackSession ? playbackSession.playMethod == this.$constants.PlayMethod.LOCAL : false
|
||||||
|
|
||||||
|
if (state.playerIsLocal) {
|
||||||
|
state.playerLibraryItemId = playbackSession ? playbackSession.localLibraryItem.id || null : null
|
||||||
|
state.playerEpisodeId = playbackSession ? playbackSession.localEpisodeId || null : null
|
||||||
|
} else {
|
||||||
state.playerLibraryItemId = playbackSession ? playbackSession.libraryItemId || null : null
|
state.playerLibraryItemId = playbackSession ? playbackSession.libraryItemId || null : null
|
||||||
state.playerEpisodeId = playbackSession ? playbackSession.episodeId || null : null
|
state.playerEpisodeId = playbackSession ? playbackSession.episodeId || null : null
|
||||||
state.playerIsLocal = playbackSession ? playbackSession.playMethod == this.$constants.PlayMethod.LOCAL : false
|
}
|
||||||
|
|
||||||
|
console.log('setPlayerItem', state.playerLibraryItemId, state.playerEpisodeId, state.playerIsLocal)
|
||||||
},
|
},
|
||||||
setPlayerPlaying(state, val) {
|
setPlayerPlaying(state, val) {
|
||||||
state.playerIsPlaying = val
|
state.playerIsPlaying = val
|
||||||
|
|
|
@ -74,6 +74,15 @@ export const mutations = {
|
||||||
if (!state.user) return
|
if (!state.user) return
|
||||||
state.user.mediaProgress = state.user.mediaProgress.filter(mp => mp.id != id)
|
state.user.mediaProgress = state.user.mediaProgress.filter(mp => mp.id != id)
|
||||||
},
|
},
|
||||||
|
updateUserMediaProgress(state, data) {
|
||||||
|
if (!data || !state.user) return
|
||||||
|
var mediaProgressIndex = state.user.mediaProgress.findIndex(mp => mp.id === data.id)
|
||||||
|
if (mediaProgressIndex >= 0) {
|
||||||
|
state.user.mediaProgress.splice(mediaProgressIndex, 1, data)
|
||||||
|
} else {
|
||||||
|
state.user.mediaProgress.push(data)
|
||||||
|
}
|
||||||
|
},
|
||||||
setServerConnectionConfig(state, serverConnectionConfig) {
|
setServerConnectionConfig(state, serverConnectionConfig) {
|
||||||
state.serverConnectionConfig = serverConnectionConfig
|
state.serverConnectionConfig = serverConnectionConfig
|
||||||
},
|
},
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue