mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-09-01 07:30:00 +02:00
Merge branch 'advplyr:master' into DataClasses
This commit is contained in:
commit
7189588c2b
34 changed files with 424 additions and 353 deletions
|
@ -29,8 +29,8 @@ android {
|
||||||
applicationId "com.audiobookshelf.app"
|
applicationId "com.audiobookshelf.app"
|
||||||
minSdkVersion rootProject.ext.minSdkVersion
|
minSdkVersion rootProject.ext.minSdkVersion
|
||||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||||
versionCode 70
|
versionCode 71
|
||||||
versionName "0.9.41-beta"
|
versionName "0.9.42-beta"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
aaptOptions {
|
aaptOptions {
|
||||||
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
// Files and dirs to omit from the packaged assets dir, modified to accommodate modern web apps.
|
||||||
|
|
|
@ -2,11 +2,6 @@ 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(
|
||||||
|
@ -27,15 +22,15 @@ data class AudioProbeChapterTags(
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
data class AudioProbeChapter(
|
data class AudioProbeChapter(
|
||||||
val id:Int,
|
val id:Int,
|
||||||
val start:Int,
|
val start:Long,
|
||||||
val end:Int,
|
val end:Long,
|
||||||
val tags:AudioProbeChapterTags?
|
val tags:AudioProbeChapterTags?
|
||||||
) {
|
) {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getBookChapter():BookChapter {
|
fun getBookChapter():BookChapter {
|
||||||
var startS = start / 1000.0
|
val startS = start / 1000.0
|
||||||
var endS = end / 1000.0
|
val endS = end / 1000.0
|
||||||
var title = tags?.title ?: "Chapter $id"
|
val title = tags?.title ?: "Chapter $id"
|
||||||
return BookChapter(id, startS, endS, title)
|
return BookChapter(id, startS, endS, title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ class Podcast(
|
||||||
) : MediaType(metadata, coverPath) {
|
) : MediaType(metadata, coverPath) {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
override fun getAudioTracks():List<AudioTrack> {
|
override fun getAudioTracks():List<AudioTrack> {
|
||||||
var tracks = episodes?.map { it.audioTrack }
|
val tracks = episodes?.map { it.audioTrack }
|
||||||
return tracks?.filterNotNull() ?: mutableListOf()
|
return tracks?.filterNotNull() ?: mutableListOf()
|
||||||
}
|
}
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
|
@ -98,7 +98,7 @@ class Podcast(
|
||||||
// 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,at.duration,0, null)
|
val 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -111,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,audioTrack.duration,0, null)
|
val 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
|
||||||
|
@ -132,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,audioTrack.duration,0, episode.id)
|
val newEpisode = PodcastEpisode("local_" + episode.id,episodes?.size ?: 0 + 1,episode.episode,episode.episodeType,episode.title,episode.subtitle,episode.description,null,audioTrack,audioTrack.duration,0, episode.id)
|
||||||
episodes?.add(newEpisode)
|
episodes?.add(newEpisode)
|
||||||
|
|
||||||
var index = 1
|
var index = 1
|
||||||
|
|
|
@ -17,9 +17,9 @@ class DbManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLocalLibraryItems(mediaType:String? = null):MutableList<LocalLibraryItem> {
|
fun getLocalLibraryItems(mediaType:String? = null):MutableList<LocalLibraryItem> {
|
||||||
var localLibraryItems:MutableList<LocalLibraryItem> = mutableListOf()
|
val localLibraryItems:MutableList<LocalLibraryItem> = mutableListOf()
|
||||||
Paper.book("localLibraryItems").allKeys.forEach {
|
Paper.book("localLibraryItems").allKeys.forEach {
|
||||||
var localLibraryItem:LocalLibraryItem? = Paper.book("localLibraryItems").read(it)
|
val localLibraryItem:LocalLibraryItem? = Paper.book("localLibraryItems").read(it)
|
||||||
if (localLibraryItem != null && (mediaType.isNullOrEmpty() || mediaType == localLibraryItem.mediaType)) {
|
if (localLibraryItem != null && (mediaType.isNullOrEmpty() || mediaType == localLibraryItem.mediaType)) {
|
||||||
localLibraryItems.add(localLibraryItem)
|
localLibraryItems.add(localLibraryItem)
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ class DbManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLocalLibraryItemsInFolder(folderId:String):List<LocalLibraryItem> {
|
fun getLocalLibraryItemsInFolder(folderId:String):List<LocalLibraryItem> {
|
||||||
var localLibraryItems = getLocalLibraryItems()
|
val localLibraryItems = getLocalLibraryItems()
|
||||||
return localLibraryItems.filter {
|
return localLibraryItems.filter {
|
||||||
it.folderId == folderId
|
it.folderId == folderId
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ class DbManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAllLocalFolders():List<LocalFolder> {
|
fun getAllLocalFolders():List<LocalFolder> {
|
||||||
var localFolders:MutableList<LocalFolder> = mutableListOf()
|
val localFolders:MutableList<LocalFolder> = mutableListOf()
|
||||||
Paper.book("localFolders").allKeys.forEach { localFolderId ->
|
Paper.book("localFolders").allKeys.forEach { localFolderId ->
|
||||||
Paper.book("localFolders").read<LocalFolder>(localFolderId)?.let {
|
Paper.book("localFolders").read<LocalFolder>(localFolderId)?.let {
|
||||||
localFolders.add(it)
|
localFolders.add(it)
|
||||||
|
@ -75,7 +75,7 @@ class DbManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeLocalFolder(folderId:String) {
|
fun removeLocalFolder(folderId:String) {
|
||||||
var localLibraryItems = getLocalLibraryItemsInFolder(folderId)
|
val localLibraryItems = getLocalLibraryItemsInFolder(folderId)
|
||||||
localLibraryItems.forEach {
|
localLibraryItems.forEach {
|
||||||
Paper.book("localLibraryItems").delete(it.id)
|
Paper.book("localLibraryItems").delete(it.id)
|
||||||
}
|
}
|
||||||
|
@ -91,7 +91,7 @@ class DbManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDownloadItems():List<AbsDownloader.DownloadItem> {
|
fun getDownloadItems():List<AbsDownloader.DownloadItem> {
|
||||||
var downloadItems:MutableList<AbsDownloader.DownloadItem> = mutableListOf()
|
val downloadItems:MutableList<AbsDownloader.DownloadItem> = mutableListOf()
|
||||||
Paper.book("downloadItems").allKeys.forEach { downloadItemId ->
|
Paper.book("downloadItems").allKeys.forEach { downloadItemId ->
|
||||||
Paper.book("downloadItems").read<AbsDownloader.DownloadItem>(downloadItemId)?.let {
|
Paper.book("downloadItems").read<AbsDownloader.DownloadItem>(downloadItemId)?.let {
|
||||||
downloadItems.add(it)
|
downloadItems.add(it)
|
||||||
|
@ -108,7 +108,7 @@ class DbManager {
|
||||||
return Paper.book("localMediaProgress").read(localMediaProgressId)
|
return Paper.book("localMediaProgress").read(localMediaProgressId)
|
||||||
}
|
}
|
||||||
fun getAllLocalMediaProgress():List<LocalMediaProgress> {
|
fun getAllLocalMediaProgress():List<LocalMediaProgress> {
|
||||||
var mediaProgress:MutableList<LocalMediaProgress> = mutableListOf()
|
val mediaProgress:MutableList<LocalMediaProgress> = mutableListOf()
|
||||||
Paper.book("localMediaProgress").allKeys.forEach { localMediaProgressId ->
|
Paper.book("localMediaProgress").allKeys.forEach { localMediaProgressId ->
|
||||||
Paper.book("localMediaProgress").read<LocalMediaProgress>(localMediaProgressId)?.let {
|
Paper.book("localMediaProgress").read<LocalMediaProgress>(localMediaProgressId)?.let {
|
||||||
mediaProgress.add(it)
|
mediaProgress.add(it)
|
||||||
|
@ -126,14 +126,14 @@ class DbManager {
|
||||||
|
|
||||||
// Make sure all local file ids still exist
|
// Make sure all local file ids still exist
|
||||||
fun cleanLocalLibraryItems() {
|
fun cleanLocalLibraryItems() {
|
||||||
var localLibraryItems = getLocalLibraryItems()
|
val localLibraryItems = getLocalLibraryItems()
|
||||||
|
|
||||||
localLibraryItems.forEach { lli ->
|
localLibraryItems.forEach { lli ->
|
||||||
var hasUpates = false
|
var hasUpates = false
|
||||||
|
|
||||||
// Check local files
|
// Check local files
|
||||||
lli.localFiles = lli.localFiles.filter { localFile ->
|
lli.localFiles = lli.localFiles.filter { localFile ->
|
||||||
var file = File(localFile.absolutePath)
|
val file = File(localFile.absolutePath)
|
||||||
if (!file.exists()) {
|
if (!file.exists()) {
|
||||||
Log.d(tag, "cleanLocalLibraryItems: Local file ${localFile.absolutePath} was removed from library item ${lli.media.metadata.title}")
|
Log.d(tag, "cleanLocalLibraryItems: Local file ${localFile.absolutePath} was removed from library item ${lli.media.metadata.title}")
|
||||||
hasUpates = true
|
hasUpates = true
|
||||||
|
@ -143,7 +143,7 @@ class DbManager {
|
||||||
|
|
||||||
// Check audio tracks and episodes
|
// Check audio tracks and episodes
|
||||||
if (lli.isPodcast) {
|
if (lli.isPodcast) {
|
||||||
var podcast = lli.media as Podcast
|
val podcast = lli.media as Podcast
|
||||||
podcast.episodes = podcast.episodes?.filter { ep ->
|
podcast.episodes = podcast.episodes?.filter { ep ->
|
||||||
if (lli.localFiles.find { lf -> lf.id == ep.audioTrack?.localFileId } == null) {
|
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}")
|
Log.d(tag, "cleanLocalLibraryItems: Podcast episode ${ep.title} was removed from library item ${lli.media.metadata.title}")
|
||||||
|
@ -152,7 +152,7 @@ class DbManager {
|
||||||
ep.audioTrack != null && lli.localFiles.find { lf -> lf.id == ep.audioTrack?.localFileId } != null
|
ep.audioTrack != null && lli.localFiles.find { lf -> lf.id == ep.audioTrack?.localFileId } != null
|
||||||
} as MutableList<PodcastEpisode>
|
} as MutableList<PodcastEpisode>
|
||||||
} else {
|
} else {
|
||||||
var book = lli.media as Book
|
val book = lli.media as Book
|
||||||
book.tracks = book.tracks?.filter { track ->
|
book.tracks = book.tracks?.filter { track ->
|
||||||
if (lli.localFiles.find { lf -> lf.id == track.localFileId } == null) {
|
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}")
|
Log.d(tag, "cleanLocalLibraryItems: Audio track ${track.title} was removed from library item ${lli.media.metadata.title}")
|
||||||
|
@ -164,7 +164,7 @@ class DbManager {
|
||||||
|
|
||||||
// Check cover still there
|
// Check cover still there
|
||||||
lli.coverAbsolutePath?.let {
|
lli.coverAbsolutePath?.let {
|
||||||
var coverFile = File(it)
|
val coverFile = File(it)
|
||||||
|
|
||||||
if (!coverFile.exists()) {
|
if (!coverFile.exists()) {
|
||||||
Log.d(tag, "cleanLocalLibraryItems: Cover $it was removed from library item ${lli.media.metadata.title}")
|
Log.d(tag, "cleanLocalLibraryItems: Cover $it was removed from library item ${lli.media.metadata.title}")
|
||||||
|
@ -183,10 +183,10 @@ class DbManager {
|
||||||
|
|
||||||
// Remove any local media progress where the local media item is not found
|
// Remove any local media progress where the local media item is not found
|
||||||
fun cleanLocalMediaProgress() {
|
fun cleanLocalMediaProgress() {
|
||||||
var localMediaProgress = getAllLocalMediaProgress()
|
val localMediaProgress = getAllLocalMediaProgress()
|
||||||
var localLibraryItems = getLocalLibraryItems()
|
val localLibraryItems = getLocalLibraryItems()
|
||||||
localMediaProgress.forEach {
|
localMediaProgress.forEach {
|
||||||
var matchingLLI = localLibraryItems.find { lli -> lli.id == it.localLibraryItemId }
|
val matchingLLI = localLibraryItems.find { lli -> lli.id == it.localLibraryItemId }
|
||||||
if (matchingLLI == null) {
|
if (matchingLLI == null) {
|
||||||
Log.d(tag, "cleanLocalMediaProgress: No matching local library item for local media progress ${it.id} - removing")
|
Log.d(tag, "cleanLocalMediaProgress: No matching local library item for local media progress ${it.id} - removing")
|
||||||
Paper.book("localMediaProgress").delete(it.id)
|
Paper.book("localMediaProgress").delete(it.id)
|
||||||
|
@ -195,8 +195,8 @@ class DbManager {
|
||||||
Log.d(tag, "cleanLocalMediaProgress: Podcast media progress has no episode id - removing")
|
Log.d(tag, "cleanLocalMediaProgress: Podcast media progress has no episode id - removing")
|
||||||
Paper.book("localMediaProgress").delete(it.id)
|
Paper.book("localMediaProgress").delete(it.id)
|
||||||
} else {
|
} else {
|
||||||
var podcast = matchingLLI.media as Podcast
|
val podcast = matchingLLI.media as Podcast
|
||||||
var matchingLEp = podcast.episodes?.find { ep -> ep.id == it.localEpisodeId }
|
val matchingLEp = podcast.episodes?.find { ep -> ep.id == it.localEpisodeId }
|
||||||
if (matchingLEp == null) {
|
if (matchingLEp == null) {
|
||||||
Log.d(tag, "cleanLocalMediaProgress: Podcast media progress for episode ${it.localEpisodeId} not found - removing")
|
Log.d(tag, "cleanLocalMediaProgress: Podcast media progress for episode ${it.localEpisodeId} not found - removing")
|
||||||
Paper.book("localMediaProgress").delete(it.id)
|
Paper.book("localMediaProgress").delete(it.id)
|
||||||
|
@ -212,15 +212,4 @@ class DbManager {
|
||||||
fun getLocalPlaybackSession(playbackSessionId:String):PlaybackSession? {
|
fun getLocalPlaybackSession(playbackSessionId:String):PlaybackSession? {
|
||||||
return Paper.book("localPlaybackSession").read(playbackSessionId)
|
return Paper.book("localPlaybackSession").read(playbackSessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveObject(db:String, key:String, value:JSONObject) {
|
|
||||||
Log.d(tag, "Saving Object $key ${value.toString()}")
|
|
||||||
Paper.book(db).write(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadObject(db:String, key:String):JSONObject? {
|
|
||||||
var json: JSONObject? = Paper.book(db).read(key)
|
|
||||||
Log.d(tag, "Loaded Object $key $json")
|
|
||||||
return json
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -251,7 +251,7 @@ class FolderScanner(var ctx: Context) {
|
||||||
Log.d(tag, "scanDownloadItem starting for ${downloadItem.itemFolderPath} | ${df.uri} | Item Folder Id:$itemFolderId | LLI Id:$localLibraryItemId")
|
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/*", "video/mp4"))
|
||||||
Log.d(tag, "scanDownloadItem ${filesFound.size} files found in ${downloadItem.itemFolderPath}")
|
Log.d(tag, "scanDownloadItem ${filesFound.size} files found in ${downloadItem.itemFolderPath}")
|
||||||
|
|
||||||
var localLibraryItem:LocalLibraryItem? = null
|
var localLibraryItem:LocalLibraryItem? = null
|
||||||
|
@ -349,7 +349,7 @@ class FolderScanner(var ctx: Context) {
|
||||||
var wasUpdated = false
|
var wasUpdated = false
|
||||||
|
|
||||||
// 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/*", "video/mp4"))
|
||||||
Log.d(tag, "scanLocalLibraryItem ${filesFound.size} files found in ${localLibraryItem.absolutePath}")
|
Log.d(tag, "scanLocalLibraryItem ${filesFound.size} files found in ${localLibraryItem.absolutePath}")
|
||||||
|
|
||||||
filesFound.forEach {
|
filesFound.forEach {
|
||||||
|
|
|
@ -30,7 +30,7 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
|
||||||
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
|
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
|
||||||
Log.d(tag, "About to prepare player with ${it.displayTitle}")
|
Log.d(tag, "About to prepare player with ${it.displayTitle}")
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post() {
|
||||||
playerNotificationService.preparePlayer(it,true)
|
playerNotificationService.preparePlayer(it,true,null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
|
||||||
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
|
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
|
||||||
Log.d(tag, "About to prepare player with ${it.displayTitle}")
|
Log.d(tag, "About to prepare player with ${it.displayTitle}")
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post() {
|
||||||
playerNotificationService.preparePlayer(it,true)
|
playerNotificationService.preparePlayer(it,true,null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi
|
||||||
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
|
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
|
||||||
Log.d(tag, "About to prepare player with ${it.displayTitle}")
|
Log.d(tag, "About to prepare player with ${it.displayTitle}")
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post() {
|
||||||
playerNotificationService.preparePlayer(it,true)
|
playerNotificationService.preparePlayer(it,true,null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ class MediaSessionPlaybackPreparer(var playerNotificationService:PlayerNotificat
|
||||||
playerNotificationService.mediaManager.getFirstItem()?.let { li ->
|
playerNotificationService.mediaManager.getFirstItem()?.let { li ->
|
||||||
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
|
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post() {
|
||||||
playerNotificationService.preparePlayer(it,playWhenReady)
|
playerNotificationService.preparePlayer(it,playWhenReady,null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,7 +46,7 @@ class MediaSessionPlaybackPreparer(var playerNotificationService:PlayerNotificat
|
||||||
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
|
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
|
||||||
Log.d(tag, "About to prepare player with ${it.displayTitle}")
|
Log.d(tag, "About to prepare player with ${it.displayTitle}")
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post() {
|
||||||
playerNotificationService.preparePlayer(it,playWhenReady)
|
playerNotificationService.preparePlayer(it,playWhenReady,null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ class MediaSessionPlaybackPreparer(var playerNotificationService:PlayerNotificat
|
||||||
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
|
playerNotificationService.mediaManager.play(li, playerNotificationService.getMediaPlayer()) {
|
||||||
Log.d(tag, "About to prepare player with ${it.displayTitle}")
|
Log.d(tag, "About to prepare player with ${it.displayTitle}")
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post() {
|
||||||
playerNotificationService.preparePlayer(it,playWhenReady)
|
playerNotificationService.preparePlayer(it,playWhenReady,null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ import android.hardware.SensorManager
|
||||||
import android.os.*
|
import android.os.*
|
||||||
import android.support.v4.media.MediaBrowserCompat
|
import android.support.v4.media.MediaBrowserCompat
|
||||||
import android.support.v4.media.MediaDescriptionCompat
|
import android.support.v4.media.MediaDescriptionCompat
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
|
||||||
import android.support.v4.media.session.MediaControllerCompat
|
import android.support.v4.media.session.MediaControllerCompat
|
||||||
import android.support.v4.media.session.MediaSessionCompat
|
import android.support.v4.media.session.MediaSessionCompat
|
||||||
import android.support.v4.media.session.PlaybackStateCompat
|
import android.support.v4.media.session.PlaybackStateCompat
|
||||||
|
@ -31,7 +30,6 @@ import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
||||||
import com.google.android.exoplayer2.source.hls.HlsMediaSource
|
import com.google.android.exoplayer2.source.hls.HlsMediaSource
|
||||||
import com.google.android.exoplayer2.ui.PlayerNotificationManager
|
import com.google.android.exoplayer2.ui.PlayerNotificationManager
|
||||||
import com.google.android.exoplayer2.upstream.*
|
import com.google.android.exoplayer2.upstream.*
|
||||||
import io.paperdb.Paper
|
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import kotlin.concurrent.schedule
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
|
@ -82,8 +80,9 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
private var channelName = "Audiobookshelf Channel"
|
private var channelName = "Audiobookshelf Channel"
|
||||||
|
|
||||||
private var currentPlaybackSession:PlaybackSession? = null
|
private var currentPlaybackSession:PlaybackSession? = null
|
||||||
|
private var initialPlaybackRate:Float? = null
|
||||||
|
|
||||||
var isAndroidAuto = false
|
private var isAndroidAuto = false
|
||||||
|
|
||||||
// The following are used for the shake detection
|
// The following are used for the shake detection
|
||||||
private var isShakeSensorRegistered:Boolean = false
|
private var isShakeSensorRegistered:Boolean = false
|
||||||
|
@ -164,7 +163,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
ctx = this
|
ctx = this
|
||||||
|
|
||||||
// Initialize player
|
// Initialize player
|
||||||
var customLoadControl:LoadControl = DefaultLoadControl.Builder().setBufferDurationsMs(
|
val customLoadControl:LoadControl = DefaultLoadControl.Builder().setBufferDurationsMs(
|
||||||
1000 * 20, // 20s min buffer
|
1000 * 20, // 20s min buffer
|
||||||
1000 * 45, // 45s max buffer
|
1000 * 45, // 45s max buffer
|
||||||
1000 * 5, // 5s playback start
|
1000 * 5, // 5s playback start
|
||||||
|
@ -178,7 +177,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
.build()
|
.build()
|
||||||
mPlayer.setHandleAudioBecomingNoisy(true)
|
mPlayer.setHandleAudioBecomingNoisy(true)
|
||||||
mPlayer.addListener(PlayerListener(this))
|
mPlayer.addListener(PlayerListener(this))
|
||||||
var audioAttributes:AudioAttributes = AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).setContentType(C.CONTENT_TYPE_SPEECH).build()
|
val audioAttributes:AudioAttributes = AudioAttributes.Builder().setUsage(C.USAGE_MEDIA).setContentType(C.CONTENT_TYPE_SPEECH).build()
|
||||||
mPlayer.setAudioAttributes(audioAttributes, true)
|
mPlayer.setAudioAttributes(audioAttributes, true)
|
||||||
|
|
||||||
currentPlayer = mPlayer
|
currentPlayer = mPlayer
|
||||||
|
@ -205,7 +204,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
|
|
||||||
val sessionActivityPendingIntent =
|
val sessionActivityPendingIntent =
|
||||||
packageManager?.getLaunchIntentForPackage(packageName)?.let { sessionIntent ->
|
packageManager?.getLaunchIntentForPackage(packageName)?.let { sessionIntent ->
|
||||||
PendingIntent.getActivity(this, 0, sessionIntent, 0)
|
PendingIntent.getActivity(this, 0, sessionIntent, PendingIntent.FLAG_IMMUTABLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
mediaSession = MediaSessionCompat(this, tag)
|
mediaSession = MediaSessionCompat(this, tag)
|
||||||
|
@ -275,7 +274,10 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
/*
|
/*
|
||||||
User callable methods
|
User callable methods
|
||||||
*/
|
*/
|
||||||
fun preparePlayer(playbackSession: PlaybackSession, playWhenReady:Boolean) {
|
fun preparePlayer(playbackSession: PlaybackSession, playWhenReady:Boolean, playbackRate:Float?) {
|
||||||
|
val playbackRateToUse = playbackRate ?: initialPlaybackRate ?: 1f
|
||||||
|
initialPlaybackRate = playbackRate
|
||||||
|
|
||||||
playbackSession.mediaPlayer = getMediaPlayer()
|
playbackSession.mediaPlayer = getMediaPlayer()
|
||||||
|
|
||||||
if (playbackSession.mediaPlayer == "cast-player" && playbackSession.isLocal) {
|
if (playbackSession.mediaPlayer == "cast-player" && playbackSession.isLocal) {
|
||||||
|
@ -296,9 +298,9 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
|
|
||||||
clientEventEmitter?.onPlaybackSession(playbackSession)
|
clientEventEmitter?.onPlaybackSession(playbackSession)
|
||||||
|
|
||||||
var metadata = playbackSession.getMediaMetadataCompat()
|
val metadata = playbackSession.getMediaMetadataCompat()
|
||||||
mediaSession.setMetadata(metadata)
|
mediaSession.setMetadata(metadata)
|
||||||
var mediaItems = playbackSession.getMediaItems()
|
val mediaItems = playbackSession.getMediaItems()
|
||||||
|
|
||||||
if (mediaItems.isEmpty()) {
|
if (mediaItems.isEmpty()) {
|
||||||
Log.e(tag, "Invalid playback session no media items to play")
|
Log.e(tag, "Invalid playback session no media items to play")
|
||||||
|
@ -307,20 +309,20 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mPlayer == currentPlayer) {
|
if (mPlayer == currentPlayer) {
|
||||||
var mediaSource:MediaSource
|
val mediaSource:MediaSource
|
||||||
|
|
||||||
if (playbackSession.isLocal) {
|
if (playbackSession.isLocal) {
|
||||||
Log.d(tag, "Playing Local Item")
|
Log.d(tag, "Playing Local Item")
|
||||||
var dataSourceFactory = DefaultDataSource.Factory(ctx)
|
val dataSourceFactory = DefaultDataSource.Factory(ctx)
|
||||||
mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItems[0])
|
mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItems[0])
|
||||||
} else if (!playbackSession.isHLS) {
|
} else if (!playbackSession.isHLS) {
|
||||||
Log.d(tag, "Direct Playing Item")
|
Log.d(tag, "Direct Playing Item")
|
||||||
var dataSourceFactory = DefaultHttpDataSource.Factory()
|
val dataSourceFactory = DefaultHttpDataSource.Factory()
|
||||||
dataSourceFactory.setUserAgent(channelId)
|
dataSourceFactory.setUserAgent(channelId)
|
||||||
mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItems[0])
|
mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItems[0])
|
||||||
} else {
|
} else {
|
||||||
Log.d(tag, "Playing HLS Item")
|
Log.d(tag, "Playing HLS Item")
|
||||||
var dataSourceFactory = DefaultHttpDataSource.Factory()
|
val dataSourceFactory = DefaultHttpDataSource.Factory()
|
||||||
dataSourceFactory.setUserAgent(channelId)
|
dataSourceFactory.setUserAgent(channelId)
|
||||||
dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${DeviceManager.token}"))
|
dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${DeviceManager.token}"))
|
||||||
mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItems[0])
|
mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItems[0])
|
||||||
|
@ -333,8 +335,8 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
currentPlayer.addMediaItems(mediaItems.subList(1, mediaItems.size))
|
currentPlayer.addMediaItems(mediaItems.subList(1, mediaItems.size))
|
||||||
Log.d(tag, "currentPlayer total media items ${currentPlayer.mediaItemCount}")
|
Log.d(tag, "currentPlayer total media items ${currentPlayer.mediaItemCount}")
|
||||||
|
|
||||||
var currentTrackIndex = playbackSession.getCurrentTrackIndex()
|
val currentTrackIndex = playbackSession.getCurrentTrackIndex()
|
||||||
var currentTrackTime = playbackSession.getCurrentTrackTimeMs()
|
val currentTrackTime = playbackSession.getCurrentTrackTimeMs()
|
||||||
Log.d(tag, "currentPlayer current track index $currentTrackIndex & current track time $currentTrackTime")
|
Log.d(tag, "currentPlayer current track index $currentTrackIndex & current track time $currentTrackTime")
|
||||||
currentPlayer.seekTo(currentTrackIndex, currentTrackTime)
|
currentPlayer.seekTo(currentTrackIndex, currentTrackTime)
|
||||||
} else {
|
} else {
|
||||||
|
@ -343,16 +345,15 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
|
|
||||||
Log.d(tag, "Prepare complete for session ${currentPlaybackSession?.displayTitle} | ${currentPlayer.mediaItemCount}")
|
Log.d(tag, "Prepare complete for session ${currentPlaybackSession?.displayTitle} | ${currentPlayer.mediaItemCount}")
|
||||||
currentPlayer.playWhenReady = playWhenReady
|
currentPlayer.playWhenReady = playWhenReady
|
||||||
currentPlayer.setPlaybackSpeed(1f) // TODO: Playback speed should come from settings
|
currentPlayer.setPlaybackSpeed(playbackRateToUse)
|
||||||
currentPlayer.prepare()
|
currentPlayer.prepare()
|
||||||
|
|
||||||
} else if (castPlayer != null) {
|
} else if (castPlayer != null) {
|
||||||
var currentTrackIndex = playbackSession.getCurrentTrackIndex()
|
val currentTrackIndex = playbackSession.getCurrentTrackIndex()
|
||||||
var currentTrackTime = playbackSession.getCurrentTrackTimeMs()
|
val currentTrackTime = playbackSession.getCurrentTrackTimeMs()
|
||||||
var mediaType = playbackSession.mediaType
|
val mediaType = playbackSession.mediaType
|
||||||
Log.d(tag, "Loading cast player $currentTrackIndex $currentTrackTime $mediaType")
|
Log.d(tag, "Loading cast player $currentTrackIndex $currentTrackTime $mediaType")
|
||||||
|
|
||||||
castPlayer?.load(mediaItems, currentTrackIndex, currentTrackTime, playWhenReady, 1f, mediaType)
|
castPlayer?.load(mediaItems, currentTrackIndex, currentTrackTime, playWhenReady, playbackRateToUse, mediaType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,14 +361,14 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
// On error and was attempting to direct play - fallback to transcode
|
// On error and was attempting to direct play - fallback to transcode
|
||||||
currentPlaybackSession?.let { playbackSession ->
|
currentPlaybackSession?.let { playbackSession ->
|
||||||
if (playbackSession.isDirectPlay) {
|
if (playbackSession.isDirectPlay) {
|
||||||
var mediaPlayer = getMediaPlayer()
|
val mediaPlayer = getMediaPlayer()
|
||||||
Log.d(tag, "Fallback to transcode $mediaPlayer")
|
Log.d(tag, "Fallback to transcode $mediaPlayer")
|
||||||
|
|
||||||
var libraryItemId = playbackSession.libraryItemId ?: "" // Must be true since direct play
|
val libraryItemId = playbackSession.libraryItemId ?: "" // Must be true since direct play
|
||||||
var episodeId = playbackSession.episodeId
|
val episodeId = playbackSession.episodeId
|
||||||
apiHandler.playLibraryItem(libraryItemId, episodeId, true, mediaPlayer) {
|
apiHandler.playLibraryItem(libraryItemId, episodeId, true, mediaPlayer) {
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post() {
|
||||||
preparePlayer(it, true)
|
preparePlayer(it, true, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -378,7 +379,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun switchToPlayer(useCastPlayer: Boolean) {
|
fun switchToPlayer(useCastPlayer: Boolean) {
|
||||||
var wasPlaying = currentPlayer.isPlaying
|
val wasPlaying = currentPlayer.isPlaying
|
||||||
if (useCastPlayer) {
|
if (useCastPlayer) {
|
||||||
if (currentPlayer == castPlayer) {
|
if (currentPlayer == castPlayer) {
|
||||||
Log.d(tag, "switchToPlayer: Already using Cast Player " + castPlayer?.deviceInfo)
|
Log.d(tag, "switchToPlayer: Already using Cast Player " + castPlayer?.deviceInfo)
|
||||||
|
@ -420,7 +421,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
if (wasPlaying) { // media is paused when switching players
|
if (wasPlaying) { // media is paused when switching players
|
||||||
clientEventEmitter?.onPlayingUpdate(false)
|
clientEventEmitter?.onPlayingUpdate(false)
|
||||||
}
|
}
|
||||||
preparePlayer(it, false)
|
preparePlayer(it, false, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -499,8 +500,8 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
Log.d(tag, "seekPlayer mediaCount = ${currentPlayer.mediaItemCount} | $time")
|
Log.d(tag, "seekPlayer mediaCount = ${currentPlayer.mediaItemCount} | $time")
|
||||||
if (currentPlayer.mediaItemCount > 1) {
|
if (currentPlayer.mediaItemCount > 1) {
|
||||||
currentPlaybackSession?.currentTime = time / 1000.0
|
currentPlaybackSession?.currentTime = time / 1000.0
|
||||||
var newWindowIndex = currentPlaybackSession?.getCurrentTrackIndex() ?: 0
|
val newWindowIndex = currentPlaybackSession?.getCurrentTrackIndex() ?: 0
|
||||||
var newTimeOffset = currentPlaybackSession?.getCurrentTrackTimeMs() ?: 0
|
val newTimeOffset = currentPlaybackSession?.getCurrentTrackTimeMs() ?: 0
|
||||||
currentPlayer.seekTo(newWindowIndex, newTimeOffset)
|
currentPlayer.seekTo(newWindowIndex, newTimeOffset)
|
||||||
} else {
|
} else {
|
||||||
currentPlayer.seekTo(time)
|
currentPlayer.seekTo(time)
|
||||||
|
@ -528,7 +529,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun sendClientMetadata(playerState: PlayerState) {
|
fun sendClientMetadata(playerState: PlayerState) {
|
||||||
var duration = currentPlaybackSession?.getTotalDuration() ?: 0.0
|
val duration = currentPlaybackSession?.getTotalDuration() ?: 0.0
|
||||||
clientEventEmitter?.onMetadata(PlaybackMetadata(duration, getCurrentTimeSeconds(), playerState))
|
clientEventEmitter?.onMetadata(PlaybackMetadata(duration, getCurrentTimeSeconds(), playerState))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -593,7 +594,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
override fun onLoadChildren(parentMediaId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
|
override fun onLoadChildren(parentMediaId: String, result: Result<MutableList<MediaBrowserCompat.MediaItem>>) {
|
||||||
Log.d(tag, "ON LOAD CHILDREN $parentMediaId")
|
Log.d(tag, "ON LOAD CHILDREN $parentMediaId")
|
||||||
|
|
||||||
var flag = if (parentMediaId == AUTO_MEDIA_ROOT) MediaBrowserCompat.MediaItem.FLAG_BROWSABLE else MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
|
val flag = if (parentMediaId == AUTO_MEDIA_ROOT) MediaBrowserCompat.MediaItem.FLAG_BROWSABLE else MediaBrowserCompat.MediaItem.FLAG_PLAYABLE
|
||||||
|
|
||||||
result.detach()
|
result.detach()
|
||||||
|
|
||||||
|
@ -652,7 +653,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
shakeSensorUnregisterTask?.cancel()
|
shakeSensorUnregisterTask?.cancel()
|
||||||
|
|
||||||
Log.d(tag, "Registering shake SENSOR ${mAccelerometer?.isWakeUpSensor}")
|
Log.d(tag, "Registering shake SENSOR ${mAccelerometer?.isWakeUpSensor}")
|
||||||
var success = mSensorManager!!.registerListener(
|
val success = mSensorManager!!.registerListener(
|
||||||
mShakeDetector,
|
mShakeDetector,
|
||||||
mAccelerometer,
|
mAccelerometer,
|
||||||
SensorManager.SENSOR_DELAY_UI
|
SensorManager.SENSOR_DELAY_UI
|
||||||
|
|
|
@ -23,8 +23,8 @@ class AbsAudioPlayer : Plugin() {
|
||||||
private val tag = "AbsAudioPlayer"
|
private val tag = "AbsAudioPlayer"
|
||||||
var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||||
|
|
||||||
lateinit var mainActivity: MainActivity
|
private lateinit var mainActivity: MainActivity
|
||||||
lateinit var apiHandler:ApiHandler
|
private lateinit var apiHandler:ApiHandler
|
||||||
lateinit var castManager:CastManager
|
lateinit var castManager:CastManager
|
||||||
|
|
||||||
lateinit var playerNotificationService: PlayerNotificationService
|
lateinit var playerNotificationService: PlayerNotificationService
|
||||||
|
@ -37,7 +37,7 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
initCastManager()
|
initCastManager()
|
||||||
|
|
||||||
var foregroundServiceReady : () -> Unit = {
|
val foregroundServiceReady : () -> Unit = {
|
||||||
playerNotificationService = mainActivity.foregroundService
|
playerNotificationService = mainActivity.foregroundService
|
||||||
|
|
||||||
playerNotificationService.clientEventEmitter = (object : PlayerNotificationService.ClientEventEmitter {
|
playerNotificationService.clientEventEmitter = (object : PlayerNotificationService.ClientEventEmitter {
|
||||||
|
@ -58,7 +58,7 @@ class AbsAudioPlayer : Plugin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPrepare(audiobookId: String, playWhenReady: Boolean) {
|
override fun onPrepare(audiobookId: String, playWhenReady: Boolean) {
|
||||||
var jsobj = JSObject()
|
val jsobj = JSObject()
|
||||||
jsobj.put("audiobookId", audiobookId)
|
jsobj.put("audiobookId", audiobookId)
|
||||||
jsobj.put("playWhenReady", playWhenReady)
|
jsobj.put("playWhenReady", playWhenReady)
|
||||||
notifyListeners("onPrepareMedia", jsobj)
|
notifyListeners("onPrepareMedia", jsobj)
|
||||||
|
@ -89,13 +89,13 @@ class AbsAudioPlayer : Plugin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun emit(evtName: String, value: Any) {
|
fun emit(evtName: String, value: Any) {
|
||||||
var ret = JSObject()
|
val ret = JSObject()
|
||||||
ret.put("value", value)
|
ret.put("value", value)
|
||||||
notifyListeners(evtName, ret)
|
notifyListeners(evtName, ret)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun initCastManager() {
|
private fun initCastManager() {
|
||||||
var connListener = object: CastManager.ChromecastListener() {
|
val connListener = object: CastManager.ChromecastListener() {
|
||||||
override fun onReceiverAvailableUpdate(available: Boolean) {
|
override fun onReceiverAvailableUpdate(available: Boolean) {
|
||||||
Log.d(tag, "ChromecastListener: CAST Receiver Update Available $available")
|
Log.d(tag, "ChromecastListener: CAST Receiver Update Available $available")
|
||||||
isCastAvailable = available
|
isCastAvailable = available
|
||||||
|
@ -141,9 +141,10 @@ class AbsAudioPlayer : Plugin() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var libraryItemId = call.getString("libraryItemId", "").toString()
|
val libraryItemId = call.getString("libraryItemId", "").toString()
|
||||||
var episodeId = call.getString("episodeId", "").toString()
|
val episodeId = call.getString("episodeId", "").toString()
|
||||||
var playWhenReady = call.getBoolean("playWhenReady") == true
|
val playWhenReady = call.getBoolean("playWhenReady") == true
|
||||||
|
var playbackRate = call.getFloat("playbackRate",1f) ?: 1f
|
||||||
|
|
||||||
if (libraryItemId.isEmpty()) {
|
if (libraryItemId.isEmpty()) {
|
||||||
Log.e(tag, "Invalid call to play library item no library item id")
|
Log.e(tag, "Invalid call to play library item no library item id")
|
||||||
|
@ -153,8 +154,8 @@ 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
|
var episode: PodcastEpisode? = null
|
||||||
if (!episodeId.isNullOrEmpty()) {
|
if (episodeId.isNotEmpty()) {
|
||||||
var podcastMedia = it.media as Podcast
|
val podcastMedia = it.media as Podcast
|
||||||
episode = podcastMedia.episodes?.find { ep -> ep.id == episodeId }
|
episode = podcastMedia.episodes?.find { ep -> ep.id == episodeId }
|
||||||
if (episode == null) {
|
if (episode == null) {
|
||||||
Log.e(tag, "prepareLibraryItem: Podcast episode not found $episodeId")
|
Log.e(tag, "prepareLibraryItem: Podcast episode not found $episodeId")
|
||||||
|
@ -162,21 +163,21 @@ class AbsAudioPlayer : Plugin() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post {
|
||||||
Log.d(tag, "prepareLibraryItem: Preparing Local Media item ${jacksonMapper.writeValueAsString(it)}")
|
Log.d(tag, "prepareLibraryItem: Preparing Local Media item ${jacksonMapper.writeValueAsString(it)}")
|
||||||
var playbackSession = it.getPlaybackSession(episode)
|
val playbackSession = it.getPlaybackSession(episode)
|
||||||
playerNotificationService.preparePlayer(playbackSession, playWhenReady)
|
playerNotificationService.preparePlayer(playbackSession, playWhenReady, playbackRate)
|
||||||
}
|
}
|
||||||
return call.resolve(JSObject())
|
return call.resolve(JSObject())
|
||||||
}
|
}
|
||||||
} else { // Play library item from server
|
} else { // Play library item from server
|
||||||
var mediaPlayer = playerNotificationService.getMediaPlayer()
|
val mediaPlayer = playerNotificationService.getMediaPlayer()
|
||||||
|
|
||||||
apiHandler.playLibraryItem(libraryItemId, episodeId, false, mediaPlayer) {
|
apiHandler.playLibraryItem(libraryItemId, episodeId, false, mediaPlayer) {
|
||||||
|
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post {
|
||||||
Log.d(tag, "Preparing Player TEST ${jacksonMapper.writeValueAsString(it)}")
|
Log.d(tag, "Preparing Player TEST ${jacksonMapper.writeValueAsString(it)}")
|
||||||
playerNotificationService.preparePlayer(it, playWhenReady)
|
playerNotificationService.preparePlayer(it, playWhenReady, playbackRate)
|
||||||
}
|
}
|
||||||
|
|
||||||
call.resolve(JSObject(jacksonMapper.writeValueAsString(it)))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(it)))
|
||||||
|
@ -186,9 +187,9 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun getCurrentTime(call: PluginCall) {
|
fun getCurrentTime(call: PluginCall) {
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post {
|
||||||
var currentTime = playerNotificationService.getCurrentTimeSeconds()
|
val currentTime = playerNotificationService.getCurrentTimeSeconds()
|
||||||
var bufferedTime = playerNotificationService.getBufferedTimeSeconds()
|
val bufferedTime = playerNotificationService.getBufferedTimeSeconds()
|
||||||
val ret = JSObject()
|
val ret = JSObject()
|
||||||
ret.put("value", currentTime)
|
ret.put("value", currentTime)
|
||||||
ret.put("bufferedTime", bufferedTime)
|
ret.put("bufferedTime", bufferedTime)
|
||||||
|
@ -198,7 +199,7 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun pausePlayer(call: PluginCall) {
|
fun pausePlayer(call: PluginCall) {
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post {
|
||||||
playerNotificationService.pause()
|
playerNotificationService.pause()
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
|
@ -206,7 +207,7 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun playPlayer(call: PluginCall) {
|
fun playPlayer(call: PluginCall) {
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post {
|
||||||
playerNotificationService.play()
|
playerNotificationService.play()
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
|
@ -214,16 +215,16 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun playPause(call: PluginCall) {
|
fun playPause(call: PluginCall) {
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post {
|
||||||
var playing = playerNotificationService.playPause()
|
val playing = playerNotificationService.playPause()
|
||||||
call.resolve(JSObject("{\"playing\":$playing}"))
|
call.resolve(JSObject("{\"playing\":$playing}"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun seek(call: PluginCall) {
|
fun seek(call: PluginCall) {
|
||||||
var time:Int = call.getInt("value", 0) ?: 0 // Value in seconds
|
val time:Int = call.getInt("value", 0) ?: 0 // Value in seconds
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post {
|
||||||
playerNotificationService.seekPlayer(time * 1000L) // convert to ms
|
playerNotificationService.seekPlayer(time * 1000L) // convert to ms
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
|
@ -231,8 +232,8 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun seekForward(call: PluginCall) {
|
fun seekForward(call: PluginCall) {
|
||||||
var amount:Int = call.getInt("value", 0) ?: 0
|
val amount:Int = call.getInt("value", 0) ?: 0
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post {
|
||||||
playerNotificationService.seekForward(amount * 1000L) // convert to ms
|
playerNotificationService.seekForward(amount * 1000L) // convert to ms
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
|
@ -240,8 +241,8 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun seekBackward(call: PluginCall) {
|
fun seekBackward(call: PluginCall) {
|
||||||
var amount:Int = call.getInt("value", 0) ?: 0 // Value in seconds
|
val amount:Int = call.getInt("value", 0) ?: 0 // Value in seconds
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post {
|
||||||
playerNotificationService.seekBackward(amount * 1000L) // convert to ms
|
playerNotificationService.seekBackward(amount * 1000L) // convert to ms
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
|
@ -249,9 +250,9 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun setPlaybackSpeed(call: PluginCall) {
|
fun setPlaybackSpeed(call: PluginCall) {
|
||||||
var playbackSpeed:Float = call.getFloat("value", 1.0f) ?: 1.0f
|
val playbackSpeed:Float = call.getFloat("value", 1.0f) ?: 1.0f
|
||||||
|
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post {
|
||||||
playerNotificationService.setPlaybackSpeed(playbackSpeed)
|
playerNotificationService.setPlaybackSpeed(playbackSpeed)
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
|
@ -259,7 +260,7 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun closePlayback(call: PluginCall) {
|
fun closePlayback(call: PluginCall) {
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post {
|
||||||
playerNotificationService.closePlayback()
|
playerNotificationService.closePlayback()
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
|
@ -267,11 +268,11 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun setSleepTimer(call: PluginCall) {
|
fun setSleepTimer(call: PluginCall) {
|
||||||
var time:Long = call.getString("time", "360000")!!.toLong()
|
val time:Long = call.getString("time", "360000")!!.toLong()
|
||||||
var isChapterTime:Boolean = call.getBoolean("isChapterTime", false) == true
|
val isChapterTime:Boolean = call.getBoolean("isChapterTime", false) == true
|
||||||
|
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post {
|
||||||
var success:Boolean = playerNotificationService.sleepTimerManager.setSleepTimer(time, isChapterTime)
|
val success:Boolean = playerNotificationService.sleepTimerManager.setSleepTimer(time, isChapterTime)
|
||||||
val ret = JSObject()
|
val ret = JSObject()
|
||||||
ret.put("success", success)
|
ret.put("success", success)
|
||||||
call.resolve(ret)
|
call.resolve(ret)
|
||||||
|
@ -280,7 +281,7 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun getSleepTimerTime(call: PluginCall) {
|
fun getSleepTimerTime(call: PluginCall) {
|
||||||
var time = playerNotificationService.sleepTimerManager.getSleepTimerTime()
|
val time = playerNotificationService.sleepTimerManager.getSleepTimerTime()
|
||||||
val ret = JSObject()
|
val ret = JSObject()
|
||||||
ret.put("value", time)
|
ret.put("value", time)
|
||||||
call.resolve(ret)
|
call.resolve(ret)
|
||||||
|
@ -288,9 +289,9 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun increaseSleepTime(call: PluginCall) {
|
fun increaseSleepTime(call: PluginCall) {
|
||||||
var time:Long = call.getString("time", "300000")!!.toLong()
|
val time:Long = call.getString("time", "300000")!!.toLong()
|
||||||
|
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post {
|
||||||
playerNotificationService.sleepTimerManager.increaseSleepTime(time)
|
playerNotificationService.sleepTimerManager.increaseSleepTime(time)
|
||||||
val ret = JSObject()
|
val ret = JSObject()
|
||||||
ret.put("success", true)
|
ret.put("success", true)
|
||||||
|
@ -300,9 +301,9 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun decreaseSleepTime(call: PluginCall) {
|
fun decreaseSleepTime(call: PluginCall) {
|
||||||
var time:Long = call.getString("time", "300000")!!.toLong()
|
val time:Long = call.getString("time", "300000")!!.toLong()
|
||||||
|
|
||||||
Handler(Looper.getMainLooper()).post() {
|
Handler(Looper.getMainLooper()).post {
|
||||||
playerNotificationService.sleepTimerManager.decreaseSleepTime(time)
|
playerNotificationService.sleepTimerManager.decreaseSleepTime(time)
|
||||||
val ret = JSObject()
|
val ret = JSObject()
|
||||||
ret.put("success", true)
|
ret.put("success", true)
|
||||||
|
@ -338,7 +339,7 @@ class AbsAudioPlayer : Plugin() {
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun getIsCastAvailable(call: PluginCall) {
|
fun getIsCastAvailable(call: PluginCall) {
|
||||||
var jsobj = JSObject()
|
val jsobj = JSObject()
|
||||||
jsobj.put("value", isCastAvailable)
|
jsobj.put("value", isCastAvailable)
|
||||||
call.resolve(jsobj)
|
call.resolve(jsobj)
|
||||||
}
|
}
|
||||||
|
|
|
@ -288,38 +288,4 @@ class AbsDatabase : Plugin() {
|
||||||
call.resolve()
|
call.resolve()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// Generic Webview calls to db
|
|
||||||
//
|
|
||||||
@PluginMethod
|
|
||||||
fun saveFromWebview(call: PluginCall) {
|
|
||||||
var db = call.getString("db", "").toString()
|
|
||||||
var key = call.getString("key", "").toString()
|
|
||||||
var value = call.getObject("value")
|
|
||||||
|
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
|
||||||
if (db == "" || key == "" || value == null) {
|
|
||||||
Log.d(tag, "saveFromWebview Invalid key/value")
|
|
||||||
} else {
|
|
||||||
var json = value as JSONObject
|
|
||||||
DeviceManager.dbManager.saveObject(db, key, json)
|
|
||||||
}
|
|
||||||
call.resolve()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PluginMethod
|
|
||||||
fun loadFromWebview(call:PluginCall) {
|
|
||||||
var db = call.getString("db", "").toString()
|
|
||||||
var key = call.getString("key", "").toString()
|
|
||||||
if (db == "" || key == "") {
|
|
||||||
Log.d(tag, "loadFromWebview Invalid Key")
|
|
||||||
call.resolve()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var json = DeviceManager.dbManager.loadObject(db, key)
|
|
||||||
var jsobj = JSObject.fromJSONObject(json)
|
|
||||||
call.resolve(jsobj)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,12 @@ import android.app.DownloadManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import com.anggrayudi.storage.callback.FileCallback
|
||||||
|
import com.anggrayudi.storage.file.*
|
||||||
|
import com.anggrayudi.storage.media.FileDescription
|
||||||
import com.audiobookshelf.app.MainActivity
|
import com.audiobookshelf.app.MainActivity
|
||||||
import com.audiobookshelf.app.data.*
|
import com.audiobookshelf.app.data.*
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
|
@ -37,19 +42,51 @@ class AbsDownloader : Plugin() {
|
||||||
data class DownloadItemPart(
|
data class DownloadItemPart(
|
||||||
val id: String,
|
val id: String,
|
||||||
val filename: String,
|
val filename: String,
|
||||||
val destinationPath:String,
|
val finalDestinationPath:String,
|
||||||
val itemTitle: String,
|
val itemTitle: String,
|
||||||
val serverPath: String,
|
val serverPath: String,
|
||||||
val localFolderName: String,
|
val localFolderName: String,
|
||||||
|
val localFolderUrl: String,
|
||||||
val localFolderId: String,
|
val localFolderId: String,
|
||||||
val audioTrack: AudioTrack?,
|
val audioTrack: AudioTrack?,
|
||||||
val episode:PodcastEpisode?,
|
val episode:PodcastEpisode?,
|
||||||
var completed:Boolean,
|
var completed:Boolean,
|
||||||
|
var moved:Boolean,
|
||||||
|
var failed:Boolean,
|
||||||
@JsonIgnore val uri: Uri,
|
@JsonIgnore val uri: Uri,
|
||||||
@JsonIgnore val destinationUri: Uri,
|
@JsonIgnore val destinationUri: Uri,
|
||||||
|
@JsonIgnore val finalDestinationUri: Uri,
|
||||||
var downloadId: Long?,
|
var downloadId: Long?,
|
||||||
var progress: Long
|
var progress: Long
|
||||||
) {
|
) {
|
||||||
|
companion object {
|
||||||
|
fun make(filename:String, destinationFile:File, finalDestinationFile:File, itemTitle:String, serverPath:String, localFolder:LocalFolder, audioTrack:AudioTrack?, episode:PodcastEpisode?) :DownloadItemPart {
|
||||||
|
val destinationUri = Uri.fromFile(destinationFile)
|
||||||
|
val finalDestinationUri = Uri.fromFile(finalDestinationFile)
|
||||||
|
val downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}")
|
||||||
|
Log.d("DownloadItemPart", "Audio File Destination Uri: $destinationUri | Final Destination Uri: $finalDestinationUri | Download URI $downloadUri")
|
||||||
|
return DownloadItemPart(
|
||||||
|
id = DeviceManager.getBase64Id(finalDestinationFile.absolutePath),
|
||||||
|
filename = filename, finalDestinationFile.absolutePath,
|
||||||
|
itemTitle = itemTitle,
|
||||||
|
serverPath = serverPath,
|
||||||
|
localFolderName = localFolder.name,
|
||||||
|
localFolderUrl = localFolder.contentUrl,
|
||||||
|
localFolderId = localFolder.id,
|
||||||
|
audioTrack = audioTrack,
|
||||||
|
episode = episode,
|
||||||
|
completed = false,
|
||||||
|
moved = false,
|
||||||
|
failed = false,
|
||||||
|
uri = downloadUri,
|
||||||
|
destinationUri = destinationUri,
|
||||||
|
finalDestinationUri = finalDestinationUri,
|
||||||
|
downloadId = null,
|
||||||
|
progress = 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getDownloadRequest(): DownloadManager.Request {
|
fun getDownloadRequest(): DownloadManager.Request {
|
||||||
val dlRequest = DownloadManager.Request(uri)
|
val dlRequest = DownloadManager.Request(uri)
|
||||||
|
@ -92,10 +129,10 @@ class AbsDownloader : Plugin() {
|
||||||
val libraryItemId = call.data.getString("libraryItemId").toString()
|
val libraryItemId = call.data.getString("libraryItemId").toString()
|
||||||
var episodeId = call.data.getString("episodeId").toString()
|
var episodeId = call.data.getString("episodeId").toString()
|
||||||
if (episodeId == "null") episodeId = ""
|
if (episodeId == "null") episodeId = ""
|
||||||
var localFolderId = call.data.getString("localFolderId").toString()
|
val localFolderId = call.data.getString("localFolderId").toString()
|
||||||
Log.d(tag, "Download library item $libraryItemId to folder $localFolderId / episode: $episodeId")
|
Log.d(tag, "Download library item $libraryItemId to folder $localFolderId / episode: $episodeId")
|
||||||
|
|
||||||
var downloadId = if (episodeId.isNullOrEmpty()) libraryItemId else "$libraryItemId-$episodeId"
|
val downloadId = if (episodeId.isEmpty()) libraryItemId else "$libraryItemId-$episodeId"
|
||||||
if (downloadQueue.find { it.id == downloadId } != null) {
|
if (downloadQueue.find { it.id == downloadId } != null) {
|
||||||
Log.d(tag, "Download already started for this media entity $downloadId")
|
Log.d(tag, "Download already started for this media entity $downloadId")
|
||||||
return call.resolve(JSObject("{\"error\":\"Download already started for this media entity\"}"))
|
return call.resolve(JSObject("{\"error\":\"Download already started for this media entity\"}"))
|
||||||
|
@ -104,15 +141,15 @@ class AbsDownloader : Plugin() {
|
||||||
apiHandler.getLibraryItem(libraryItemId) { libraryItem ->
|
apiHandler.getLibraryItem(libraryItemId) { libraryItem ->
|
||||||
Log.d(tag, "Got library item from server ${libraryItem.id}")
|
Log.d(tag, "Got library item from server ${libraryItem.id}")
|
||||||
|
|
||||||
var localFolder = DeviceManager.dbManager.getLocalFolder(localFolderId)
|
val localFolder = DeviceManager.dbManager.getLocalFolder(localFolderId)
|
||||||
if (localFolder != null) {
|
if (localFolder != null) {
|
||||||
|
|
||||||
if (!episodeId.isNullOrEmpty() && libraryItem.mediaType != "podcast") {
|
if (episodeId.isNotEmpty() && libraryItem.mediaType != "podcast") {
|
||||||
Log.e(tag, "Library item is not a podcast but episode was requested")
|
Log.e(tag, "Library item is not a podcast but episode was requested")
|
||||||
call.resolve(JSObject("{\"error\":\"Invalid library item not a podcast\"}"))
|
call.resolve(JSObject("{\"error\":\"Invalid library item not a podcast\"}"))
|
||||||
} else if (!episodeId.isNullOrEmpty()) {
|
} else if (episodeId.isNotEmpty()) {
|
||||||
var podcast = libraryItem.media as Podcast
|
val podcast = libraryItem.media as Podcast
|
||||||
var episode = podcast.episodes?.find { podcastEpisode ->
|
val episode = podcast.episodes?.find { podcastEpisode ->
|
||||||
podcastEpisode.id == episodeId
|
podcastEpisode.id == episodeId
|
||||||
}
|
}
|
||||||
if (episode == null) {
|
if (episode == null) {
|
||||||
|
@ -132,77 +169,70 @@ class AbsDownloader : Plugin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean folder path so it can be used in URL
|
// Clean folder path so it can be used in URL
|
||||||
fun cleanRelPath(relPath: String): String {
|
private fun cleanRelPath(relPath: String): String {
|
||||||
var cleanedRelPath = relPath.replace("\\", "/").replace("%", "%25").replace("#", "%23")
|
val cleanedRelPath = relPath.replace("\\", "/").replace("%", "%25").replace("#", "%23")
|
||||||
return if (cleanedRelPath.startsWith("/")) cleanedRelPath.substring(1) else cleanedRelPath
|
return if (cleanedRelPath.startsWith("/")) cleanedRelPath.substring(1) else cleanedRelPath
|
||||||
}
|
}
|
||||||
|
|
||||||
// Item filenames could be the same if they are in subfolders, this will make them unique
|
// Item filenames could be the same if they are in sub-folders, this will make them unique
|
||||||
fun getFilenameFromRelPath(relPath: String): String {
|
private fun getFilenameFromRelPath(relPath: String): String {
|
||||||
var cleanedRelPath = relPath.replace("\\", "_").replace("/", "_")
|
val cleanedRelPath = relPath.replace("\\", "_").replace("/", "_")
|
||||||
return if (cleanedRelPath.startsWith("_")) cleanedRelPath.substring(1) else cleanedRelPath
|
return if (cleanedRelPath.startsWith("_")) cleanedRelPath.substring(1) else cleanedRelPath
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startLibraryItemDownload(libraryItem: LibraryItem, localFolder: LocalFolder, episode:PodcastEpisode?) {
|
private fun startLibraryItemDownload(libraryItem: LibraryItem, localFolder: LocalFolder, episode:PodcastEpisode?) {
|
||||||
|
val tempFolderPath = mainActivity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
|
||||||
if (libraryItem.mediaType == "book") {
|
if (libraryItem.mediaType == "book") {
|
||||||
var bookTitle = libraryItem.media.metadata.title
|
val bookTitle = libraryItem.media.metadata.title
|
||||||
var tracks = libraryItem.media.getAudioTracks()
|
val tracks = libraryItem.media.getAudioTracks()
|
||||||
Log.d(tag, "Starting library item download with ${tracks.size} tracks")
|
Log.d(tag, "Starting library item download with ${tracks.size} tracks")
|
||||||
var itemFolderPath = localFolder.absolutePath + "/" + bookTitle
|
val itemFolderPath = localFolder.absolutePath + "/" + bookTitle
|
||||||
var downloadItem = DownloadItem(libraryItem.id, libraryItem.id, null,DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, libraryItem.media, mutableListOf())
|
val downloadItem = DownloadItem(libraryItem.id, libraryItem.id, null,DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, libraryItem.media, mutableListOf())
|
||||||
|
|
||||||
// Create download item part for each audio track
|
// Create download item part for each audio track
|
||||||
tracks.forEach { audioTrack ->
|
tracks.forEach { audioTrack ->
|
||||||
var serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioTrack.relPath)}"
|
val serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioTrack.relPath)}"
|
||||||
var destinationFilename = getFilenameFromRelPath(audioTrack.relPath)
|
val destinationFilename = getFilenameFromRelPath(audioTrack.relPath)
|
||||||
Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioTrack.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}")
|
Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioTrack.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}")
|
||||||
var destinationFile = File("$itemFolderPath/$destinationFilename")
|
|
||||||
|
|
||||||
if (destinationFile.exists()) {
|
val finalDestinationFile = File("$itemFolderPath/$destinationFilename")
|
||||||
Log.d(tag, "Audio file already exists, removing it from ${destinationFile.absolutePath}")
|
val destinationFile = File("$tempFolderPath/$destinationFilename")
|
||||||
destinationFile.delete()
|
|
||||||
|
if (finalDestinationFile.exists()) {
|
||||||
|
Log.d(tag, "Audio file already exists, removing it from ${finalDestinationFile.absolutePath}")
|
||||||
|
finalDestinationFile.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
var destinationUri = Uri.fromFile(destinationFile)
|
val downloadItemPart = DownloadItemPart.make(destinationFilename,destinationFile,finalDestinationFile,bookTitle,serverPath,localFolder,audioTrack,null)
|
||||||
var downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}")
|
|
||||||
Log.d(tag, "Audio File Destination Uri $destinationUri | Download URI $downloadUri")
|
|
||||||
var downloadItemPart = DownloadItemPart(DeviceManager.getBase64Id(destinationFile.absolutePath), destinationFilename, destinationFile.absolutePath, bookTitle, serverPath, localFolder.name, localFolder.id, audioTrack, null, false, downloadUri, destinationUri, null, 0)
|
|
||||||
|
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
|
|
||||||
var dlRequest = downloadItemPart.getDownloadRequest()
|
val dlRequest = downloadItemPart.getDownloadRequest()
|
||||||
var downloadId = downloadManager.enqueue(dlRequest)
|
val downloadId = downloadManager.enqueue(dlRequest)
|
||||||
downloadItemPart.downloadId = downloadId
|
downloadItemPart.downloadId = downloadId
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadItem.downloadItemParts.isNotEmpty()) {
|
if (downloadItem.downloadItemParts.isNotEmpty()) {
|
||||||
// Add cover download item
|
// Add cover download item
|
||||||
if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) {
|
if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) {
|
||||||
var serverPath = "/api/items/${libraryItem.id}/cover?format=jpeg"
|
val serverPath = "/api/items/${libraryItem.id}/cover?format=jpeg"
|
||||||
var destinationFilename = "cover.jpg"
|
val destinationFilename = "cover.jpg"
|
||||||
var destinationFile = File("$itemFolderPath/$destinationFilename")
|
val destinationFile = File("$tempFolderPath/$destinationFilename")
|
||||||
|
val finalDestinationFile = File("$itemFolderPath/$destinationFilename")
|
||||||
|
|
||||||
if (destinationFile.exists()) {
|
if (finalDestinationFile.exists()) {
|
||||||
Log.d(tag, "Cover already exists, removing it from ${destinationFile.absolutePath}")
|
Log.d(tag, "Cover already exists, removing it from ${finalDestinationFile.absolutePath}")
|
||||||
destinationFile.delete()
|
finalDestinationFile.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
var destinationUri = Uri.fromFile(destinationFile)
|
val downloadItemPart = DownloadItemPart.make(destinationFilename,destinationFile,finalDestinationFile,bookTitle,serverPath,localFolder,null,null)
|
||||||
var downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}&token=${DeviceManager.token}")
|
|
||||||
var downloadItemPart = DownloadItemPart(DeviceManager.getBase64Id(destinationFile.absolutePath), destinationFilename, destinationFile.absolutePath, bookTitle, serverPath, localFolder.name, localFolder.id, null,null, false, downloadUri, destinationUri, null, 0)
|
|
||||||
|
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
|
|
||||||
var dlRequest = downloadItemPart.getDownloadRequest()
|
val dlRequest = downloadItemPart.getDownloadRequest()
|
||||||
var downloadId = downloadManager.enqueue(dlRequest)
|
val downloadId = downloadManager.enqueue(dlRequest)
|
||||||
downloadItemPart.downloadId = downloadId
|
downloadItemPart.downloadId = downloadId
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Cannot create new text file here but can download here... ??
|
|
||||||
// var abmetadataFile = File(itemFolderPath, "abmetadata.abs")
|
|
||||||
// abmetadataFile.createNewFileIfPossible()
|
|
||||||
// abmetadataFile.writeText(getAbMetadataText(libraryItem))
|
|
||||||
|
|
||||||
downloadQueue.add(downloadItem)
|
downloadQueue.add(downloadItem)
|
||||||
startWatchingDownloads(downloadItem)
|
startWatchingDownloads(downloadItem)
|
||||||
DeviceManager.dbManager.saveDownloadItem(downloadItem)
|
DeviceManager.dbManager.saveDownloadItem(downloadItem)
|
||||||
|
@ -210,26 +240,25 @@ class AbsDownloader : Plugin() {
|
||||||
} else {
|
} else {
|
||||||
// Podcast episode download
|
// Podcast episode download
|
||||||
|
|
||||||
var podcastTitle = libraryItem.media.metadata.title
|
val podcastTitle = libraryItem.media.metadata.title
|
||||||
var audioTrack = episode?.audioTrack
|
val audioTrack = episode?.audioTrack
|
||||||
Log.d(tag, "Starting podcast episode download")
|
Log.d(tag, "Starting podcast episode download")
|
||||||
var itemFolderPath = localFolder.absolutePath + "/" + podcastTitle
|
val itemFolderPath = localFolder.absolutePath + "/" + podcastTitle
|
||||||
var downloadItemId = "${libraryItem.id}-${episode?.id}"
|
val downloadItemId = "${libraryItem.id}-${episode?.id}"
|
||||||
var downloadItem = DownloadItem(downloadItemId, libraryItem.id, episode?.id, DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, podcastTitle, libraryItem.media, mutableListOf())
|
val downloadItem = DownloadItem(downloadItemId, libraryItem.id, episode?.id, DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, podcastTitle, libraryItem.media, mutableListOf())
|
||||||
|
|
||||||
var serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioTrack?.relPath ?: "")}"
|
var serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioTrack?.relPath ?: "")}"
|
||||||
var destinationFilename = getFilenameFromRelPath(audioTrack?.relPath ?: "")
|
var destinationFilename = getFilenameFromRelPath(audioTrack?.relPath ?: "")
|
||||||
Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioTrack?.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}")
|
Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioTrack?.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}")
|
||||||
var destinationFile = File("$itemFolderPath/$destinationFilename")
|
|
||||||
if (destinationFile.exists()) {
|
var destinationFile = File("$tempFolderPath/$destinationFilename")
|
||||||
Log.d(tag, "Audio file already exists, removing it from ${destinationFile.absolutePath}")
|
var finalDestinationFile = File("$itemFolderPath/$destinationFilename")
|
||||||
destinationFile.delete()
|
if (finalDestinationFile.exists()) {
|
||||||
|
Log.d(tag, "Audio file already exists, removing it from ${finalDestinationFile.absolutePath}")
|
||||||
|
finalDestinationFile.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
var destinationUri = Uri.fromFile(destinationFile)
|
var downloadItemPart = DownloadItemPart.make(destinationFilename,destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,audioTrack,null)
|
||||||
var downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}")
|
|
||||||
Log.d(tag, "Audio File Destination Uri $destinationUri | Download URI $downloadUri")
|
|
||||||
var downloadItemPart = DownloadItemPart(DeviceManager.getBase64Id(destinationFile.absolutePath), destinationFilename, destinationFile.absolutePath, podcastTitle, serverPath, localFolder.name, localFolder.id, audioTrack, episode,false, downloadUri, destinationUri, null, 0)
|
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
|
|
||||||
var dlRequest = downloadItemPart.getDownloadRequest()
|
var dlRequest = downloadItemPart.getDownloadRequest()
|
||||||
|
@ -239,15 +268,14 @@ class AbsDownloader : Plugin() {
|
||||||
if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) {
|
if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) {
|
||||||
serverPath = "/api/items/${libraryItem.id}/cover?format=jpeg"
|
serverPath = "/api/items/${libraryItem.id}/cover?format=jpeg"
|
||||||
destinationFilename = "cover.jpg"
|
destinationFilename = "cover.jpg"
|
||||||
destinationFile = File("$itemFolderPath/$destinationFilename")
|
|
||||||
|
|
||||||
if (destinationFile.exists()) {
|
destinationFile = File("$tempFolderPath/$destinationFilename")
|
||||||
|
finalDestinationFile = File("$itemFolderPath/$destinationFilename")
|
||||||
|
|
||||||
|
if (finalDestinationFile.exists()) {
|
||||||
Log.d(tag, "Podcast cover already exists - not downloading cover again")
|
Log.d(tag, "Podcast cover already exists - not downloading cover again")
|
||||||
} else {
|
} else {
|
||||||
destinationUri = Uri.fromFile(destinationFile)
|
downloadItemPart = DownloadItemPart.make(destinationFilename,destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,audioTrack,null)
|
||||||
downloadUri = Uri.parse("${DeviceManager.serverAddress}${serverPath}&token=${DeviceManager.token}")
|
|
||||||
downloadItemPart = DownloadItemPart(DeviceManager.getBase64Id(destinationFile.absolutePath), destinationFilename, destinationFile.absolutePath, podcastTitle, serverPath, localFolder.name, localFolder.id, null,null, false, downloadUri, destinationUri, null, 0)
|
|
||||||
|
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
|
|
||||||
dlRequest = downloadItemPart.getDownloadRequest()
|
dlRequest = downloadItemPart.getDownloadRequest()
|
||||||
|
@ -264,8 +292,8 @@ class AbsDownloader : Plugin() {
|
||||||
|
|
||||||
fun startWatchingDownloads(downloadItem: DownloadItem) {
|
fun startWatchingDownloads(downloadItem: DownloadItem) {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
while (downloadItem.downloadItemParts.find { !it.completed } != null) { // While some item is not completed
|
while (downloadItem.downloadItemParts.find { !it.moved && !it.failed } != null) { // While some item is not completed
|
||||||
var numPartsBefore = downloadItem.downloadItemParts.size
|
val numPartsBefore = downloadItem.downloadItemParts.size
|
||||||
checkDownloads(downloadItem)
|
checkDownloads(downloadItem)
|
||||||
|
|
||||||
// Keep database updated as item parts finish downloading
|
// Keep database updated as item parts finish downloading
|
||||||
|
@ -278,13 +306,13 @@ class AbsDownloader : Plugin() {
|
||||||
delay(500)
|
delay(500)
|
||||||
}
|
}
|
||||||
|
|
||||||
var localLibraryItem = folderScanner.scanDownloadItem(downloadItem)
|
val localLibraryItem = folderScanner.scanDownloadItem(downloadItem)
|
||||||
DeviceManager.dbManager.removeDownloadItem(downloadItem.id)
|
DeviceManager.dbManager.removeDownloadItem(downloadItem.id)
|
||||||
downloadQueue.remove(downloadItem)
|
downloadQueue.remove(downloadItem)
|
||||||
|
|
||||||
Log.d(tag, "Item download complete ${downloadItem.itemTitle} | local library item id: ${localLibraryItem?.id} | Items remaining in Queue ${downloadQueue.size}")
|
Log.d(tag, "Item download complete ${downloadItem.itemTitle} | local library item id: ${localLibraryItem?.id} | Items remaining in Queue ${downloadQueue.size}")
|
||||||
|
|
||||||
var jsobj = JSObject()
|
val jsobj = JSObject()
|
||||||
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) {
|
||||||
|
@ -295,26 +323,58 @@ class AbsDownloader : Plugin() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkDownloads(downloadItem: DownloadItem) {
|
fun checkDownloads(downloadItem: DownloadItem) {
|
||||||
var itemParts = downloadItem.downloadItemParts.map { it }
|
val itemParts = downloadItem.downloadItemParts.map { it }
|
||||||
for (downloadItemPart in itemParts) {
|
for (downloadItemPart in itemParts) {
|
||||||
if (downloadItemPart.downloadId != null) {
|
if (downloadItemPart.downloadId != null) {
|
||||||
var dlid = downloadItemPart.downloadId!!
|
val dlid = downloadItemPart.downloadId!!
|
||||||
val query = DownloadManager.Query().setFilterById(dlid)
|
val query = DownloadManager.Query().setFilterById(dlid)
|
||||||
downloadManager.query(query).use {
|
downloadManager.query(query).use {
|
||||||
if (it.moveToFirst()) {
|
if (it.moveToFirst()) {
|
||||||
val totalBytes = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
|
val bytesColumnIndex = it.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
|
||||||
val downloadStatus = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_STATUS))
|
val statusColumnIndex = it.getColumnIndex(DownloadManager.COLUMN_STATUS)
|
||||||
val bytesDownloadedSoFar = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
|
val bytesDownloadedColumnIndex = it.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
|
||||||
|
|
||||||
|
val totalBytes = if (bytesColumnIndex >= 0) it.getInt(bytesColumnIndex) else 0
|
||||||
|
val downloadStatus = if (statusColumnIndex >= 0) it.getInt(statusColumnIndex) else 0
|
||||||
|
val bytesDownloadedSoFar = if (bytesDownloadedColumnIndex >= 0) it.getInt(bytesDownloadedColumnIndex) else 0
|
||||||
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} bytes $totalBytes | bytes dled $bytesDownloadedSoFar | downloadStatus $downloadStatus")
|
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} bytes $totalBytes | bytes dled $bytesDownloadedSoFar | downloadStatus $downloadStatus")
|
||||||
|
|
||||||
if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL) {
|
if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL) {
|
||||||
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Done")
|
// Once file download is complete move the file to the final destination
|
||||||
// downloadItem.downloadItemParts.remove(downloadItemPart)
|
if (!downloadItemPart.completed) {
|
||||||
downloadItemPart.completed = true
|
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Done")
|
||||||
|
downloadItemPart.completed = true
|
||||||
|
val file = DocumentFileCompat.fromUri(mainActivity, downloadItemPart.destinationUri)
|
||||||
|
Log.d(tag, "DOWNLOAD: Attempt move for file at destination ${downloadItemPart.destinationUri} | ${file?.getBasePath(mainActivity)}")
|
||||||
|
|
||||||
|
val fcb = object : FileCallback() {
|
||||||
|
override fun onPrepare() {
|
||||||
|
Log.d(tag, "DOWNLOAD: PREPARING MOVE FILE")
|
||||||
|
}
|
||||||
|
override fun onFailed(errorCode:ErrorCode) {
|
||||||
|
Log.e(tag, "DOWNLOAD: FAILED TO MOVE FILE $errorCode")
|
||||||
|
downloadItemPart.failed = true
|
||||||
|
file?.delete()
|
||||||
|
}
|
||||||
|
override fun onCompleted(result:Any) {
|
||||||
|
Log.d(tag, "DOWNLOAD: FILE MOVE COMPLETED")
|
||||||
|
val resultDocFile = result as DocumentFile
|
||||||
|
Log.d(tag, "DOWNLOAD: COMPLETED FILE INFO ${resultDocFile.getAbsolutePath(mainActivity)}")
|
||||||
|
downloadItemPart.moved = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(tag, "DOWNLOAD: Move file to final destination path: ${downloadItemPart.finalDestinationPath}")
|
||||||
|
val localFolderFile = DocumentFileCompat.fromUri(mainActivity,Uri.parse(downloadItemPart.localFolderUrl))
|
||||||
|
val mimetype = if (downloadItemPart.audioTrack != null) MimeType.AUDIO else MimeType.IMAGE
|
||||||
|
val fileDescription = FileDescription(downloadItemPart.filename, downloadItemPart.itemTitle, mimetype)
|
||||||
|
file?.moveFileTo(mainActivity,localFolderFile!!,fileDescription,fcb)
|
||||||
|
} else {
|
||||||
|
// Why is kotlin requiring an else here..
|
||||||
|
}
|
||||||
} else if (downloadStatus == DownloadManager.STATUS_FAILED) {
|
} else if (downloadStatus == DownloadManager.STATUS_FAILED) {
|
||||||
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Failed")
|
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Failed")
|
||||||
downloadItem.downloadItemParts.remove(downloadItemPart)
|
downloadItem.downloadItemParts.remove(downloadItemPart)
|
||||||
// downloadItemPart.completed = true
|
|
||||||
} else {
|
} else {
|
||||||
//update progress
|
//update progress
|
||||||
val percentProgress = if (totalBytes > 0) ((bytesDownloadedSoFar * 100L) / totalBytes) else 0
|
val percentProgress = if (totalBytes > 0) ((bytesDownloadedSoFar * 100L) / totalBytes) else 0
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
<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 v-if="!isPodcast" 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 && !isLocalPlayMethod" 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 -->
|
<!-- 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 v-else class="material-icons text-3xl text-white text-opacity-0">bookmark</span>
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
<modals-playback-speed-modal v-model="showPlaybackSpeedModal" :playback-rate.sync="playbackSpeed" @update:playbackRate="updatePlaybackSpeed" @change="changePlaybackSpeed" />
|
<modals-playback-speed-modal v-model="showPlaybackSpeedModal" :playback-rate.sync="playbackSpeed" @update:playbackRate="updatePlaybackSpeed" @change="changePlaybackSpeed" />
|
||||||
<modals-sleep-timer-modal v-model="showSleepTimerModal" :current-time="sleepTimeRemaining" :sleep-timer-running="isSleepTimerRunning" :current-end-of-chapter-time="currentEndOfChapterTime" @change="selectSleepTimeout" @cancel="cancelSleepTimer" @increase="increaseSleepTimer" @decrease="decreaseSleepTimer" />
|
<modals-sleep-timer-modal v-model="showSleepTimerModal" :current-time="sleepTimeRemaining" :sleep-timer-running="isSleepTimerRunning" :current-end-of-chapter-time="currentEndOfChapterTime" @change="selectSleepTimeout" @cancel="cancelSleepTimer" @increase="increaseSleepTimer" @decrease="decreaseSleepTimer" />
|
||||||
<modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :current-time="currentTime" @select="selectBookmark" />
|
<modals-bookmarks-modal v-model="showBookmarksModal" :bookmarks="bookmarks" :current-time="currentTime" :library-item-id="serverLibraryItemId" @select="selectBookmark" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -31,7 +31,8 @@ export default {
|
||||||
onSleepTimerSetListener: null,
|
onSleepTimerSetListener: null,
|
||||||
onMediaPlayerChangedListener: null,
|
onMediaPlayerChangedListener: null,
|
||||||
sleepInterval: null,
|
sleepInterval: null,
|
||||||
currentEndOfChapterTime: 0
|
currentEndOfChapterTime: 0,
|
||||||
|
serverLibraryItemId: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -44,8 +45,8 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
bookmarks() {
|
bookmarks() {
|
||||||
// return this.$store.getters['user/getUserBookmarksForItem'](this.)
|
if (!this.serverLibraryItemId) return []
|
||||||
return []
|
return this.$store.getters['user/getUserBookmarksForItem'](this.serverLibraryItemId)
|
||||||
},
|
},
|
||||||
socketConnected() {
|
socketConnected() {
|
||||||
return this.$store.state.socketConnected
|
return this.$store.state.socketConnected
|
||||||
|
@ -181,10 +182,20 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.serverLibraryItemId = null
|
||||||
|
|
||||||
|
var playbackRate = 1
|
||||||
|
if (this.$refs.audioPlayer) {
|
||||||
|
playbackRate = this.$refs.audioPlayer.currentPlaybackRate || 1
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Called playLibraryItem', libraryItemId)
|
console.log('Called playLibraryItem', libraryItemId)
|
||||||
AbsAudioPlayer.prepareLibraryItem({ libraryItemId, episodeId, playWhenReady: true })
|
AbsAudioPlayer.prepareLibraryItem({ libraryItemId, episodeId, playWhenReady: true, playbackRate })
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
console.log('Library item play response', JSON.stringify(data))
|
console.log('Library item play response', JSON.stringify(data))
|
||||||
|
if (!libraryItemId.startsWith('local')) {
|
||||||
|
this.serverLibraryItemId = libraryItemId
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Failed', error)
|
console.error('Failed', error)
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
<div class="bookshelfRow flex items-end px-3 max-w-full overflow-x-auto" :style="{ height: shelfHeight + 'px' }">
|
<div class="bookshelfRow flex items-end px-3 max-w-full overflow-x-auto" :style="{ height: shelfHeight + 'px' }">
|
||||||
<template v-for="(entity, index) in entities">
|
<template v-for="(entity, index) in entities">
|
||||||
<cards-lazy-book-card v-if="type === 'book' || type === 'podcast'" :key="entity.id" :index="index" :book-mount="entity" :width="bookWidth" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" is-categorized class="mx-2 relative" />
|
<cards-lazy-book-card v-if="type === 'book' || type === 'podcast'" :key="entity.id" :index="index" :book-mount="entity" :width="bookWidth" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" is-categorized class="mx-2 relative" />
|
||||||
|
<cards-lazy-book-card v-if="type === 'episode'" :key="entity.recentEpisode.id" :index="index" :book-mount="entity" :width="bookWidth" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" is-categorized class="mx-2 relative" />
|
||||||
<cards-lazy-series-card v-else-if="type === 'series'" :key="entity.id" :index="index" :series-mount="entity" :width="bookWidth * 2" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" is-categorized class="mx-2 relative" />
|
<cards-lazy-series-card v-else-if="type === 'series'" :key="entity.id" :index="index" :series-mount="entity" :width="bookWidth * 2" :height="entityHeight" :book-cover-aspect-ratio="bookCoverAspectRatio" is-categorized class="mx-2 relative" />
|
||||||
<cards-author-card v-else-if="type === 'authors'" :key="entity.id" :width="bookWidth / 1.25" :height="bookWidth" :author="entity" :size-multiplier="1" class="mx-2" />
|
<cards-author-card v-else-if="type === 'authors'" :key="entity.id" :width="bookWidth / 1.25" :height="bookWidth" :author="entity" :size-multiplier="1" class="mx-2" />
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -34,8 +34,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Play/pause button for podcast episode -->
|
||||||
|
<div v-if="recentEpisode" class="absolute z-10 top-0 left-0 bottom-0 right-0 m-auto flex items-center justify-center w-12 h-12 rounded-full bg-white bg-opacity-70">
|
||||||
|
<span class="material-icons text-6xl text-black text-opacity-80">{{ streamIsPlaying ? 'pause_circle' : 'play_circle_filled' }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- No progress shown for collapsed series in library -->
|
<!-- No progress shown for collapsed series in library -->
|
||||||
<div v-if="!collapsedSeries && !isPodcast" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
<div v-if="!collapsedSeries && (!isPodcast || recentEpisode)" class="absolute bottom-0 left-0 h-1 shadow-sm max-w-full z-10 rounded-b" :class="itemIsFinished ? 'bg-success' : 'bg-yellow-400'" :style="{ width: width * userProgressPercent + 'px' }"></div>
|
||||||
|
|
||||||
<div v-if="localLibraryItem || isLocal" class="absolute top-0 right-0 z-20" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
<div v-if="localLibraryItem || isLocal" class="absolute top-0 right-0 z-20" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||||
<span class="material-icons text-2xl text-success">{{ isLocalOnly ? 'task' : 'download_done' }}</span>
|
<span class="material-icons text-2xl text-success">{{ isLocalOnly ? 'task' : 'download_done' }}</span>
|
||||||
|
@ -46,13 +51,18 @@
|
||||||
<span class="material-icons text-red-100 pr-1" :style="{ fontSize: 0.875 * sizeMultiplier + 'rem' }">priority_high</span>
|
<span class="material-icons text-red-100 pr-1" :style="{ fontSize: 0.875 * sizeMultiplier + 'rem' }">priority_high</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Volume number -->
|
<!-- Series sequence -->
|
||||||
<div v-if="seriesSequence && showSequence && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
<div v-if="seriesSequence && showSequence && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequence }}</p>
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">#{{ seriesSequence }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Podcast Episode # -->
|
||||||
|
<div v-if="recentEpisodeNumber && !isSelectionMode" class="absolute rounded-lg bg-black bg-opacity-90 box-shadow-md z-10" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', padding: `${0.1 * sizeMultiplier}rem ${0.25 * sizeMultiplier}rem` }">
|
||||||
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">Episode #{{ recentEpisodeNumber }}</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Podcast Num Episodes -->
|
<!-- Podcast Num Episodes -->
|
||||||
<div v-if="numEpisodes && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
<div v-else-if="numEpisodes && !isSelectionMode" class="absolute rounded-full bg-black bg-opacity-90 box-shadow-md z-10 flex items-center justify-center" :style="{ top: 0.375 * sizeMultiplier + 'rem', right: 0.375 * sizeMultiplier + 'rem', width: 1.25 * sizeMultiplier + 'rem', height: 1.25 * sizeMultiplier + 'rem' }">
|
||||||
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
|
<p :style="{ fontSize: sizeMultiplier * 0.8 + 'rem' }">{{ numEpisodes }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -196,6 +206,17 @@ export default {
|
||||||
seriesSequence() {
|
seriesSequence() {
|
||||||
return this.series ? this.series.sequence : null
|
return this.series ? this.series.sequence : null
|
||||||
},
|
},
|
||||||
|
recentEpisode() {
|
||||||
|
// Only added to item when getting currently listening podcasts
|
||||||
|
return this._libraryItem.recentEpisode
|
||||||
|
},
|
||||||
|
recentEpisodeNumber() {
|
||||||
|
if (!this.recentEpisode) return null
|
||||||
|
if (this.recentEpisode.episode) {
|
||||||
|
return this.recentEpisode.episode.replace(/^#/, '')
|
||||||
|
}
|
||||||
|
return this.recentEpisode.index
|
||||||
|
},
|
||||||
collapsedSeries() {
|
collapsedSeries() {
|
||||||
// Only added to item object when collapseSeries is enabled
|
// Only added to item object when collapseSeries is enabled
|
||||||
return this._libraryItem.collapsedSeries
|
return this._libraryItem.collapsedSeries
|
||||||
|
@ -222,7 +243,14 @@ export default {
|
||||||
if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size)
|
if (this.orderBy === 'size') return 'Size: ' + this.$bytesPretty(this._libraryItem.size)
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
|
episodeProgress() {
|
||||||
|
// Only used on home page currently listening podcast shelf
|
||||||
|
if (!this.recentEpisode) return null
|
||||||
|
if (this.isLocal) return this.store.getters['globals/getLocalMediaProgressById'](this.libraryItemId, this.recentEpisode.id)
|
||||||
|
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId, this.recentEpisode.id)
|
||||||
|
},
|
||||||
userProgress() {
|
userProgress() {
|
||||||
|
if (this.episodeProgress) return this.episodeProgress
|
||||||
if (this.isLocal) return this.store.getters['globals/getLocalMediaProgressById'](this.libraryItemId)
|
if (this.isLocal) return this.store.getters['globals/getLocalMediaProgressById'](this.libraryItemId)
|
||||||
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
return this.store.getters['user/getUserMediaProgress'](this.libraryItemId)
|
||||||
},
|
},
|
||||||
|
@ -233,19 +261,28 @@ export default {
|
||||||
return this.userProgress ? !!this.userProgress.isFinished : false
|
return this.userProgress ? !!this.userProgress.isFinished : false
|
||||||
},
|
},
|
||||||
showError() {
|
showError() {
|
||||||
return this.hasMissingParts || this.hasInvalidParts || this.isMissing || this.isInvalid
|
return this.numMissingParts || this.isMissing || this.isInvalid
|
||||||
|
},
|
||||||
|
playerIsLocal() {
|
||||||
|
return !!this.$store.state.playerIsLocal
|
||||||
|
},
|
||||||
|
localLibraryItemId() {
|
||||||
|
if (this.isLocal) return this.libraryItemId
|
||||||
|
return this.localLibraryItem ? this.localLibraryItem.id : null
|
||||||
},
|
},
|
||||||
isStreaming() {
|
isStreaming() {
|
||||||
return this.store.getters['getlibraryItemIdStreaming'] === this.libraryItemId
|
if (this.isPodcast) {
|
||||||
|
if (this.playerIsLocal) {
|
||||||
|
// Check is streaming local version of this episode
|
||||||
|
return false // episode cards not implemented for local yet
|
||||||
|
}
|
||||||
|
return this.$store.getters['getIsEpisodeStreaming'](this.libraryItemId, this.recentEpisode.id)
|
||||||
|
} else {
|
||||||
|
return false // not yet necessary for books
|
||||||
|
}
|
||||||
},
|
},
|
||||||
showReadButton() {
|
streamIsPlaying() {
|
||||||
return !this.isSelectionMode && this.showExperimentalFeatures && !this.showPlayButton && this.hasEbook
|
return this.$store.state.playerIsPlaying && this.isStreaming
|
||||||
},
|
|
||||||
showPlayButton() {
|
|
||||||
return !this.isSelectionMode && !this.isMissing && !this.isInvalid && this.numTracks && !this.isStreaming
|
|
||||||
},
|
|
||||||
showSmallEBookIcon() {
|
|
||||||
return !this.isSelectionMode && this.showExperimentalFeatures && this.hasEbook
|
|
||||||
},
|
},
|
||||||
isMissing() {
|
isMissing() {
|
||||||
return this._libraryItem.isMissing
|
return this._libraryItem.isMissing
|
||||||
|
@ -253,24 +290,9 @@ export default {
|
||||||
isInvalid() {
|
isInvalid() {
|
||||||
return this._libraryItem.isInvalid
|
return this._libraryItem.isInvalid
|
||||||
},
|
},
|
||||||
hasMissingParts() {
|
numMissingParts() {
|
||||||
return this._libraryItem.hasMissingParts
|
if (this.isPodcast) return 0
|
||||||
},
|
return this.media.numMissingParts
|
||||||
hasInvalidParts() {
|
|
||||||
return this._libraryItem.hasInvalidParts
|
|
||||||
},
|
|
||||||
errorText() {
|
|
||||||
if (this.isMissing) return 'Item directory is missing!'
|
|
||||||
else if (this.isInvalid) return 'Item has no media files'
|
|
||||||
var txt = ''
|
|
||||||
if (this.hasMissingParts) {
|
|
||||||
txt = `${this.hasMissingParts} missing parts.`
|
|
||||||
}
|
|
||||||
if (this.hasInvalidParts) {
|
|
||||||
if (this.hasMissingParts) txt += ' '
|
|
||||||
txt += `${this.hasInvalidParts} invalid parts.`
|
|
||||||
}
|
|
||||||
return txt || 'Unknown Error'
|
|
||||||
},
|
},
|
||||||
overlayWrapperClasslist() {
|
overlayWrapperClasslist() {
|
||||||
var classes = []
|
var classes = []
|
||||||
|
@ -343,6 +365,10 @@ export default {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
this.selectBtnClick()
|
this.selectBtnClick()
|
||||||
|
} else if (this.recentEpisode) {
|
||||||
|
var eventBus = this.$eventBus || this.$nuxt.$eventBus
|
||||||
|
if (this.streamIsPlaying) eventBus.$emit('pause-item')
|
||||||
|
else eventBus.$emit('play-item', { libraryItemId: this.libraryItemId, episodeId: this.recentEpisode.id })
|
||||||
} else {
|
} else {
|
||||||
var router = this.$router || this.$nuxt.$router
|
var router = this.$router || this.$nuxt.$router
|
||||||
if (router) {
|
if (router) {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<div class="absolute cover-bg" ref="coverBg" />
|
<div class="absolute cover-bg" ref="coverBg" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<img v-if="fullCoverUrl" ref="cover" :src="fullCoverUrl" loading="lazy" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0 z-10 duration-300 transition-opacity" :style="{ opacity: imageReady ? '1' : '0' }" :class="showCoverBg && !hasCover ? 'object-contain' : 'object-fill'" />
|
<img v-if="fullCoverUrl" ref="cover" :src="fullCoverUrl" loading="lazy" @error="imageError" @load="imageLoaded" class="w-full h-full absolute top-0 left-0 z-10 duration-300 transition-opacity" :style="{ opacity: imageReady ? 1 : 0 }" :class="showCoverBg && !hasCover ? 'object-contain' : 'object-fill'" />
|
||||||
|
|
||||||
<div v-show="loading && libraryItem" class="absolute top-0 left-0 h-full w-full flex items-center justify-center">
|
<div v-show="loading && libraryItem" class="absolute top-0 left-0 h-full w-full flex items-center justify-center">
|
||||||
<p class="font-book text-center" :style="{ fontSize: 0.75 * sizeMultiplier + 'rem' }">{{ title }}</p>
|
<p class="font-book text-center" :style="{ fontSize: 0.75 * sizeMultiplier + 'rem' }">{{ title }}</p>
|
||||||
|
|
|
@ -45,6 +45,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { Dialog } from '@capacitor/dialog'
|
||||||
export default {
|
export default {
|
||||||
props: {
|
props: {
|
||||||
value: Boolean,
|
value: Boolean,
|
||||||
|
@ -56,7 +57,7 @@ export default {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0
|
default: 0
|
||||||
},
|
},
|
||||||
audiobookId: String
|
libraryItemId: String
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -96,40 +97,78 @@ export default {
|
||||||
this.newBookmarkTitle = bm.title
|
this.newBookmarkTitle = bm.title
|
||||||
this.showBookmarkTitleInput = true
|
this.showBookmarkTitleInput = true
|
||||||
},
|
},
|
||||||
deleteBookmark(bm) {
|
async deleteBookmark(bm) {
|
||||||
var bookmark = { ...bm, audiobookId: this.audiobookId }
|
const { value } = await Dialog.confirm({
|
||||||
this.$server.socket.emit('delete_bookmark', bookmark)
|
title: 'Remove Bookmark',
|
||||||
|
message: `Are you sure you want to remove bookmark?`
|
||||||
|
})
|
||||||
|
if (!value) return
|
||||||
|
|
||||||
|
this.$axios
|
||||||
|
.$delete(`/api/me/item/${this.libraryItemId}/bookmark/${bm.time}`)
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success('Bookmark removed')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toast.error(`Failed to remove bookmark`)
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
|
this.show = false
|
||||||
},
|
},
|
||||||
clickBookmark(bm) {
|
clickBookmark(bm) {
|
||||||
this.$emit('select', bm)
|
this.$emit('select', bm)
|
||||||
},
|
},
|
||||||
|
submitUpdateBookmark(updatedBookmark) {
|
||||||
|
var bookmark = { ...updatedBookmark }
|
||||||
|
this.$axios
|
||||||
|
.$patch(`/api/me/item/${this.libraryItemId}/bookmark`, bookmark)
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success('Bookmark updated')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toast.error(`Failed to update bookmark`)
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
|
this.show = false
|
||||||
|
},
|
||||||
|
submitCreateBookmark() {
|
||||||
|
if (!this.newBookmarkTitle) {
|
||||||
|
this.newBookmarkTitle = this.$formatDate(Date.now(), 'MMM dd, yyyy HH:mm')
|
||||||
|
}
|
||||||
|
var bookmark = {
|
||||||
|
title: this.newBookmarkTitle,
|
||||||
|
time: Math.floor(this.currentTime)
|
||||||
|
}
|
||||||
|
this.$axios
|
||||||
|
.$post(`/api/me/item/${this.libraryItemId}/bookmark`, bookmark)
|
||||||
|
.then(() => {
|
||||||
|
this.$toast.success('Bookmark added')
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
this.$toast.error(`Failed to create bookmark`)
|
||||||
|
console.error(error)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.newBookmarkTitle = ''
|
||||||
|
this.showBookmarkTitleInput = false
|
||||||
|
|
||||||
|
this.show = false
|
||||||
|
},
|
||||||
createBookmark() {
|
createBookmark() {
|
||||||
this.selectedBookmark = null
|
this.selectedBookmark = null
|
||||||
this.newBookmarkTitle = this.$formatDate(Date.now(), 'MMM dd, yyyy HH:mm')
|
this.newBookmarkTitle = this.$formatDate(Date.now(), 'MMM dd, yyyy HH:mm')
|
||||||
this.showBookmarkTitleInput = true
|
this.showBookmarkTitleInput = true
|
||||||
},
|
},
|
||||||
submitBookmark() {
|
submitBookmark() {
|
||||||
console.log(`[BookmarksModal] Submit Bookmark ${this.newBookmarkTitle}/${this.audiobookId}`)
|
|
||||||
if (this.selectedBookmark) {
|
if (this.selectedBookmark) {
|
||||||
if (this.selectedBookmark.title !== this.newBookmarkTitle) {
|
var updatePayload = {
|
||||||
var bookmark = { ...this.selectedBookmark }
|
...this.selectedBookmark,
|
||||||
bookmark.audiobookId = this.audiobookId
|
title: this.newBookmarkTitle
|
||||||
bookmark.title = this.newBookmarkTitle
|
|
||||||
console.log(`[BookmarksModal] Update Bookmark ${JSON.stringify(bookmark)}`)
|
|
||||||
this.$server.socket.emit('update_bookmark', bookmark)
|
|
||||||
}
|
}
|
||||||
|
this.submitUpdateBookmark(updatePayload)
|
||||||
} else {
|
} else {
|
||||||
var bookmark = {
|
this.submitCreateBookmark()
|
||||||
audiobookId: this.audiobookId,
|
|
||||||
title: this.newBookmarkTitle,
|
|
||||||
time: this.currentTime
|
|
||||||
}
|
|
||||||
console.log(`[BookmarksModal] Create Bookmark ${JSON.stringify(bookmark)}`)
|
|
||||||
this.$server.socket.emit('create_bookmark', bookmark)
|
|
||||||
}
|
}
|
||||||
this.newBookmarkTitle = ''
|
|
||||||
this.showBookmarkTitleInput = false
|
|
||||||
this.show = false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {}
|
mounted() {}
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<div :key="bookmark.id" :id="`bookmark-row-${bookmark.id}`" class="flex items-center px-4 py-4 justify-start cursor-pointer hover:bg-bg relative" :class="highlight ? 'bg-bg bg-opacity-60' : ' bg-opacity-20'" @click="click">
|
<div :key="bookmark.id" :id="`bookmark-row-${bookmark.id}`" class="flex items-center px-3 py-4 justify-start cursor-pointer hover:bg-bg relative" :class="highlight ? 'bg-bg bg-opacity-60' : ' bg-opacity-20'" @click="click">
|
||||||
<span class="material-icons" :class="highlight ? 'text-success' : 'text-white text-opacity-60'">{{ highlight ? 'bookmark' : 'bookmark_border' }}</span>
|
<span class="material-icons text-xl" :class="highlight ? 'text-success' : 'text-white text-opacity-60'">{{ highlight ? 'bookmark' : 'bookmark_border' }}</span>
|
||||||
<div class="flex-grow overflow-hidden">
|
<div class="flex-grow overflow-hidden">
|
||||||
<p class="pl-2 pr-2 truncate">{{ bookmark.title }}</p>
|
<p class="pl-2 pr-2 truncate text-sm">{{ bookmark.title }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-full flex items-center w-16 justify-end">
|
<div class="h-full flex items-center w-14 justify-end">
|
||||||
<span class="font-mono text-sm text-gray-300">{{ $secondsToTimestamp(bookmark.time) }}</span>
|
<span class="font-mono text-xs text-gray-300">{{ $secondsToTimestamp(bookmark.time) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="h-full flex items-center justify-end transform w-16">
|
<div class="h-full flex items-center justify-end transform w-16">
|
||||||
<span class="material-icons text-lg mr-2 text-gray-200 hover:text-yellow-400" @click.stop="editClick">edit</span>
|
<span class="material-icons text-lg mr-2 text-gray-200 hover:text-yellow-400" @click.stop="editClick">edit</span>
|
||||||
|
|
|
@ -459,12 +459,12 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 4;
|
CURRENT_PROJECT_VERSION = 5;
|
||||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 0.9.41;
|
MARKETING_VERSION = 0.9.42;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app.development;
|
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app.development;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
@ -483,12 +483,12 @@
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
ASSETCATALOG_COMPILER_APPICON_NAME = Icons;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 4;
|
CURRENT_PROJECT_VERSION = 5;
|
||||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 0.9.41;
|
MARKETING_VERSION = 0.9.42;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "";
|
||||||
|
|
|
@ -13,7 +13,7 @@ public class AbsAudioPlayer: CAPPlugin {
|
||||||
override public func load() {
|
override public func load() {
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: NSNotification.Name(PlayerEvents.update.rawValue), object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(sendPlaybackClosedEvent), name: NSNotification.Name(PlayerEvents.closed.rawValue), object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(sendPlaybackClosedEvent), name: NSNotification.Name(PlayerEvents.closed.rawValue), object: nil)
|
||||||
|
self.bridge?.webView?.allowsBackForwardNavigationGestures = true;
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: UIApplication.didBecomeActiveNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: UIApplication.didBecomeActiveNotification, object: nil)
|
||||||
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: UIApplication.willEnterForegroundNotification, object: nil)
|
NotificationCenter.default.addObserver(self, selector: #selector(sendMetadata), name: UIApplication.willEnterForegroundNotification, object: nil)
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ public class AbsAudioPlayer: CAPPlugin {
|
||||||
let libraryItemId = call.getString("libraryItemId")
|
let libraryItemId = call.getString("libraryItemId")
|
||||||
let episodeId = call.getString("episodeId")
|
let episodeId = call.getString("episodeId")
|
||||||
let playWhenReady = call.getBool("playWhenReady", true)
|
let playWhenReady = call.getBool("playWhenReady", true)
|
||||||
|
let playbackRate = call.getFloat("playbackRate", 1)
|
||||||
|
|
||||||
if libraryItemId == nil {
|
if libraryItemId == nil {
|
||||||
NSLog("provide library item id")
|
NSLog("provide library item id")
|
||||||
|
@ -34,7 +35,7 @@ public class AbsAudioPlayer: CAPPlugin {
|
||||||
|
|
||||||
sendPrepareMetadataEvent(itemId: libraryItemId!, playWhenReady: playWhenReady)
|
sendPrepareMetadataEvent(itemId: libraryItemId!, playWhenReady: playWhenReady)
|
||||||
ApiClient.startPlaybackSession(libraryItemId: libraryItemId!, episodeId: episodeId) { session in
|
ApiClient.startPlaybackSession(libraryItemId: libraryItemId!, episodeId: episodeId) { session in
|
||||||
PlayerHandler.startPlayback(session: session, playWhenReady: playWhenReady)
|
PlayerHandler.startPlayback(session: session, playWhenReady: playWhenReady, playbackRate: playbackRate)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
self.sendPlaybackSession(session: try session.asDictionary())
|
self.sendPlaybackSession(session: try session.asDictionary())
|
||||||
|
|
|
@ -22,18 +22,21 @@ class AudioPlayer: NSObject {
|
||||||
private var playerItemContext = 0
|
private var playerItemContext = 0
|
||||||
|
|
||||||
private var playWhenReady: Bool
|
private var playWhenReady: Bool
|
||||||
|
private var initialPlaybackRate: Float
|
||||||
|
|
||||||
private var audioPlayer: AVPlayer
|
private var audioPlayer: AVPlayer
|
||||||
private var playbackSession: PlaybackSession
|
private var playbackSession: PlaybackSession
|
||||||
private var activeAudioTrack: AudioTrack
|
private var activeAudioTrack: AudioTrack
|
||||||
|
|
||||||
// MARK: - Constructor
|
// MARK: - Constructor
|
||||||
init(playbackSession: PlaybackSession, playWhenReady: Bool = false) {
|
init(playbackSession: PlaybackSession, playWhenReady: Bool = false, playbackRate: Float = 1) {
|
||||||
self.playWhenReady = playWhenReady
|
self.playWhenReady = playWhenReady
|
||||||
|
self.initialPlaybackRate = playbackRate
|
||||||
self.audioPlayer = AVPlayer()
|
self.audioPlayer = AVPlayer()
|
||||||
self.playbackSession = playbackSession
|
self.playbackSession = playbackSession
|
||||||
self.status = -1
|
self.status = -1
|
||||||
self.rate = 0.0
|
self.rate = 0.0
|
||||||
|
self.tmpRate = playbackRate
|
||||||
|
|
||||||
if playbackSession.audioTracks.count != 1 || playbackSession.audioTracks[0].mimeType != "application/vnd.apple.mpegurl" {
|
if playbackSession.audioTracks.count != 1 || playbackSession.audioTracks[0].mimeType != "application/vnd.apple.mpegurl" {
|
||||||
NSLog("The player only support HLS streams right now")
|
NSLog("The player only support HLS streams right now")
|
||||||
|
@ -74,9 +77,9 @@ class AudioPlayer: NSObject {
|
||||||
print(error)
|
print(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
DispatchQueue.main.sync {
|
// DispatchQueue.main.sync {
|
||||||
UIApplication.shared.endReceivingRemoteControlEvents()
|
UIApplication.shared.endReceivingRemoteControlEvents()
|
||||||
}
|
// }
|
||||||
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.closed.rawValue), object: nil)
|
NotificationCenter.default.post(name: NSNotification.Name(PlayerEvents.closed.rawValue), object: nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@ class PlayerHandler {
|
||||||
|
|
||||||
private static var listeningTimePassedSinceLastSync = 0.0
|
private static var listeningTimePassedSinceLastSync = 0.0
|
||||||
|
|
||||||
public static func startPlayback(session: PlaybackSession, playWhenReady: Bool) {
|
public static func startPlayback(session: PlaybackSession, playWhenReady: Bool, playbackRate: Float) {
|
||||||
if player != nil {
|
if player != nil {
|
||||||
player?.destroy()
|
player?.destroy()
|
||||||
player = nil
|
player = nil
|
||||||
|
@ -23,7 +23,7 @@ class PlayerHandler {
|
||||||
NowPlayingInfo.setSessionMetadata(metadata: NowPlayingMetadata(id: session.id, itemId: session.libraryItemId!, artworkUrl: session.coverPath, title: session.displayTitle ?? "Unknown title", author: session.displayAuthor, series: nil))
|
NowPlayingInfo.setSessionMetadata(metadata: NowPlayingMetadata(id: session.id, itemId: session.libraryItemId!, artworkUrl: session.coverPath, title: session.displayTitle ?? "Unknown title", author: session.displayAuthor, series: nil))
|
||||||
|
|
||||||
self.session = session
|
self.session = session
|
||||||
player = AudioPlayer(playbackSession: session, playWhenReady: playWhenReady)
|
player = AudioPlayer(playbackSession: session, playWhenReady: playWhenReady, playbackRate: playbackRate)
|
||||||
|
|
||||||
// DispatchQueue.main.sync {
|
// DispatchQueue.main.sync {
|
||||||
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
|
||||||
|
|
|
@ -192,7 +192,9 @@ export default {
|
||||||
socketConnectionFailed(err) {
|
socketConnectionFailed(err) {
|
||||||
this.$toast.error('Socket connection error: ' + err.message)
|
this.$toast.error('Socket connection error: ' + err.message)
|
||||||
},
|
},
|
||||||
socketInit(data) {},
|
socketInit(data) {
|
||||||
|
console.log('Socket init', data)
|
||||||
|
},
|
||||||
async initLibraries() {
|
async initLibraries() {
|
||||||
if (this.inittingLibraries) {
|
if (this.inittingLibraries) {
|
||||||
return
|
return
|
||||||
|
|
2
package-lock.json
generated
2
package-lock.json
generated
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "audiobookshelf-app",
|
"name": "audiobookshelf-app",
|
||||||
"version": "0.9.41-beta",
|
"version": "0.9.42-beta",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "audiobookshelf-app",
|
"name": "audiobookshelf-app",
|
||||||
"version": "0.9.41-beta",
|
"version": "0.9.42-beta",
|
||||||
"author": "advplyr",
|
"author": "advplyr",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nuxt --hostname localhost --port 1337",
|
"dev": "nuxt --hostname localhost --port 1337",
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="!shelves.length" class="absolute top-0 left-0 w-full h-full flex items-center justify-center">
|
<div v-if="!shelves.length && !loading" class="absolute top-0 left-0 w-full h-full flex items-center justify-center">
|
||||||
<div>
|
<div>
|
||||||
<p class="mb-4 text-center text-xl">
|
<p class="mb-4 text-center text-xl">
|
||||||
Bookshelf empty
|
Bookshelf empty
|
||||||
|
@ -20,13 +20,15 @@
|
||||||
<span class="material-icons text-error text-lg">cloud_off</span>
|
<span class="material-icons text-error text-lg">cloud_off</span>
|
||||||
<p class="pl-2 text-error text-sm">Audiobookshelf server not connected.</p>
|
<p class="pl-2 text-error text-sm">Audiobookshelf server not connected.</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- <p class="px-4 text-center text-error absolute bottom-12 left-0 right-0 mx-auto"><strong>Important!</strong> This app requires that you are running <u>your own server</u> and does not provide any content.</p> -->
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-center">
|
<div class="flex justify-center">
|
||||||
<ui-btn v-if="!user" small @click="$router.push('/connect')" class="w-32">Connect</ui-btn>
|
<ui-btn v-if="!user" small @click="$router.push('/connect')" class="w-32">Connect</ui-btn>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="loading" class="absolute top-0 left-0 w-full h-full flex items-center justify-center">
|
||||||
|
<ui-loading-indicator text="Loading Library..." />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|
|
@ -6,28 +6,6 @@ const isWeb = Capacitor.getPlatform() == 'web'
|
||||||
class DbService {
|
class DbService {
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
// Please dont use this, it is not implemented in ios (maybe key: primary value: any ?)
|
|
||||||
save(db, key, value) {
|
|
||||||
if (isWeb) return
|
|
||||||
return AbsDatabase.saveFromWebview({ db, key, value }).then(() => {
|
|
||||||
console.log('Saved data', db, key, JSON.stringify(value))
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error('Failed to save data', error)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Please dont use this, it is not implemented in ios
|
|
||||||
load(db, key) {
|
|
||||||
if (isWeb) return null
|
|
||||||
return AbsDatabase.loadFromWebview({ db, key }).then((data) => {
|
|
||||||
console.log('Loaded data', db, key, JSON.stringify(data))
|
|
||||||
return data
|
|
||||||
}).catch((error) => {
|
|
||||||
console.error('Failed to load', error)
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
getDeviceData() {
|
getDeviceData() {
|
||||||
return AbsDatabase.getDeviceData().then((data) => {
|
return AbsDatabase.getDeviceData().then((data) => {
|
||||||
console.log('Loaded device data', JSON.stringify(data))
|
console.log('Loaded device data', JSON.stringify(data))
|
||||||
|
|
|
@ -48,9 +48,10 @@ class ServerSocket extends EventEmitter {
|
||||||
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.on('user_item_progress_updated', this.onUserItemProgressUpdated.bind(this))
|
||||||
|
|
||||||
this.socket.onAny((evt, args) => {
|
// Good for testing socket requests
|
||||||
console.log(`[SOCKET] onAny: ${this.socket.id}: ${evt} ${JSON.stringify(args)}`)
|
// this.socket.onAny((evt, args) => {
|
||||||
})
|
// console.log(`[SOCKET] onAny: ${this.socket.id}: ${evt} ${JSON.stringify(args)}`)
|
||||||
|
// })
|
||||||
}
|
}
|
||||||
|
|
||||||
onConnect() {
|
onConnect() {
|
||||||
|
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 122 KiB |
Binary file not shown.
Binary file not shown.
Before Width: | Height: | Size: 151 KiB |
|
@ -35,7 +35,7 @@ export const getters = {
|
||||||
return state.serverSettings[key]
|
return state.serverSettings[key]
|
||||||
},
|
},
|
||||||
getBookCoverAspectRatio: state => {
|
getBookCoverAspectRatio: state => {
|
||||||
if (!state.serverSettings || !state.serverSettings.coverAspectRatio) return 1
|
if (!state.serverSettings) return 1
|
||||||
return state.serverSettings.coverAspectRatio === 0 ? 1.6 : 1
|
return state.serverSettings.coverAspectRatio === 0 ? 1.6 : 1
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -71,8 +71,6 @@ export const mutations = {
|
||||||
|
|
||||||
var mediaPlayer = playbackSession ? playbackSession.mediaPlayer : null
|
var mediaPlayer = playbackSession ? playbackSession.mediaPlayer : null
|
||||||
state.isCasting = mediaPlayer === "cast-player"
|
state.isCasting = mediaPlayer === "cast-player"
|
||||||
|
|
||||||
console.log('setPlayerItem', state.playerLibraryItemId, state.playerEpisodeId, state.playerIsLocal)
|
|
||||||
},
|
},
|
||||||
setMediaPlayer(state, mediaPlayer) {
|
setMediaPlayer(state, mediaPlayer) {
|
||||||
state.isCasting = mediaPlayer === 'cast-player'
|
state.isCasting = mediaPlayer === 'cast-player'
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue