mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-03 10:34:44 +02:00
New downloader for downloading multiple tracks, android media player support for using multiple tracks
This commit is contained in:
parent
f70f707100
commit
7a091dd428
15 changed files with 767 additions and 457 deletions
|
@ -7,6 +7,7 @@
|
||||||
<!-- Permissions -->
|
<!-- Permissions -->
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
|
|
@ -7,38 +7,77 @@ import android.os.Build
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.documentfile.provider.DocumentFile
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import com.anggrayudi.storage.SimpleStorage
|
||||||
import com.anggrayudi.storage.callback.FileCallback
|
import com.anggrayudi.storage.callback.FileCallback
|
||||||
import com.anggrayudi.storage.file.*
|
import com.anggrayudi.storage.file.*
|
||||||
import com.anggrayudi.storage.media.FileDescription
|
import com.anggrayudi.storage.media.FileDescription
|
||||||
|
import com.audiobookshelf.app.data.LibraryItem
|
||||||
|
import com.audiobookshelf.app.data.LocalFolder
|
||||||
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
|
import com.audiobookshelf.app.device.FolderScanner
|
||||||
|
import com.audiobookshelf.app.server.ApiHandler
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.getcapacitor.JSObject
|
import com.getcapacitor.JSObject
|
||||||
import com.getcapacitor.Plugin
|
import com.getcapacitor.Plugin
|
||||||
import com.getcapacitor.PluginCall
|
import com.getcapacitor.PluginCall
|
||||||
import com.getcapacitor.PluginMethod
|
import com.getcapacitor.PluginMethod
|
||||||
import com.getcapacitor.annotation.CapacitorPlugin
|
import com.getcapacitor.annotation.CapacitorPlugin
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import java.io.FileOutputStream
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
|
||||||
@CapacitorPlugin(name = "AudioDownloader")
|
@CapacitorPlugin(name = "AudioDownloader")
|
||||||
class AudioDownloader : Plugin() {
|
class AudioDownloader : Plugin() {
|
||||||
private val tag = "AudioDownloader"
|
private val tag = "AudioDownloader"
|
||||||
|
|
||||||
lateinit var mainActivity:MainActivity
|
lateinit var mainActivity: MainActivity
|
||||||
lateinit var downloadManager:DownloadManager
|
lateinit var downloadManager: DownloadManager
|
||||||
|
lateinit var apiHandler: ApiHandler
|
||||||
|
lateinit var folderScanner: FolderScanner
|
||||||
|
|
||||||
// data class AudiobookItem(val uri: Uri, val name: String, val size: Long, val coverUrl: String) {
|
data class DownloadItemPart(
|
||||||
// fun toJSObject() : JSObject {
|
val id: String,
|
||||||
// var obj = JSObject()
|
val name: String,
|
||||||
// obj.put("uri", this.uri)
|
val itemTitle: String,
|
||||||
// obj.put("name", this.name)
|
val serverPath: String,
|
||||||
// obj.put("size", this.size)
|
val folderName: String,
|
||||||
// obj.put("coverUrl", this.coverUrl)
|
val localFolderId: String,
|
||||||
// return obj
|
@JsonIgnore val uri: Uri,
|
||||||
// }
|
@JsonIgnore val destinationUri: Uri,
|
||||||
// }
|
var downloadId: Long?,
|
||||||
|
var progress: Long
|
||||||
|
) {
|
||||||
|
@JsonIgnore
|
||||||
|
fun getDownloadRequest(): DownloadManager.Request {
|
||||||
|
var dlRequest = DownloadManager.Request(uri)
|
||||||
|
dlRequest.setTitle(name)
|
||||||
|
dlRequest.setDescription("Downloading to $folderName for book $itemTitle")
|
||||||
|
dlRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||||
|
dlRequest.setDestinationUri(destinationUri)
|
||||||
|
return dlRequest
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class DownloadItem(
|
||||||
|
val id: String,
|
||||||
|
val localFolder: LocalFolder,
|
||||||
|
val itemTitle: String,
|
||||||
|
val downloadItemParts: MutableList<DownloadItemPart>
|
||||||
|
)
|
||||||
|
|
||||||
|
var downloadQueue: MutableList<DownloadItem> = mutableListOf()
|
||||||
|
|
||||||
override fun load() {
|
override fun load() {
|
||||||
mainActivity = (activity as MainActivity)
|
mainActivity = (activity as MainActivity)
|
||||||
downloadManager = activity.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
downloadManager = activity.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
|
||||||
|
folderScanner = FolderScanner(mainActivity)
|
||||||
|
apiHandler = ApiHandler(mainActivity)
|
||||||
|
|
||||||
var recieverEvent: (evt: String, id: Long) -> Unit = { evt: String, id: Long ->
|
var recieverEvent: (evt: String, id: Long) -> Unit = { evt: String, id: Long ->
|
||||||
if (evt == "complete") {
|
if (evt == "complete") {
|
||||||
|
@ -52,303 +91,355 @@ class AudioDownloader : Plugin() {
|
||||||
Log.d(tag, "Build SDK ${Build.VERSION.SDK_INT}")
|
Log.d(tag, "Build SDK ${Build.VERSION.SDK_INT}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// @PluginMethod
|
|
||||||
// fun load(call: PluginCall) {
|
|
||||||
// var audiobookUrls = call.data.getJSONArray("audiobookUrls")
|
|
||||||
// var len = audiobookUrls?.length()
|
|
||||||
// if (len == null) {
|
|
||||||
// len = 0
|
|
||||||
// }
|
|
||||||
// Log.d(tag, "CALLED LOAD $len")
|
|
||||||
// var audiobookItems:MutableList<AudiobookItem> = mutableListOf()
|
|
||||||
//
|
|
||||||
// (0 until len).forEach {
|
|
||||||
// var jsobj = audiobookUrls.get(it) as JSONObject
|
|
||||||
// var audiobookUrl = jsobj.get("contentUrl").toString()
|
|
||||||
// var coverUrl = jsobj.get("coverUrl").toString()
|
|
||||||
// var storageId = ""
|
|
||||||
// if(jsobj.has("storageId")) jsobj.get("storageId").toString()
|
|
||||||
//
|
|
||||||
// var basePath = ""
|
|
||||||
// if(jsobj.has("basePath")) jsobj.get("basePath").toString()
|
|
||||||
//
|
|
||||||
// var coverBasePath = ""
|
|
||||||
// if(jsobj.has("coverBasePath")) jsobj.get("coverBasePath").toString()
|
|
||||||
//
|
|
||||||
// Log.d(tag, "LOOKUP $storageId $basePath $audiobookUrl")
|
|
||||||
//
|
|
||||||
// var audiobookFile: DocumentFile? = null
|
|
||||||
// var coverFile: DocumentFile? = null
|
|
||||||
//
|
|
||||||
// // Android 9 OR Below use storage id and base path
|
|
||||||
// if (Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.P) {
|
|
||||||
// audiobookFile = DocumentFileCompat.fromSimplePath(context, storageId, basePath)
|
|
||||||
// if (coverUrl != null && coverUrl != "") {
|
|
||||||
// coverFile = DocumentFileCompat.fromSimplePath(context, storageId, coverBasePath)
|
|
||||||
// }
|
|
||||||
// } else {
|
|
||||||
// // Android 10 and up manually deleting will still load the file causing crash
|
|
||||||
// var exists = checkUriExists(Uri.parse(audiobookUrl))
|
|
||||||
// if (exists) {
|
|
||||||
// Log.d(tag, "Audiobook exists")
|
|
||||||
// audiobookFile = DocumentFileCompat.fromUri(context, Uri.parse(audiobookUrl))
|
|
||||||
// } else {
|
|
||||||
// Log.e(tag, "Audiobook does not exist")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// var coverExists = checkUriExists(Uri.parse(coverUrl))
|
|
||||||
// if (coverExists) {
|
|
||||||
// Log.d(tag, "Cover Exists")
|
|
||||||
// coverFile = DocumentFileCompat.fromUri(context, Uri.parse(coverUrl))
|
|
||||||
// } else if (coverUrl != null && coverUrl != "") {
|
|
||||||
// Log.e(tag, "Cover does not exist")
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (audiobookFile == null) {
|
|
||||||
// Log.e(tag, "Audiobook was not found $audiobookUrl")
|
|
||||||
// } else {
|
|
||||||
// Log.d(tag, "Audiobook File Found StorageId:${audiobookFile.getStorageId(context)} | AbsolutePath:${audiobookFile.getAbsolutePath(context)} | BasePath:${audiobookFile.getBasePath(context)}")
|
|
||||||
//
|
|
||||||
// var _name = audiobookFile.name
|
|
||||||
// if (_name == null) _name = ""
|
|
||||||
//
|
|
||||||
// var size = audiobookFile.length()
|
|
||||||
//
|
|
||||||
// if (audiobookFile.uri.toString() !== audiobookUrl) {
|
|
||||||
// Log.d(tag, "Audiobook URI ${audiobookFile.uri} is different from $audiobookUrl => using the latter")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Use existing URI's - bug happening where new uri is different from initial
|
|
||||||
// var abItem = AudiobookItem(Uri.parse(audiobookUrl), _name, size, coverUrl)
|
|
||||||
//
|
|
||||||
// Log.d(tag, "Setting AB ITEM ${abItem.name} | ${abItem.size} | ${abItem.uri} | ${abItem.coverUrl}")
|
|
||||||
//
|
|
||||||
// audiobookItems.add(abItem)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Log.d(tag, "Load Finished ${audiobookItems.size} found")
|
|
||||||
//
|
|
||||||
// var audiobookObjs:List<JSObject> = audiobookItems.map{ it.toJSObject() }
|
|
||||||
// var mediaItemNoticePayload = JSObject()
|
|
||||||
// mediaItemNoticePayload.put("items", audiobookObjs)
|
|
||||||
// notifyListeners("onMediaLoaded", mediaItemNoticePayload)
|
|
||||||
// }
|
|
||||||
|
|
||||||
@PluginMethod
|
@PluginMethod
|
||||||
fun download(call: PluginCall) {
|
fun downloadLibraryItem(call: PluginCall) {
|
||||||
var audiobookId = call.data.getString("audiobookId", "audiobook").toString()
|
var libraryItemId = call.data.getString("libraryItemId").toString()
|
||||||
var url = call.data.getString("downloadUrl", "unknown").toString()
|
var localFolderId = call.data.getString("localFolderId").toString()
|
||||||
var coverDownloadUrl = call.data.getString("coverDownloadUrl", "").toString()
|
Log.d(tag, "Download library item $libraryItemId to folder $localFolderId")
|
||||||
var title = call.data.getString("title", "Audiobook").toString()
|
|
||||||
var filename = call.data.getString("filename", "audiobook.mp3").toString()
|
|
||||||
var coverFilename = call.data.getString("coverFilename", "cover.png").toString()
|
|
||||||
var downloadFolderUrl = call.data.getString("downloadFolderUrl", "").toString()
|
|
||||||
var folder = DocumentFileCompat.fromUri(context, Uri.parse(downloadFolderUrl))!!
|
|
||||||
Log.d(tag, "Called download: $url | Folder: ${folder.name} | $downloadFolderUrl")
|
|
||||||
|
|
||||||
var dlfilename = audiobookId + "." + File(filename).extension
|
apiHandler.getLibraryItem(libraryItemId) { libraryItem ->
|
||||||
var coverdlfilename = audiobookId + "." + File(coverFilename).extension
|
Log.d(tag, "Got library item from server ${libraryItem.id}")
|
||||||
Log.d(tag, "DL Filename $dlfilename | Cover DL Filename $coverdlfilename")
|
var localFolder = DeviceManager.dbManager.getLocalFolder(localFolderId)
|
||||||
|
if (localFolder != null) {
|
||||||
var canWriteToFolder = folder.canWrite()
|
startLibraryItemDownload(libraryItem, localFolder)
|
||||||
if (!canWriteToFolder) {
|
call.resolve()
|
||||||
Log.e(tag, "Error Cannot Write to Folder ${folder.baseName}")
|
}
|
||||||
val ret = JSObject()
|
|
||||||
ret.put("error", "Cannot write to ${folder.baseName}")
|
|
||||||
call.resolve(ret)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var dlRequest = DownloadManager.Request(Uri.parse(url))
|
call.resolve(JSObject("{\"error\":\"Library Item not found\"}"))
|
||||||
dlRequest.setTitle("Ab: $title")
|
|
||||||
dlRequest.setDescription("Downloading to ${folder.name}")
|
|
||||||
dlRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
|
||||||
dlRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, dlfilename)
|
|
||||||
|
|
||||||
var audiobookDownloadId = downloadManager.enqueue(dlRequest)
|
|
||||||
var coverDownloadId:Long? = null
|
|
||||||
|
|
||||||
if (coverDownloadUrl != "") {
|
|
||||||
var coverDlRequest = DownloadManager.Request(Uri.parse(coverDownloadUrl))
|
|
||||||
coverDlRequest.setTitle("Cover: $title")
|
|
||||||
coverDlRequest.setDescription("Downloading to ${folder.name}")
|
|
||||||
coverDlRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION)
|
|
||||||
coverDlRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, coverdlfilename)
|
|
||||||
coverDownloadId = downloadManager.enqueue(coverDlRequest)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var progressReceiver : (id:Long, prog: Long) -> Unit = { id:Long, prog: Long ->
|
// Clean folder path so it can be used in URL
|
||||||
if (id == audiobookDownloadId) {
|
fun cleanRelPath(relPath: String): String {
|
||||||
|
var cleanedRelPath = relPath.replace("\\", "/").replace("%", "%25").replace("#", "%23")
|
||||||
|
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
|
||||||
|
fun getFilenameFromRelPath(relPath: String): String {
|
||||||
|
var cleanedRelPath = relPath.replace("\\", "_").replace("/", "_")
|
||||||
|
return if (cleanedRelPath.startsWith("_")) cleanedRelPath.substring(1) else cleanedRelPath
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getAbMetadataText(libraryItem:LibraryItem):String {
|
||||||
|
var bookMedia = libraryItem.media as com.audiobookshelf.app.data.Book
|
||||||
|
var fileString = ";ABMETADATA1\n"
|
||||||
|
fileString += "#libraryItemId=${libraryItem.id}\n"
|
||||||
|
fileString += "title=${bookMedia.metadata.title}\n"
|
||||||
|
fileString += "author=${bookMedia.metadata.authorName}\n"
|
||||||
|
fileString += "narrator=${bookMedia.metadata.narratorName}\n"
|
||||||
|
fileString += "series=${bookMedia.metadata.seriesName}\n"
|
||||||
|
return fileString
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startLibraryItemDownload(libraryItem: LibraryItem, localFolder: LocalFolder) {
|
||||||
|
if (libraryItem.mediaType == "book") {
|
||||||
|
var bookMedia = libraryItem.media as com.audiobookshelf.app.data.Book
|
||||||
|
var bookTitle = bookMedia.metadata.title
|
||||||
|
var tracks = bookMedia.tracks ?: mutableListOf()
|
||||||
|
Log.d(tag, "Starting library item download with ${tracks.size} tracks")
|
||||||
|
var downloadItem = DownloadItem(libraryItem.id, localFolder, bookTitle, mutableListOf())
|
||||||
|
var itemFolderPath = localFolder.absolutePath + "/" + bookTitle
|
||||||
|
tracks.forEach { audioFile ->
|
||||||
|
var serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioFile.metadata.relPath)}"
|
||||||
|
var destinationFilename = getFilenameFromRelPath(audioFile.metadata.relPath)
|
||||||
|
Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioFile.metadata.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}")
|
||||||
|
var destinationFile = File("$itemFolderPath/$destinationFilename")
|
||||||
|
var destinationUri = Uri.fromFile(destinationFile)
|
||||||
|
var downloadUri = Uri.parse("${apiHandler.serverUrl}${serverPath}?token=${apiHandler.token}")
|
||||||
|
Log.d(tag, "Audio File Destination Uri $destinationUri | Download URI $downloadUri")
|
||||||
|
var downloadItemPart = DownloadItemPart(UUID.randomUUID().toString(), destinationFilename, bookTitle, serverPath, localFolder.name
|
||||||
|
?: "", localFolder.id, downloadUri, destinationUri, null, 0)
|
||||||
|
|
||||||
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
|
|
||||||
|
var dlRequest = downloadItemPart.getDownloadRequest()
|
||||||
|
var downloadId = downloadManager.enqueue(dlRequest)
|
||||||
|
downloadItemPart.downloadId = downloadId
|
||||||
|
}
|
||||||
|
Log.d(tag, "Done queueing downloads ${downloadQueue.size}")
|
||||||
|
if (downloadItem.downloadItemParts.isNotEmpty()) {
|
||||||
|
// 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)
|
||||||
|
startWatchingDownloads(downloadItem)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// TODO: Download podcast episode(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startWatchingDownloads(downloadItem: DownloadItem) {
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
while (downloadItem.downloadItemParts.isNotEmpty()) {
|
||||||
|
checkDownloads(downloadItem)
|
||||||
|
notifyListeners("onItemDownloadUpdate", JSObject(jacksonObjectMapper().writeValueAsString(downloadItem)))
|
||||||
|
delay(500)
|
||||||
|
}
|
||||||
|
|
||||||
|
var folderScanResult = folderScanner.scanForMediaItems(downloadItem.localFolder, false)
|
||||||
|
|
||||||
|
Log.d(tag, "Item download complete ${downloadItem.itemTitle}")
|
||||||
var jsobj = JSObject()
|
var jsobj = JSObject()
|
||||||
jsobj.put("audiobookId", audiobookId)
|
jsobj.put("libraryItemId", downloadItem.id)
|
||||||
jsobj.put("progress", prog)
|
jsobj.put("localFolderId", downloadItem.localFolder.id)
|
||||||
notifyListeners("onDownloadProgress", jsobj)
|
jsobj.put("folderScanResult", JSObject(jacksonObjectMapper().writeValueAsString(folderScanResult)))
|
||||||
|
notifyListeners("onItemDownloadComplete", jsobj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var coverDocFile:DocumentFile? = null
|
fun checkDownloads(downloadItem: DownloadItem) {
|
||||||
|
var itemParts = downloadItem.downloadItemParts.map { it }
|
||||||
var doneReceiver : (id:Long, success: Boolean) -> Unit = { id:Long, success: Boolean ->
|
Log.d(tag, "Check Downloads ${itemParts.size}")
|
||||||
Log.d(tag, "RECEIVER DONE $id, SUCCES? $success")
|
for (downloadItemPart in itemParts) {
|
||||||
var docfile:DocumentFile? = null
|
if (downloadItemPart.downloadId != null) {
|
||||||
|
var dlid = downloadItemPart.downloadId!!
|
||||||
// Download was complete, now find downloaded file
|
val query = DownloadManager.Query().setFilterById(dlid)
|
||||||
if (id == coverDownloadId) {
|
downloadManager.query(query).use {
|
||||||
docfile = DocumentFileCompat.fromPublicFolder(context, PublicDirectory.DOWNLOADS, coverdlfilename)
|
|
||||||
Log.d(tag, "Move Cover File ${docfile?.name}")
|
|
||||||
|
|
||||||
// For unknown reason, Android 10 test was using the title set in "setTitle" for the dl manager as the filename
|
|
||||||
// check if this was the case
|
|
||||||
if (docfile?.name == null) {
|
|
||||||
docfile = DocumentFileCompat.fromPublicFolder(context, PublicDirectory.DOWNLOADS, "Cover: $title")
|
|
||||||
Log.d(tag, "Cover File name attempt 2 ${docfile?.name}")
|
|
||||||
}
|
|
||||||
} else if (id == audiobookDownloadId) {
|
|
||||||
docfile = DocumentFileCompat.fromPublicFolder(context, PublicDirectory.DOWNLOADS, dlfilename)
|
|
||||||
Log.d(tag, "Move Audiobook File ${docfile?.name}")
|
|
||||||
|
|
||||||
if (docfile?.name == null) {
|
|
||||||
docfile = DocumentFileCompat.fromPublicFolder(context, PublicDirectory.DOWNLOADS, "Ab: $title")
|
|
||||||
Log.d(tag, "File name attempt 2 ${docfile?.name}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback for moving the downloaded file
|
|
||||||
var callback = object : FileCallback() {
|
|
||||||
override fun onPrepare() {
|
|
||||||
Log.d(tag, "PREPARING MOVE FILE")
|
|
||||||
}
|
|
||||||
override fun onFailed(errorCode:ErrorCode) {
|
|
||||||
Log.e(tag, "FAILED MOVE FILE $errorCode")
|
|
||||||
|
|
||||||
docfile?.delete()
|
|
||||||
coverDocFile?.delete()
|
|
||||||
|
|
||||||
if (id == audiobookDownloadId) {
|
|
||||||
var jsobj = JSObject()
|
|
||||||
jsobj.put("audiobookId", audiobookId)
|
|
||||||
jsobj.put("error", "Move failed")
|
|
||||||
notifyListeners("onDownloadFailed", jsobj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override fun onCompleted(result:Any) {
|
|
||||||
var resultDocFile = result as DocumentFile
|
|
||||||
var simplePath = resultDocFile.getSimplePath(context)
|
|
||||||
var storageId = resultDocFile.getStorageId(context)
|
|
||||||
var size = resultDocFile.length()
|
|
||||||
Log.d(tag, "Finished Moving File, NAME: ${resultDocFile.name} | URI:${resultDocFile.uri} | AbsolutePath:${resultDocFile.getAbsolutePath(context)} | $storageId | SimplePath: $simplePath")
|
|
||||||
|
|
||||||
var abFolder = folder.findFolder(title)
|
|
||||||
var jsobj = JSObject()
|
|
||||||
jsobj.put("audiobookId", audiobookId)
|
|
||||||
jsobj.put("downloadId", id)
|
|
||||||
jsobj.put("storageId", storageId)
|
|
||||||
jsobj.put("storageType", resultDocFile.getStorageType(context))
|
|
||||||
jsobj.put("folderUrl", abFolder?.uri)
|
|
||||||
jsobj.put("folderName", abFolder?.name)
|
|
||||||
jsobj.put("downloadFolderUrl", downloadFolderUrl)
|
|
||||||
jsobj.put("contentUrl", resultDocFile.uri)
|
|
||||||
jsobj.put("basePath", resultDocFile.getBasePath(context))
|
|
||||||
jsobj.put("filename", filename)
|
|
||||||
jsobj.put("simplePath", simplePath)
|
|
||||||
jsobj.put("size", size)
|
|
||||||
|
|
||||||
if (resultDocFile.name == filename) {
|
|
||||||
Log.d(tag, "Audiobook Finishing Moving")
|
|
||||||
} else if (resultDocFile.name == coverFilename) {
|
|
||||||
coverDocFile = docfile
|
|
||||||
Log.d(tag, "Audiobook Cover Finished Moving")
|
|
||||||
jsobj.put("isCover", true)
|
|
||||||
}
|
|
||||||
notifyListeners("onDownloadComplete", jsobj)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// After file is downloaded, move the files into an audiobook directory inside the user selected folder
|
|
||||||
if (id == coverDownloadId) {
|
|
||||||
docfile?.moveFileTo(context, folder, FileDescription(coverFilename, title, MimeType.IMAGE), callback)
|
|
||||||
} else if (id == audiobookDownloadId) {
|
|
||||||
docfile?.moveFileTo(context, folder, FileDescription(filename, title, MimeType.AUDIO), callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var progressUpdater = DownloadProgressUpdater(downloadManager, audiobookDownloadId, progressReceiver, doneReceiver)
|
|
||||||
progressUpdater.run()
|
|
||||||
if (coverDownloadId != null) {
|
|
||||||
var coverProgressUpdater = DownloadProgressUpdater(downloadManager, coverDownloadId, progressReceiver, doneReceiver)
|
|
||||||
coverProgressUpdater.run()
|
|
||||||
}
|
|
||||||
|
|
||||||
val ret = JSObject()
|
|
||||||
ret.put("audiobookDownloadId", audiobookDownloadId)
|
|
||||||
ret.put("coverDownloadId", coverDownloadId)
|
|
||||||
call.resolve(ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class DownloadProgressUpdater(private val manager: DownloadManager, private val downloadId: Long, private var receiver: (Long, Long) -> Unit, private var doneReceiver: (Long, Boolean) -> Unit) : Thread() {
|
|
||||||
private val query: DownloadManager.Query = DownloadManager.Query()
|
|
||||||
private var totalBytes: Int = 0
|
|
||||||
private var TAG = "DownloadProgressUpdater"
|
|
||||||
|
|
||||||
init {
|
|
||||||
query.setFilterById(this.downloadId)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun run() {
|
|
||||||
Log.d(TAG, "RUN FOR ID $downloadId")
|
|
||||||
var keepRunning = true
|
|
||||||
var increment = 0
|
|
||||||
while (keepRunning) {
|
|
||||||
Thread.sleep(500)
|
|
||||||
increment++
|
|
||||||
|
|
||||||
if (increment % 4 == 0) {
|
|
||||||
Log.d(TAG, "Loop $increment : $downloadId")
|
|
||||||
}
|
|
||||||
|
|
||||||
manager.query(query).use {
|
|
||||||
if (it.moveToFirst()) {
|
if (it.moveToFirst()) {
|
||||||
//get total bytes of the file
|
val totalBytes = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
|
||||||
if (totalBytes <= 0) {
|
Log.d(tag, "Download ${downloadItemPart.name} bytes $totalBytes")
|
||||||
totalBytes = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
|
|
||||||
if (totalBytes <= 0) {
|
|
||||||
Log.e(TAG, "Download Is 0 Bytes $downloadId")
|
|
||||||
doneReceiver(downloadId, false)
|
|
||||||
keepRunning = false
|
|
||||||
this.interrupt()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val downloadStatus = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_STATUS))
|
val downloadStatus = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_STATUS))
|
||||||
val bytesDownloadedSoFar = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
|
val bytesDownloadedSoFar = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
|
||||||
|
|
||||||
if (increment % 4 == 0) {
|
|
||||||
Log.d(TAG, "BYTES $increment : $downloadId : $bytesDownloadedSoFar : TOTAL: $totalBytes")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL || downloadStatus == DownloadManager.STATUS_FAILED) {
|
|
||||||
if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL) {
|
if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL) {
|
||||||
doneReceiver(downloadId, true)
|
Log.d(tag, "Download ${downloadItemPart.name} Done")
|
||||||
} else {
|
downloadItem.downloadItemParts.remove(downloadItemPart)
|
||||||
doneReceiver(downloadId, false)
|
} else if (downloadStatus == DownloadManager.STATUS_FAILED) {
|
||||||
}
|
Log.d(tag, "Download ${downloadItemPart.name} Failed")
|
||||||
keepRunning = false
|
downloadItem.downloadItemParts.remove(downloadItemPart)
|
||||||
this.interrupt()
|
|
||||||
} else {
|
} else {
|
||||||
//update progress
|
//update progress
|
||||||
val percentProgress = ((bytesDownloadedSoFar * 100L) / totalBytes)
|
val percentProgress = if (totalBytes > 0) ((bytesDownloadedSoFar * 100L) / totalBytes) else 0
|
||||||
receiver(downloadId, percentProgress)
|
Log.d(tag, "${downloadItemPart.name} Progress = $percentProgress%")
|
||||||
|
downloadItemPart.progress = percentProgress
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Log.e(TAG, "NOT FOUND IN QUERY")
|
Log.d(tag, "Download ${downloadItemPart.name} not found in dlmanager")
|
||||||
keepRunning = false
|
downloadItem.downloadItemParts.remove(downloadItemPart)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//
|
||||||
|
// @PluginMethod
|
||||||
|
// fun download(call: PluginCall) {
|
||||||
|
// var audiobookId = call.data.getString("audiobookId", "audiobook").toString()
|
||||||
|
// var url = call.data.getString("downloadUrl", "unknown").toString()
|
||||||
|
// var coverDownloadUrl = call.data.getString("coverDownloadUrl", "").toString()
|
||||||
|
// var title = call.data.getString("title", "Audiobook").toString()
|
||||||
|
// var filename = call.data.getString("filename", "audiobook.mp3").toString()
|
||||||
|
// var coverFilename = call.data.getString("coverFilename", "cover.png").toString()
|
||||||
|
// var downloadFolderUrl = call.data.getString("downloadFolderUrl", "").toString()
|
||||||
|
// var folder = DocumentFileCompat.fromUri(context, Uri.parse(downloadFolderUrl))!!
|
||||||
|
// Log.d(tag, "Called download: $url | Folder: ${folder.name} | $downloadFolderUrl")
|
||||||
|
//
|
||||||
|
// var dlfilename = audiobookId + "." + File(filename).extension
|
||||||
|
// var coverdlfilename = audiobookId + "." + File(coverFilename).extension
|
||||||
|
// Log.d(tag, "DL Filename $dlfilename | Cover DL Filename $coverdlfilename")
|
||||||
|
//
|
||||||
|
// var canWriteToFolder = folder.canWrite()
|
||||||
|
// if (!canWriteToFolder) {
|
||||||
|
// Log.e(tag, "Error Cannot Write to Folder ${folder.baseName}")
|
||||||
|
// val ret = JSObject()
|
||||||
|
// ret.put("error", "Cannot write to ${folder.baseName}")
|
||||||
|
// call.resolve(ret)
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var dlRequest = DownloadManager.Request(Uri.parse(url))
|
||||||
|
// dlRequest.setTitle("Ab: $title")
|
||||||
|
// dlRequest.setDescription("Downloading to ${folder.name}")
|
||||||
|
// dlRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED)
|
||||||
|
// dlRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, dlfilename)
|
||||||
|
//
|
||||||
|
// var audiobookDownloadId = downloadManager.enqueue(dlRequest)
|
||||||
|
// var coverDownloadId:Long? = null
|
||||||
|
//
|
||||||
|
// if (coverDownloadUrl != "") {
|
||||||
|
// var coverDlRequest = DownloadManager.Request(Uri.parse(coverDownloadUrl))
|
||||||
|
// coverDlRequest.setTitle("Cover: $title")
|
||||||
|
// coverDlRequest.setDescription("Downloading to ${folder.name}")
|
||||||
|
// coverDlRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_ONLY_COMPLETION)
|
||||||
|
// coverDlRequest.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, coverdlfilename)
|
||||||
|
// coverDownloadId = downloadManager.enqueue(coverDlRequest)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var progressReceiver : (id:Long, prog: Long) -> Unit = { id:Long, prog: Long ->
|
||||||
|
// if (id == audiobookDownloadId) {
|
||||||
|
// var jsobj = JSObject()
|
||||||
|
// jsobj.put("audiobookId", audiobookId)
|
||||||
|
// jsobj.put("progress", prog)
|
||||||
|
// notifyListeners("onDownloadProgress", jsobj)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var coverDocFile:DocumentFile? = null
|
||||||
|
//
|
||||||
|
// var doneReceiver : (id:Long, success: Boolean) -> Unit = { id:Long, success: Boolean ->
|
||||||
|
// Log.d(tag, "RECEIVER DONE $id, SUCCES? $success")
|
||||||
|
// var docfile:DocumentFile? = null
|
||||||
|
//
|
||||||
|
// // Download was complete, now find downloaded file
|
||||||
|
// if (id == coverDownloadId) {
|
||||||
|
// docfile = DocumentFileCompat.fromPublicFolder(context, PublicDirectory.DOWNLOADS, coverdlfilename)
|
||||||
|
// Log.d(tag, "Move Cover File ${docfile?.name}")
|
||||||
|
//
|
||||||
|
// // For unknown reason, Android 10 test was using the title set in "setTitle" for the dl manager as the filename
|
||||||
|
// // check if this was the case
|
||||||
|
// if (docfile?.name == null) {
|
||||||
|
// docfile = DocumentFileCompat.fromPublicFolder(context, PublicDirectory.DOWNLOADS, "Cover: $title")
|
||||||
|
// Log.d(tag, "Cover File name attempt 2 ${docfile?.name}")
|
||||||
|
// }
|
||||||
|
// } else if (id == audiobookDownloadId) {
|
||||||
|
// docfile = DocumentFileCompat.fromPublicFolder(context, PublicDirectory.DOWNLOADS, dlfilename)
|
||||||
|
// Log.d(tag, "Move Audiobook File ${docfile?.name}")
|
||||||
|
//
|
||||||
|
// if (docfile?.name == null) {
|
||||||
|
// docfile = DocumentFileCompat.fromPublicFolder(context, PublicDirectory.DOWNLOADS, "Ab: $title")
|
||||||
|
// Log.d(tag, "File name attempt 2 ${docfile?.name}")
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // Callback for moving the downloaded file
|
||||||
|
// var callback = object : FileCallback() {
|
||||||
|
// override fun onPrepare() {
|
||||||
|
// Log.d(tag, "PREPARING MOVE FILE")
|
||||||
|
// }
|
||||||
|
// override fun onFailed(errorCode:ErrorCode) {
|
||||||
|
// Log.e(tag, "FAILED MOVE FILE $errorCode")
|
||||||
|
//
|
||||||
|
// docfile?.delete()
|
||||||
|
// coverDocFile?.delete()
|
||||||
|
//
|
||||||
|
// if (id == audiobookDownloadId) {
|
||||||
|
// var jsobj = JSObject()
|
||||||
|
// jsobj.put("audiobookId", audiobookId)
|
||||||
|
// jsobj.put("error", "Move failed")
|
||||||
|
// notifyListeners("onDownloadFailed", jsobj)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// override fun onCompleted(result:Any) {
|
||||||
|
// var resultDocFile = result as DocumentFile
|
||||||
|
// var simplePath = resultDocFile.getSimplePath(context)
|
||||||
|
// var storageId = resultDocFile.getStorageId(context)
|
||||||
|
// var size = resultDocFile.length()
|
||||||
|
// Log.d(tag, "Finished Moving File, NAME: ${resultDocFile.name} | URI:${resultDocFile.uri} | AbsolutePath:${resultDocFile.getAbsolutePath(context)} | $storageId | SimplePath: $simplePath")
|
||||||
|
//
|
||||||
|
// var abFolder = folder.findFolder(title)
|
||||||
|
// var jsobj = JSObject()
|
||||||
|
// jsobj.put("audiobookId", audiobookId)
|
||||||
|
// jsobj.put("downloadId", id)
|
||||||
|
// jsobj.put("storageId", storageId)
|
||||||
|
// jsobj.put("storageType", resultDocFile.getStorageType(context))
|
||||||
|
// jsobj.put("folderUrl", abFolder?.uri)
|
||||||
|
// jsobj.put("folderName", abFolder?.name)
|
||||||
|
// jsobj.put("downloadFolderUrl", downloadFolderUrl)
|
||||||
|
// jsobj.put("contentUrl", resultDocFile.uri)
|
||||||
|
// jsobj.put("basePath", resultDocFile.getBasePath(context))
|
||||||
|
// jsobj.put("filename", filename)
|
||||||
|
// jsobj.put("simplePath", simplePath)
|
||||||
|
// jsobj.put("size", size)
|
||||||
|
//
|
||||||
|
// if (resultDocFile.name == filename) {
|
||||||
|
// Log.d(tag, "Audiobook Finishing Moving")
|
||||||
|
// } else if (resultDocFile.name == coverFilename) {
|
||||||
|
// coverDocFile = docfile
|
||||||
|
// Log.d(tag, "Audiobook Cover Finished Moving")
|
||||||
|
// jsobj.put("isCover", true)
|
||||||
|
// }
|
||||||
|
// notifyListeners("onDownloadComplete", jsobj)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // After file is downloaded, move the files into an audiobook directory inside the user selected folder
|
||||||
|
// if (id == coverDownloadId) {
|
||||||
|
// docfile?.moveFileTo(context, folder, FileDescription(coverFilename, title, MimeType.IMAGE), callback)
|
||||||
|
// } else if (id == audiobookDownloadId) {
|
||||||
|
// docfile?.moveFileTo(context, folder, FileDescription(filename, title, MimeType.AUDIO), callback)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var progressUpdater = DownloadProgressUpdater(downloadManager, audiobookDownloadId, progressReceiver, doneReceiver)
|
||||||
|
// progressUpdater.run()
|
||||||
|
// if (coverDownloadId != null) {
|
||||||
|
// var coverProgressUpdater = DownloadProgressUpdater(downloadManager, coverDownloadId, progressReceiver, doneReceiver)
|
||||||
|
// coverProgressUpdater.run()
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val ret = JSObject()
|
||||||
|
// ret.put("audiobookDownloadId", audiobookDownloadId)
|
||||||
|
// ret.put("coverDownloadId", coverDownloadId)
|
||||||
|
// call.resolve(ret)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
//
|
||||||
|
//internal class DownloadProgressUpdater(private val manager: DownloadManager, private val downloadId: Long, private var receiver: (Long, Long) -> Unit, private var doneReceiver: (Long, Boolean) -> Unit) : Thread() {
|
||||||
|
// private val query: DownloadManager.Query = DownloadManager.Query()
|
||||||
|
// private var totalBytes: Int = 0
|
||||||
|
// private var TAG = "DownloadProgressUpdater"
|
||||||
|
//
|
||||||
|
// init {
|
||||||
|
// query.setFilterById(this.downloadId)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// override fun run() {
|
||||||
|
// Log.d(TAG, "RUN FOR ID $downloadId")
|
||||||
|
// var keepRunning = true
|
||||||
|
// var increment = 0
|
||||||
|
// while (keepRunning) {
|
||||||
|
// Thread.sleep(500)
|
||||||
|
// increment++
|
||||||
|
//
|
||||||
|
// if (increment % 4 == 0) {
|
||||||
|
// Log.d(TAG, "Loop $increment : $downloadId")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// manager.query(query).use {
|
||||||
|
// if (it.moveToFirst()) {
|
||||||
|
// //get total bytes of the file
|
||||||
|
// if (totalBytes <= 0) {
|
||||||
|
// totalBytes = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES))
|
||||||
|
// if (totalBytes <= 0) {
|
||||||
|
// Log.e(TAG, "Download Is 0 Bytes $downloadId")
|
||||||
|
// doneReceiver(downloadId, false)
|
||||||
|
// keepRunning = false
|
||||||
|
// this.interrupt()
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// val downloadStatus = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_STATUS))
|
||||||
|
// val bytesDownloadedSoFar = it.getInt(it.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR))
|
||||||
|
//
|
||||||
|
// if (increment % 4 == 0) {
|
||||||
|
// Log.d(TAG, "BYTES $increment : $downloadId : $bytesDownloadedSoFar : TOTAL: $totalBytes")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL || downloadStatus == DownloadManager.STATUS_FAILED) {
|
||||||
|
// if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL) {
|
||||||
|
// doneReceiver(downloadId, true)
|
||||||
|
// } else {
|
||||||
|
// doneReceiver(downloadId, false)
|
||||||
|
// }
|
||||||
|
// keepRunning = false
|
||||||
|
// this.interrupt()
|
||||||
|
// } else {
|
||||||
|
// //update progress
|
||||||
|
// val percentProgress = ((bytesDownloadedSoFar * 100L) / totalBytes)
|
||||||
|
// receiver(downloadId, percentProgress)
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
|
// Log.e(TAG, "NOT FOUND IN QUERY")
|
||||||
|
// keepRunning = false
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
|
@ -52,6 +52,9 @@ class MainActivity : BridgeActivity() {
|
||||||
|
|
||||||
Log.d(tag, "onCreate")
|
Log.d(tag, "onCreate")
|
||||||
|
|
||||||
|
// var ss = SimpleStorage(this)
|
||||||
|
// ss.requestFullStorageAccess()
|
||||||
|
|
||||||
var permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
var permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||||
ActivityCompat.requestPermissions(this,
|
ActivityCompat.requestPermissions(this,
|
||||||
|
|
|
@ -381,10 +381,10 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
.setMediaId(currentPlaybackSession!!.id)
|
.setMediaId(currentPlaybackSession!!.id)
|
||||||
.setTitle(currentPlaybackSession!!.displayTitle)
|
.setTitle(currentPlaybackSession!!.displayTitle)
|
||||||
.setSubtitle(currentPlaybackSession!!.displayAuthor)
|
.setSubtitle(currentPlaybackSession!!.displayAuthor)
|
||||||
.setMediaUri(currentPlaybackSession!!.getContentUri())
|
|
||||||
.setIconUri(currentPlaybackSession!!.getCoverUri())
|
.setIconUri(currentPlaybackSession!!.getCoverUri())
|
||||||
return builder.build()
|
return builder.build()
|
||||||
}
|
}
|
||||||
|
// .setMediaUri(currentPlaybackSession!!.getContentUri())
|
||||||
}
|
}
|
||||||
|
|
||||||
val myPlaybackPreparer:MediaSessionConnector.PlaybackPreparer = object :MediaSessionConnector.PlaybackPreparer {
|
val myPlaybackPreparer:MediaSessionConnector.PlaybackPreparer = object :MediaSessionConnector.PlaybackPreparer {
|
||||||
|
@ -661,7 +661,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
User callable methods
|
User callable methods
|
||||||
*/
|
*/
|
||||||
|
@ -672,28 +671,44 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
|
|
||||||
var metadata = playbackSession.getMediaMetadataCompat()
|
var metadata = playbackSession.getMediaMetadataCompat()
|
||||||
mediaSession.setMetadata(metadata)
|
mediaSession.setMetadata(metadata)
|
||||||
var mediaMetadata = playbackSession.getExoMediaMetadata()
|
|
||||||
|
|
||||||
var mediaUri = playbackSession.getContentUri()
|
var mediaItems = playbackSession.getMediaItems()
|
||||||
var mimeType = playbackSession.getMimeType()
|
|
||||||
var mediaItem = MediaItem.Builder().setUri(mediaUri).setMediaMetadata(mediaMetadata).setMimeType(mimeType).build()
|
|
||||||
|
|
||||||
if (mPlayer == currentPlayer) {
|
if (mPlayer == currentPlayer) {
|
||||||
var mediaSource:MediaSource
|
var mediaSource:MediaSource
|
||||||
|
|
||||||
if (!playbackSession.isHLS) {
|
if (playbackSession.isLocal) {
|
||||||
Log.d(tag, "Playing Local File")
|
Log.d(tag, "Playing Local Item")
|
||||||
var dataSourceFactory = DefaultDataSourceFactory(ctx, channelId)
|
var dataSourceFactory = DefaultDataSourceFactory(ctx, channelId)
|
||||||
mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
|
mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItems[0])
|
||||||
|
} else if (!playbackSession.isHLS) {
|
||||||
|
Log.d(tag, "Direct Playing Item")
|
||||||
|
var dataSourceFactory = DefaultDataSourceFactory(ctx, channelId)
|
||||||
|
mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItems[0])
|
||||||
} else {
|
} else {
|
||||||
Log.d(tag, "Playing HLS File")
|
Log.d(tag, "Playing HLS Item")
|
||||||
var dataSourceFactory = DefaultHttpDataSource.Factory()
|
var dataSourceFactory = DefaultHttpDataSource.Factory()
|
||||||
dataSourceFactory.setUserAgent(channelId)
|
dataSourceFactory.setUserAgent(channelId)
|
||||||
dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${playbackSession.token}"))
|
dataSourceFactory.setDefaultRequestProperties(hashMapOf("Authorization" to "Bearer ${playbackSession.token}"))
|
||||||
mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
|
mediaSource = HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItems[0])
|
||||||
}
|
}
|
||||||
Log.d(tag, "Playback Session CURRENT TIME ${playbackSession.currentTime} | ${playbackSession.currentTimeMs}")
|
mPlayer.setMediaSource(mediaSource)
|
||||||
mPlayer.setMediaSource(mediaSource, playbackSession.currentTimeMs)
|
|
||||||
|
// Add remaining media items if multi-track
|
||||||
|
if (mediaItems.size > 1) {
|
||||||
|
mPlayer.addMediaItems(mediaItems.subList(1, mediaItems.size))
|
||||||
|
Log.d(tag, "mPlayer total media items ${mPlayer.mediaItemCount}")
|
||||||
|
|
||||||
|
var currentTrackIndex = playbackSession.getCurrentTrackIndex()
|
||||||
|
var currentTrackTime = playbackSession.getCurrentTrackTimeMs()
|
||||||
|
Log.d(tag, "mPlayer current track index $currentTrackIndex & current track time $currentTrackTime")
|
||||||
|
mPlayer.seekTo(currentTrackIndex, currentTrackTime)
|
||||||
|
} else {
|
||||||
|
mPlayer.seekTo(playbackSession.currentTimeMs)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} else if (castPlayer != null) {
|
} else if (castPlayer != null) {
|
||||||
//// var mediaQueue = currentAudiobookStreamData!!.getCastQueue()
|
//// var mediaQueue = currentAudiobookStreamData!!.getCastQueue()
|
||||||
// // TODO: Start position will need to be adjusted if using multi-track queue
|
// // TODO: Start position will need to be adjusted if using multi-track queue
|
||||||
|
@ -784,12 +799,24 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCurrentTime() : Long {
|
fun getCurrentTime() : Long {
|
||||||
|
if (currentPlayer.mediaItemCount > 1) {
|
||||||
|
var windowIndex = currentPlayer.currentWindowIndex
|
||||||
|
var currentTrackStartOffset = currentPlaybackSession?.getTrackStartOffsetMs(windowIndex) ?: 0L
|
||||||
|
return currentPlayer.currentPosition + currentTrackStartOffset
|
||||||
|
} else {
|
||||||
return currentPlayer.currentPosition
|
return currentPlayer.currentPosition
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getBufferedTime() : Long {
|
fun getBufferedTime() : Long {
|
||||||
|
if (currentPlayer.mediaItemCount > 1) {
|
||||||
|
var windowIndex = currentPlayer.currentWindowIndex
|
||||||
|
var currentTrackStartOffset = currentPlaybackSession?.getTrackStartOffsetMs(windowIndex) ?: 0L
|
||||||
|
return currentPlayer.bufferedPosition + currentTrackStartOffset
|
||||||
|
} else {
|
||||||
return currentPlayer.bufferedPosition
|
return currentPlayer.bufferedPosition
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getTheLastPauseTime() : Long {
|
fun getTheLastPauseTime() : Long {
|
||||||
return lastPauseTime
|
return lastPauseTime
|
||||||
|
@ -867,8 +894,15 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun seekPlayer(time: Long) {
|
fun seekPlayer(time: Long) {
|
||||||
|
if (currentPlayer.mediaItemCount > 1) {
|
||||||
|
currentPlaybackSession?.currentTime = time / 1000.0
|
||||||
|
var newWindowIndex = currentPlaybackSession?.getCurrentTrackIndex() ?: 0
|
||||||
|
var newTimeOffset = currentPlaybackSession?.getCurrentTrackTimeMs() ?: 0
|
||||||
|
currentPlayer.seekTo(newWindowIndex, newTimeOffset)
|
||||||
|
} else {
|
||||||
currentPlayer.seekTo(time)
|
currentPlayer.seekTo(time)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun seekForward(amount: Long) {
|
fun seekForward(amount: Long) {
|
||||||
currentPlayer.seekTo(mPlayer.currentPosition + amount)
|
currentPlayer.seekTo(mPlayer.currentPosition + amount)
|
||||||
|
@ -883,9 +917,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun terminateStream() {
|
fun terminateStream() {
|
||||||
// if (currentPlayer.playbackState == Player.STATE_READY) {
|
|
||||||
// currentPlayer.clearMediaItems()
|
|
||||||
// }
|
|
||||||
currentPlayer.clearMediaItems()
|
currentPlayer.clearMediaItems()
|
||||||
currentPlaybackSession = null
|
currentPlaybackSession = null
|
||||||
lastPauseTime = 0
|
lastPauseTime = 0
|
||||||
|
@ -894,15 +925,13 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
|
|
||||||
fun sendClientMetadata(stateName: String) {
|
fun sendClientMetadata(stateName: String) {
|
||||||
var metadata = JSObject()
|
var metadata = JSObject()
|
||||||
var duration = mPlayer.duration
|
var duration = currentPlaybackSession?.getTotalDuration() ?: 0
|
||||||
if (duration < 0) duration = 0
|
|
||||||
metadata.put("duration", duration)
|
metadata.put("duration", duration)
|
||||||
metadata.put("currentTime", mPlayer.currentPosition)
|
metadata.put("currentTime", getCurrentTime())
|
||||||
metadata.put("stateName", stateName)
|
metadata.put("stateName", stateName)
|
||||||
clientEventEmitter?.onMetadata(metadata)
|
clientEventEmitter?.onMetadata(metadata)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// MEDIA BROWSER STUFF (ANDROID AUTO)
|
// MEDIA BROWSER STUFF (ANDROID AUTO)
|
||||||
//
|
//
|
||||||
|
|
|
@ -47,7 +47,10 @@ data class Book(
|
||||||
var coverPath:String?,
|
var coverPath:String?,
|
||||||
var tags:MutableList<String>,
|
var tags:MutableList<String>,
|
||||||
var audioFiles:MutableList<AudioFile>,
|
var audioFiles:MutableList<AudioFile>,
|
||||||
var chapters:MutableList<BookChapter>
|
var chapters:MutableList<BookChapter>,
|
||||||
|
var tracks:MutableList<AudioFile>?,
|
||||||
|
var size:Long?,
|
||||||
|
var duration:Double?
|
||||||
) : MediaType()
|
) : MediaType()
|
||||||
|
|
||||||
// This auto-detects whether it is a Book or Podcast
|
// This auto-detects whether it is a Book or Podcast
|
||||||
|
@ -154,7 +157,15 @@ data class AudioTrack(
|
||||||
var isLocal:Boolean,
|
var isLocal:Boolean,
|
||||||
var localFileId:String?,
|
var localFileId:String?,
|
||||||
var audioProbeResult:AudioProbeResult?
|
var audioProbeResult:AudioProbeResult?
|
||||||
)
|
) {
|
||||||
|
|
||||||
|
@get:JsonIgnore
|
||||||
|
val startOffsetMs get() = (startOffset * 1000L).toLong()
|
||||||
|
@get:JsonIgnore
|
||||||
|
val durationMs get() = (duration * 1000L).toLong()
|
||||||
|
@get:JsonIgnore
|
||||||
|
val endOffsetMs get() = startOffsetMs + durationMs
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
data class BookChapter(
|
data class BookChapter(
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package com.audiobookshelf.app.data
|
package com.audiobookshelf.app.data
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.getcapacitor.JSObject
|
import com.getcapacitor.JSObject
|
||||||
import com.getcapacitor.Plugin
|
import com.getcapacitor.Plugin
|
||||||
|
@ -12,6 +13,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
@CapacitorPlugin(name = "DbManager")
|
@CapacitorPlugin(name = "DbManager")
|
||||||
class DbManager : Plugin() {
|
class DbManager : Plugin() {
|
||||||
|
@ -25,14 +27,28 @@ class DbManager : Plugin() {
|
||||||
Paper.book("device").write("data", deviceData)
|
Paper.book("device").write("data", deviceData)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadLocalMediaItems():List<LocalMediaItem> {
|
fun loadLocalMediaItems():MutableList<LocalMediaItem> {
|
||||||
var localMediaItems:MutableList<LocalMediaItem> = mutableListOf()
|
var localMediaItems:MutableList<LocalMediaItem> = mutableListOf()
|
||||||
Paper.book("localMediaItems").allKeys.forEach {
|
Paper.book("localMediaItems").allKeys.forEach {
|
||||||
var localMediaItem:LocalMediaItem? = Paper.book("localMediaItems").read(it)
|
var localMediaItem:LocalMediaItem? = Paper.book("localMediaItems").read(it)
|
||||||
if (localMediaItem != null) {
|
if (localMediaItem != null) {
|
||||||
|
// if (localMediaItem.coverContentUrl != null) {
|
||||||
|
// var file = DocumentFile.fromSingleUri(ctx)
|
||||||
|
// if (!file.exists()) {
|
||||||
|
// Log.e(tag, "Local media item cover url does not exist ${localMediaItem.coverContentUrl}")
|
||||||
|
// removeLocalMediaItem(localMediaItem.id)
|
||||||
|
// } else {
|
||||||
|
// localMediaItems.add(localMediaItem)
|
||||||
|
// }
|
||||||
|
// } else {
|
||||||
localMediaItems.add(localMediaItem)
|
localMediaItems.add(localMediaItem)
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// localMediaItems = localMediaItems.filter {
|
||||||
|
//
|
||||||
|
// file.exists()
|
||||||
|
// }
|
||||||
return localMediaItems
|
return localMediaItems
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,7 +29,8 @@ data class LocalMediaItem(
|
||||||
var absolutePath:String,
|
var absolutePath:String,
|
||||||
var audioTracks:MutableList<AudioTrack>,
|
var audioTracks:MutableList<AudioTrack>,
|
||||||
var localFiles:MutableList<LocalFile>,
|
var localFiles:MutableList<LocalFile>,
|
||||||
var coverContentUrl:String?
|
var coverContentUrl:String?,
|
||||||
|
var coverAbsolutePath:String?
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
|
|
|
@ -2,9 +2,11 @@ package com.audiobookshelf.app.data
|
||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.support.v4.media.MediaMetadataCompat
|
import android.support.v4.media.MediaMetadataCompat
|
||||||
|
import android.util.Log
|
||||||
import com.audiobookshelf.app.R
|
import com.audiobookshelf.app.R
|
||||||
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.google.android.exoplayer2.MediaItem
|
||||||
import com.google.android.exoplayer2.MediaMetadata
|
import com.google.android.exoplayer2.MediaMetadata
|
||||||
|
|
||||||
// TODO: enum or something in kotlin?
|
// TODO: enum or something in kotlin?
|
||||||
|
@ -39,6 +41,37 @@ class PlaybackSession(
|
||||||
val isLocal get() = playMethod == PLAYMETHOD_LOCAL
|
val isLocal get() = playMethod == PLAYMETHOD_LOCAL
|
||||||
val currentTimeMs get() = (currentTime * 1000L).toLong()
|
val currentTimeMs get() = (currentTime * 1000L).toLong()
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun getCurrentTrackIndex():Int {
|
||||||
|
for (i in 0..(audioTracks.size - 1)) {
|
||||||
|
var track = audioTracks[i]
|
||||||
|
if (currentTimeMs >= track.startOffsetMs && (track.endOffsetMs) > currentTimeMs) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return audioTracks.size - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun getCurrentTrackTimeMs():Long {
|
||||||
|
var currentTrack = audioTracks[this.getCurrentTrackIndex()]
|
||||||
|
var time = currentTime - currentTrack.startOffset
|
||||||
|
return (time * 1000L).toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun getTrackStartOffsetMs(index:Int):Long {
|
||||||
|
var currentTrack = audioTracks[index]
|
||||||
|
return (currentTrack.startOffset * 1000L).toLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun getTotalDuration():Double {
|
||||||
|
var total = 0.0
|
||||||
|
audioTracks.forEach { total += it.duration }
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getCoverUri(): Uri {
|
fun getCoverUri(): Uri {
|
||||||
if (localMediaItem?.coverContentUrl != null) return Uri.parse(localMediaItem?.coverContentUrl) ?: Uri.parse("android.resource://com.audiobookshelf.app/" + R.drawable.icon)
|
if (localMediaItem?.coverContentUrl != null) return Uri.parse(localMediaItem?.coverContentUrl) ?: Uri.parse("android.resource://com.audiobookshelf.app/" + R.drawable.icon)
|
||||||
|
@ -48,18 +81,11 @@ class PlaybackSession(
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getContentUri(): Uri {
|
fun getContentUri(audioTrack:AudioTrack): Uri {
|
||||||
var audioTrack = audioTracks[0]
|
|
||||||
if (isLocal) return Uri.parse(audioTrack.contentUrl) // Local content url
|
if (isLocal) return Uri.parse(audioTrack.contentUrl) // Local content url
|
||||||
return Uri.parse("$serverUrl${audioTrack.contentUrl}?token=$token")
|
return Uri.parse("$serverUrl${audioTrack.contentUrl}?token=$token")
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
|
||||||
fun getMimeType():String {
|
|
||||||
var audioTrack = audioTracks[0]
|
|
||||||
return audioTrack.mimeType
|
|
||||||
}
|
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getMediaMetadataCompat(): MediaMetadataCompat {
|
fun getMediaMetadataCompat(): MediaMetadataCompat {
|
||||||
var metadataBuilder = MediaMetadataCompat.Builder()
|
var metadataBuilder = MediaMetadataCompat.Builder()
|
||||||
|
@ -74,7 +100,7 @@ class PlaybackSession(
|
||||||
}
|
}
|
||||||
|
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
fun getExoMediaMetadata(): MediaMetadata {
|
fun getExoMediaMetadata(audioTrack:AudioTrack): MediaMetadata {
|
||||||
var metadataBuilder = MediaMetadata.Builder()
|
var metadataBuilder = MediaMetadata.Builder()
|
||||||
.setTitle(displayTitle)
|
.setTitle(displayTitle)
|
||||||
.setDisplayTitle(displayTitle)
|
.setDisplayTitle(displayTitle)
|
||||||
|
@ -82,9 +108,23 @@ class PlaybackSession(
|
||||||
.setAlbumArtist(displayAuthor)
|
.setAlbumArtist(displayAuthor)
|
||||||
.setSubtitle(displayAuthor)
|
.setSubtitle(displayAuthor)
|
||||||
|
|
||||||
var contentUri = this.getContentUri()
|
var contentUri = this.getContentUri(audioTrack)
|
||||||
metadataBuilder.setMediaUri(contentUri)
|
metadataBuilder.setMediaUri(contentUri)
|
||||||
|
|
||||||
return metadataBuilder.build()
|
return metadataBuilder.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun getMediaItems():List<MediaItem> {
|
||||||
|
var mediaItems:MutableList<MediaItem> = mutableListOf()
|
||||||
|
|
||||||
|
for (audioTrack in audioTracks) {
|
||||||
|
var mediaMetadata = this.getExoMediaMetadata(audioTrack)
|
||||||
|
var mediaUri = this.getContentUri(audioTrack)
|
||||||
|
var mimeType = audioTrack.mimeType
|
||||||
|
var mediaItem = MediaItem.Builder().setUri(mediaUri).setMediaMetadata(mediaMetadata).setMimeType(mimeType).build()
|
||||||
|
mediaItems.add(mediaItem)
|
||||||
|
}
|
||||||
|
return mediaItems
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -73,6 +73,7 @@ class FolderScanner(var ctx: Context) {
|
||||||
var index = 1
|
var index = 1
|
||||||
var startOffset = 0.0
|
var startOffset = 0.0
|
||||||
var coverContentUrl:String? = null
|
var coverContentUrl:String? = null
|
||||||
|
var coverAbsolutePath:String? = null
|
||||||
|
|
||||||
var filesInFolder = it.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
|
var filesInFolder = it.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
|
||||||
|
|
||||||
|
@ -157,6 +158,7 @@ class FolderScanner(var ctx: Context) {
|
||||||
// First image file use as cover path
|
// First image file use as cover path
|
||||||
if (coverContentUrl == null) {
|
if (coverContentUrl == null) {
|
||||||
coverContentUrl = localFile.contentUrl
|
coverContentUrl = localFile.contentUrl
|
||||||
|
coverAbsolutePath = localFile.absolutePath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -173,7 +175,7 @@ class FolderScanner(var ctx: Context) {
|
||||||
else mediaItemsAdded++
|
else mediaItemsAdded++
|
||||||
|
|
||||||
Log.d(tag, "Found local media item named $itemFolderName with ${audioTracks.size} tracks and ${localFiles.size} local files")
|
Log.d(tag, "Found local media item named $itemFolderName with ${audioTracks.size} tracks and ${localFiles.size} local files")
|
||||||
var localMediaItem = LocalMediaItem(itemId, itemFolderName, localFolder.mediaType, localFolder.id, it.uri.toString(), it.getSimplePath(ctx), it.getAbsolutePath(ctx),audioTracks,localFiles,coverContentUrl)
|
var localMediaItem = LocalMediaItem(itemId, itemFolderName, localFolder.mediaType, localFolder.id, it.uri.toString(), it.getSimplePath(ctx), it.getAbsolutePath(ctx),audioTracks,localFiles,coverContentUrl,coverAbsolutePath)
|
||||||
mediaItems.add(localMediaItem)
|
mediaItems.add(localMediaItem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -94,14 +94,20 @@ class ApiHandler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getLibraryItem(libraryItemId:String, cb: (LibraryItem) -> Unit) {
|
||||||
|
getRequest("/api/items/$libraryItemId?expanded=1") {
|
||||||
|
val libraryItem = jacksonObjectMapper().readValue<LibraryItem>(it.toString())
|
||||||
|
cb(libraryItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getLibraryItems(libraryId:String, cb: (List<LibraryItem>) -> Unit) {
|
fun getLibraryItems(libraryId:String, cb: (List<LibraryItem>) -> Unit) {
|
||||||
val mapper = jacksonObjectMapper()
|
|
||||||
getRequest("/api/libraries/$libraryId/items") {
|
getRequest("/api/libraries/$libraryId/items") {
|
||||||
val items = mutableListOf<LibraryItem>()
|
val items = mutableListOf<LibraryItem>()
|
||||||
if (it.has("results")) {
|
if (it.has("results")) {
|
||||||
var array = it.getJSONArray("results")
|
var array = it.getJSONArray("results")
|
||||||
for (i in 0 until array.length()) {
|
for (i in 0 until array.length()) {
|
||||||
val item = mapper.readValue<LibraryItem>(array.get(i).toString())
|
val item = jacksonObjectMapper().readValue<LibraryItem>(array.get(i).toString())
|
||||||
items.add(item)
|
items.add(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -291,8 +291,8 @@ export default {
|
||||||
return this.restart()
|
return this.restart()
|
||||||
}
|
}
|
||||||
|
|
||||||
// If 1 second or less into current chapter, then go to previous
|
// If 4 seconds or less into current chapter, then go to previous
|
||||||
if (this.currentTime - this.currentChapter.start <= 1) {
|
if (this.currentTime - this.currentChapter.start <= 4) {
|
||||||
var currChapterIndex = this.chapters.findIndex((ch) => Number(ch.start) <= this.currentTime && Number(ch.end) >= this.currentTime)
|
var currChapterIndex = this.chapters.findIndex((ch) => Number(ch.start) <= this.currentTime && Number(ch.end) >= this.currentTime)
|
||||||
if (currChapterIndex > 0) {
|
if (currChapterIndex > 0) {
|
||||||
var prevChapter = this.chapters[currChapterIndex - 1]
|
var prevChapter = this.chapters[currChapterIndex - 1]
|
||||||
|
@ -509,8 +509,8 @@ export default {
|
||||||
console.log('onMetadata', JSON.stringify(data))
|
console.log('onMetadata', JSON.stringify(data))
|
||||||
this.isLoading = false
|
this.isLoading = false
|
||||||
|
|
||||||
this.totalDuration = Number((data.duration / 1000).toFixed(2))
|
// this.totalDuration = Number((data.duration / 1000).toFixed(2))
|
||||||
this.$emit('setTotalDuration', this.totalDuration)
|
this.totalDuration = Number(data.duration.toFixed(2))
|
||||||
this.currentTime = Number((data.currentTime / 1000).toFixed(2))
|
this.currentTime = Number((data.currentTime / 1000).toFixed(2))
|
||||||
this.stateName = data.stateName
|
this.stateName = data.stateName
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div id="streamContainer">
|
<div id="streamContainer">
|
||||||
<app-audio-player
|
<app-audio-player ref="audioPlayer" :playing.sync="isPlaying" :bookmarks="bookmarks" :sleep-timer-running="isSleepTimerRunning" :sleep-time-remaining="sleepTimeRemaining" @selectPlaybackSpeed="showPlaybackSpeedModal = true" @updateTime="(t) => (currentTime = t)" @showSleepTimer="showSleepTimer" @showBookmarks="showBookmarks" @hook:mounted="audioPlayerMounted" />
|
||||||
ref="audioPlayer"
|
|
||||||
:playing.sync="isPlaying"
|
|
||||||
:bookmarks="bookmarks"
|
|
||||||
:sleep-timer-running="isSleepTimerRunning"
|
|
||||||
:sleep-time-remaining="sleepTimeRemaining"
|
|
||||||
@setTotalDuration="setTotalDuration"
|
|
||||||
@selectPlaybackSpeed="showPlaybackSpeedModal = true"
|
|
||||||
@updateTime="(t) => (currentTime = t)"
|
|
||||||
@showSleepTimer="showSleepTimer"
|
|
||||||
@showBookmarks="showBookmarks"
|
|
||||||
@hook:mounted="audioPlayerMounted"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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" />
|
||||||
|
@ -43,8 +31,7 @@ export default {
|
||||||
onSleepTimerEndedListener: null,
|
onSleepTimerEndedListener: null,
|
||||||
onSleepTimerSetListener: null,
|
onSleepTimerSetListener: null,
|
||||||
sleepInterval: null,
|
sleepInterval: null,
|
||||||
currentEndOfChapterTime: 0,
|
currentEndOfChapterTime: 0
|
||||||
totalDuration: 0
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
|
@ -121,9 +108,6 @@ export default {
|
||||||
console.log('Canceling sleep timer')
|
console.log('Canceling sleep timer')
|
||||||
await MyNativeAudio.cancelSleepTimer()
|
await MyNativeAudio.cancelSleepTimer()
|
||||||
},
|
},
|
||||||
setTotalDuration(duration) {
|
|
||||||
this.totalDuration = duration
|
|
||||||
},
|
|
||||||
streamClosed() {
|
streamClosed() {
|
||||||
console.log('Stream Closed')
|
console.log('Stream Closed')
|
||||||
},
|
},
|
||||||
|
|
62
components/modals/SelectLocalFolderModal.vue
Normal file
62
components/modals/SelectLocalFolderModal.vue
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
<template>
|
||||||
|
<modals-modal v-model="show" :width="300" height="100%">
|
||||||
|
<template #outer>
|
||||||
|
<div class="absolute top-7 left-4 z-40" style="max-width: 80%">
|
||||||
|
<p class="text-white text-lg truncate">Select Local Folder</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="w-full h-full overflow-hidden absolute top-0 left-0 flex items-center justify-center" @click="show = false">
|
||||||
|
<div ref="container" class="w-full overflow-x-hidden overflow-y-auto bg-primary rounded-lg border border-white border-opacity-20" style="max-height: 75%" @click.stop>
|
||||||
|
<ul class="h-full w-full" role="listbox" aria-labelledby="listbox-label">
|
||||||
|
<template v-for="folder in localFolders">
|
||||||
|
<li :key="folder.id" :id="`folder-${folder.id}`" class="text-gray-50 select-none relative py-4" role="option" @click="clickedOption(folder)">
|
||||||
|
<div class="relative flex items-center pl-3" style="padding-right: 4.5rem">
|
||||||
|
<p class="font-normal block truncate text-sm text-white text-opacity-80">{{ folder.name }}</p>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</modals-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
value: Boolean,
|
||||||
|
mediaType: String
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
localFolders: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(newVal) {
|
||||||
|
this.$nextTick(this.init)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
show: {
|
||||||
|
get() {
|
||||||
|
return this.value
|
||||||
|
},
|
||||||
|
set(val) {
|
||||||
|
this.$emit('input', val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
clickedOption(folder) {
|
||||||
|
this.$emit('select', folder)
|
||||||
|
},
|
||||||
|
async init() {
|
||||||
|
var localFolders = (await this.$db.loadFolders()) || []
|
||||||
|
this.localFolders = localFolders.filter((lf) => lf.mediaType == this.mediaType)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -253,17 +253,26 @@ export default {
|
||||||
await this.$store.dispatch('downloads/linkOrphanDownloads')
|
await this.$store.dispatch('downloads/linkOrphanDownloads')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onItemDownloadUpdate(data) {
|
||||||
|
console.log('ON ITEM DOWNLOAD UPDATE', JSON.stringify(data))
|
||||||
|
},
|
||||||
|
onItemDownloadComplete(data) {
|
||||||
|
console.log('ON ITEM DOWNLOAD COMPLETE', JSON.stringify(data))
|
||||||
|
},
|
||||||
async initMediaStore() {
|
async initMediaStore() {
|
||||||
// Request and setup listeners for media files on native
|
// Request and setup listeners for media files on native
|
||||||
AudioDownloader.addListener('onDownloadComplete', (data) => {
|
AudioDownloader.addListener('onItemDownloadUpdate', (data) => {
|
||||||
this.onDownloadComplete(data)
|
this.onItemDownloadUpdate(data)
|
||||||
})
|
})
|
||||||
AudioDownloader.addListener('onDownloadFailed', (data) => {
|
AudioDownloader.addListener('onItemDownloadComplete', (data) => {
|
||||||
this.onDownloadFailed(data)
|
this.onItemDownloadComplete(data)
|
||||||
})
|
|
||||||
AudioDownloader.addListener('onDownloadProgress', (data) => {
|
|
||||||
this.onDownloadProgress(data)
|
|
||||||
})
|
})
|
||||||
|
// AudioDownloader.addListener('onDownloadFailed', (data) => {
|
||||||
|
// this.onDownloadFailed(data)
|
||||||
|
// })
|
||||||
|
// AudioDownloader.addListener('onDownloadProgress', (data) => {
|
||||||
|
// this.onDownloadProgress(data)
|
||||||
|
// })
|
||||||
|
|
||||||
var downloads = await this.$store.dispatch('downloads/loadFromStorage')
|
var downloads = await this.$store.dispatch('downloads/loadFromStorage')
|
||||||
var downloadFolder = await this.$localStore.getDownloadFolder()
|
var downloadFolder = await this.$localStore.getDownloadFolder()
|
||||||
|
|
|
@ -45,6 +45,8 @@
|
||||||
<div class="w-full py-4">
|
<div class="w-full py-4">
|
||||||
<p>{{ description }}</p>
|
<p>{{ description }}</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<modals-select-local-folder-modal v-model="showSelectLocalFolder" :media-type="mediaType" @select="selectedLocalFolder" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -81,7 +83,8 @@ export default {
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
resettingProgress: false
|
resettingProgress: false,
|
||||||
|
showSelectLocalFolder: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -97,6 +100,9 @@ export default {
|
||||||
libraryItemId() {
|
libraryItemId() {
|
||||||
return this.libraryItem.id
|
return this.libraryItem.id
|
||||||
},
|
},
|
||||||
|
mediaType() {
|
||||||
|
return this.libraryItem.mediaType
|
||||||
|
},
|
||||||
media() {
|
media() {
|
||||||
return this.libraryItem.media || {}
|
return this.libraryItem.media || {}
|
||||||
},
|
},
|
||||||
|
@ -235,17 +241,6 @@ export default {
|
||||||
this.resettingProgress = false
|
this.resettingProgress = false
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// if (value) {
|
|
||||||
// this.resettingProgress = true
|
|
||||||
// this.$store.dispatch('user/updateUserAudiobookData', {
|
|
||||||
// libraryItemId: this.libraryItemId,
|
|
||||||
// currentTime: 0,
|
|
||||||
// totalDuration: this.duration,
|
|
||||||
// progress: 0,
|
|
||||||
// lastUpdate: Date.now(),
|
|
||||||
// isRead: false
|
|
||||||
// })
|
|
||||||
// }
|
|
||||||
},
|
},
|
||||||
itemUpdated(libraryItem) {
|
itemUpdated(libraryItem) {
|
||||||
if (libraryItem.id === this.libraryItemId) {
|
if (libraryItem.id === this.libraryItemId) {
|
||||||
|
@ -253,14 +248,74 @@ export default {
|
||||||
this.libraryItem = libraryItem
|
this.libraryItem = libraryItem
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
async selectFolder() {
|
||||||
|
// Select and save the local folder for media type
|
||||||
|
var folderObj = await StorageManager.selectFolder({ mediaType: this.mediaType })
|
||||||
|
if (folderObj.error) {
|
||||||
|
return this.$toast.error(`Error: ${folderObj.error || 'Unknown Error'}`)
|
||||||
|
}
|
||||||
|
return folderObj
|
||||||
|
},
|
||||||
|
selectedLocalFolder(localFolder) {
|
||||||
|
this.showSelectLocalFolder = false
|
||||||
|
this.download(localFolder)
|
||||||
|
},
|
||||||
downloadClick() {
|
downloadClick() {
|
||||||
|
this.download()
|
||||||
|
},
|
||||||
|
async download(selectedLocalFolder = null) {
|
||||||
console.log('downloadClick ' + this.$server.connected + ' | ' + !!this.downloadObj)
|
console.log('downloadClick ' + this.$server.connected + ' | ' + !!this.downloadObj)
|
||||||
if (!this.$server.connected) return
|
if (!this.$server.connected) return
|
||||||
|
|
||||||
if (this.downloadObj) {
|
if (!this.numTracks || this.downloadObj) {
|
||||||
console.log('Already downloaded', this.downloadObj)
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the local folder to download to
|
||||||
|
var localFolder = selectedLocalFolder
|
||||||
|
if (!localFolder) {
|
||||||
|
var localFolders = (await this.$db.loadFolders()) || []
|
||||||
|
console.log('Local folders loaded', localFolders.length)
|
||||||
|
var foldersWithMediaType = localFolders.filter((lf) => {
|
||||||
|
console.log('Checking local folder', lf.mediaType)
|
||||||
|
return lf.mediaType == this.mediaType
|
||||||
|
})
|
||||||
|
console.log('Folders with media type', this.mediaType, foldersWithMediaType.length)
|
||||||
|
if (!foldersWithMediaType.length) {
|
||||||
|
// No local folders or no local folders with this media type
|
||||||
|
localFolder = await this.selectFolder()
|
||||||
|
} else if (foldersWithMediaType.length == 1) {
|
||||||
|
console.log('Only 1 local folder with this media type - auto select it')
|
||||||
|
localFolder = foldersWithMediaType[0]
|
||||||
} else {
|
} else {
|
||||||
this.prepareDownload()
|
console.log('Multiple folders with media type')
|
||||||
|
this.showSelectLocalFolder = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!localFolder) {
|
||||||
|
return this.$toast.error('Invalid download folder')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Local folder', JSON.stringify(localFolder))
|
||||||
|
|
||||||
|
var startDownloadMessage = `Start download for "${this.title}" with ${this.numTracks} audio track${this.numTracks == 1 ? '' : 's'} to folder ${localFolder.name}?`
|
||||||
|
const { value } = await Dialog.confirm({
|
||||||
|
title: 'Confirm',
|
||||||
|
message: startDownloadMessage
|
||||||
|
})
|
||||||
|
if (value) {
|
||||||
|
this.startDownload(localFolder)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async startDownload(localFolder) {
|
||||||
|
console.log('Starting download to local folder', localFolder.name)
|
||||||
|
var downloadRes = await AudioDownloader.downloadLibraryItem({ libraryItemId: this.libraryItemId, localFolderId: localFolder.id })
|
||||||
|
if (downloadRes.error) {
|
||||||
|
var errorMsg = downloadRes.error || 'Unknown error'
|
||||||
|
console.error('Download error', errorMsg)
|
||||||
|
this.$toast.update(download.toastId, { content: `Error: ${errorMsg}`, options: { timeout: 5000, type: 'error' } })
|
||||||
|
this.$store.commit('downloads/removeDownload', download)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async changeDownloadFolderClick() {
|
async changeDownloadFolderClick() {
|
||||||
|
@ -286,103 +341,103 @@ export default {
|
||||||
await this.$localStore.setDownloadFolder(folderObj)
|
await this.$localStore.setDownloadFolder(folderObj)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async prepareDownload() {
|
// async prepareDownload() {
|
||||||
var audiobook = this.libraryItem
|
// var audiobook = this.libraryItem
|
||||||
if (!audiobook) {
|
// if (!audiobook) {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Download Path
|
// // Download Path
|
||||||
var dlFolder = this.$localStore.downloadFolder
|
// var dlFolder = this.$localStore.downloadFolder
|
||||||
console.log('Prepare download: ' + this.hasStoragePermission + ' | ' + dlFolder)
|
// console.log('Prepare download: ' + this.hasStoragePermission + ' | ' + dlFolder)
|
||||||
|
|
||||||
if (!this.hasStoragePermission || !dlFolder) {
|
// if (!this.hasStoragePermission || !dlFolder) {
|
||||||
console.log('No download folder, request from user')
|
// console.log('No download folder, request from user')
|
||||||
// User to select download folder from download modal to ensure permissions
|
// // User to select download folder from download modal to ensure permissions
|
||||||
// this.$store.commit('downloads/setShowModal', true)
|
// // this.$store.commit('downloads/setShowModal', true)
|
||||||
this.changeDownloadFolderClick()
|
// this.changeDownloadFolderClick()
|
||||||
return
|
// return
|
||||||
} else {
|
// } else {
|
||||||
console.log('Has Download folder: ' + JSON.stringify(dlFolder))
|
// console.log('Has Download folder: ' + JSON.stringify(dlFolder))
|
||||||
}
|
// }
|
||||||
|
|
||||||
var downloadObject = {
|
// var downloadObject = {
|
||||||
id: this.libraryItemId,
|
// id: this.libraryItemId,
|
||||||
downloadFolderUrl: dlFolder.uri,
|
// downloadFolderUrl: dlFolder.uri,
|
||||||
audiobook: {
|
// audiobook: {
|
||||||
...audiobook
|
// ...audiobook
|
||||||
},
|
// },
|
||||||
isPreparing: true,
|
// isPreparing: true,
|
||||||
isDownloading: false,
|
// isDownloading: false,
|
||||||
toastId: this.$toast(`Preparing download for "${this.title}"`, { timeout: false })
|
// toastId: this.$toast(`Preparing download for "${this.title}"`, { timeout: false })
|
||||||
}
|
// }
|
||||||
if (audiobook.tracks.length === 1) {
|
// if (audiobook.tracks.length === 1) {
|
||||||
// Single track should not need preparation
|
// // Single track should not need preparation
|
||||||
console.log('Single track, start download no prep needed')
|
// console.log('Single track, start download no prep needed')
|
||||||
var track = audiobook.tracks[0]
|
// var track = audiobook.tracks[0]
|
||||||
var fileext = track.ext
|
// var fileext = track.ext
|
||||||
|
|
||||||
console.log('Download Single Track Path: ' + track.path)
|
// console.log('Download Single Track Path: ' + track.path)
|
||||||
|
|
||||||
var relTrackPath = track.path.replace('\\', '/').replace(this.libraryItem.path.replace('\\', '/'), '')
|
// var relTrackPath = track.path.replace('\\', '/').replace(this.libraryItem.path.replace('\\', '/'), '')
|
||||||
|
|
||||||
var url = `${this.$store.state.serverUrl}/s/book/${this.libraryItemId}${relTrackPath}?token=${this.userToken}`
|
// var url = `${this.$store.state.serverUrl}/s/book/${this.libraryItemId}${relTrackPath}?token=${this.userToken}`
|
||||||
this.startDownload(url, fileext, downloadObject)
|
// this.startDownload(url, fileext, downloadObject)
|
||||||
} else {
|
// } else {
|
||||||
// Multi-track merge
|
// // Multi-track merge
|
||||||
this.$store.commit('downloads/addUpdateDownload', downloadObject)
|
// this.$store.commit('downloads/addUpdateDownload', downloadObject)
|
||||||
|
|
||||||
var prepareDownloadPayload = {
|
// var prepareDownloadPayload = {
|
||||||
audiobookId: this.libraryItemId,
|
// audiobookId: this.libraryItemId,
|
||||||
audioFileType: 'same',
|
// audioFileType: 'same',
|
||||||
type: 'singleAudio'
|
// type: 'singleAudio'
|
||||||
}
|
// }
|
||||||
this.$server.socket.emit('download', prepareDownloadPayload)
|
// this.$server.socket.emit('download', prepareDownloadPayload)
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
getCoverUrlForDownload() {
|
// getCoverUrlForDownload() {
|
||||||
if (!this.book || !this.book.cover) return null
|
// if (!this.book || !this.book.cover) return null
|
||||||
|
|
||||||
var cover = this.book.cover
|
// var cover = this.book.cover
|
||||||
if (cover.startsWith('http')) return cover
|
// if (cover.startsWith('http')) return cover
|
||||||
var coverSrc = this.$store.getters['global/getLibraryItemCoverSrc'](this.libraryItem)
|
// var coverSrc = this.$store.getters['global/getLibraryItemCoverSrc'](this.libraryItem)
|
||||||
return coverSrc
|
// return coverSrc
|
||||||
},
|
// },
|
||||||
async startDownload(url, fileext, download) {
|
// async startDownload(url, fileext, download) {
|
||||||
this.$toast.update(download.toastId, { content: `Downloading "${download.audiobook.book.title}"...` })
|
// this.$toast.update(download.toastId, { content: `Downloading "${download.audiobook.book.title}"...` })
|
||||||
|
|
||||||
var coverDownloadUrl = this.getCoverUrlForDownload()
|
// var coverDownloadUrl = this.getCoverUrlForDownload()
|
||||||
var coverFilename = null
|
// var coverFilename = null
|
||||||
if (coverDownloadUrl) {
|
// if (coverDownloadUrl) {
|
||||||
var coverNoQueryString = coverDownloadUrl.split('?')[0]
|
// var coverNoQueryString = coverDownloadUrl.split('?')[0]
|
||||||
|
|
||||||
var coverExt = Path.extname(coverNoQueryString) || '.jpg'
|
// var coverExt = Path.extname(coverNoQueryString) || '.jpg'
|
||||||
coverFilename = `cover-${download.id}${coverExt}`
|
// coverFilename = `cover-${download.id}${coverExt}`
|
||||||
}
|
// }
|
||||||
|
|
||||||
download.isDownloading = true
|
// download.isDownloading = true
|
||||||
download.isPreparing = false
|
// download.isPreparing = false
|
||||||
download.filename = `${download.audiobook.book.title}${fileext}`
|
// download.filename = `${download.audiobook.book.title}${fileext}`
|
||||||
this.$store.commit('downloads/addUpdateDownload', download)
|
// this.$store.commit('downloads/addUpdateDownload', download)
|
||||||
|
|
||||||
console.log('Starting Download URL', url)
|
// console.log('Starting Download URL', url)
|
||||||
var downloadRequestPayload = {
|
// var downloadRequestPayload = {
|
||||||
audiobookId: download.id,
|
// audiobookId: download.id,
|
||||||
filename: download.filename,
|
// filename: download.filename,
|
||||||
coverFilename,
|
// coverFilename,
|
||||||
coverDownloadUrl,
|
// coverDownloadUrl,
|
||||||
downloadUrl: url,
|
// downloadUrl: url,
|
||||||
title: download.audiobook.book.title,
|
// title: download.audiobook.book.title,
|
||||||
downloadFolderUrl: download.downloadFolderUrl
|
// downloadFolderUrl: download.downloadFolderUrl
|
||||||
}
|
// }
|
||||||
var downloadRes = await AudioDownloader.download(downloadRequestPayload)
|
// var downloadRes = await AudioDownloader.download(downloadRequestPayload)
|
||||||
if (downloadRes.error) {
|
// if (downloadRes.error) {
|
||||||
var errorMsg = downloadRes.error || 'Unknown error'
|
// var errorMsg = downloadRes.error || 'Unknown error'
|
||||||
console.error('Download error', errorMsg)
|
// console.error('Download error', errorMsg)
|
||||||
this.$toast.update(download.toastId, { content: `Error: ${errorMsg}`, options: { timeout: 5000, type: 'error' } })
|
// this.$toast.update(download.toastId, { content: `Error: ${errorMsg}`, options: { timeout: 5000, type: 'error' } })
|
||||||
this.$store.commit('downloads/removeDownload', download)
|
// this.$store.commit('downloads/removeDownload', download)
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
downloadReady(prepareDownload) {
|
downloadReady(prepareDownload) {
|
||||||
var download = this.$store.getters['downloads/getDownload'](prepareDownload.audiobookId)
|
var download = this.$store.getters['downloads/getDownload'](prepareDownload.audiobookId)
|
||||||
if (download) {
|
if (download) {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue