mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-08-03 17:44:51 +02:00
Updates to downloader, audio track ordering, hard deleting from file system, UI updates and fixes
This commit is contained in:
parent
105451ebf1
commit
f309e1fcf2
27 changed files with 561 additions and 19031 deletions
|
@ -11,6 +11,7 @@ apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle"
|
|||
dependencies {
|
||||
implementation project(':capacitor-app')
|
||||
implementation project(':capacitor-dialog')
|
||||
implementation project(':capacitor-haptics')
|
||||
implementation project(':capacitor-network')
|
||||
implementation project(':capacitor-status-bar')
|
||||
implementation project(':capacitor-storage')
|
||||
|
|
|
@ -7,6 +7,10 @@
|
|||
"pkg": "@capacitor/dialog",
|
||||
"classpath": "com.capacitorjs.plugins.dialog.DialogPlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "@capacitor/haptics",
|
||||
"classpath": "com.capacitorjs.plugins.haptics.HapticsPlugin"
|
||||
},
|
||||
{
|
||||
"pkg": "@capacitor/network",
|
||||
"classpath": "com.capacitorjs.plugins.network.NetworkPlugin"
|
||||
|
|
|
@ -119,9 +119,18 @@ class Book(
|
|||
override fun removeAudioTrack(localFileId:String) {
|
||||
tracks?.removeIf { it.localFileId == localFileId }
|
||||
|
||||
tracks?.sortBy { it.index }
|
||||
|
||||
var index = 1
|
||||
var startOffset = 0.0
|
||||
var totalDuration = 0.0
|
||||
tracks?.forEach {
|
||||
it.index = index
|
||||
it.startOffset = startOffset
|
||||
totalDuration += it.duration
|
||||
|
||||
index++
|
||||
startOffset += it.duration
|
||||
}
|
||||
duration = totalDuration
|
||||
}
|
||||
|
@ -233,6 +242,7 @@ data class AudioTrack(
|
|||
var isLocal:Boolean,
|
||||
var localFileId:String?,
|
||||
var audioProbeResult:AudioProbeResult?,
|
||||
var serverIndex:Int? // Need to know if server track index is different
|
||||
) {
|
||||
|
||||
@get:JsonIgnore
|
||||
|
|
|
@ -22,8 +22,10 @@ data class DeviceData(
|
|||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class LocalLibraryItem(
|
||||
var id:String,
|
||||
var serverAddress:String?,
|
||||
var libraryItemId:String?,
|
||||
var folderId:String,
|
||||
var basePath:String,
|
||||
var absolutePath:String,
|
||||
var contentUrl:String,
|
||||
var isInvalid:Boolean,
|
||||
|
@ -70,16 +72,23 @@ data class LocalLibraryItem(
|
|||
}
|
||||
return PlaybackSession(sessionId,null,null,null, mediaType, mediaMetadata, chapters, mediaMetadata.title, authorName,null,getDuration(),PLAYMETHOD_LOCAL, media.getAudioTracks() as MutableList<AudioTrack>,0.0,null,this,null,null)
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
fun removeLocalFile(localFileId:String) {
|
||||
localFiles.removeIf { it.id == localFileId }
|
||||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
data class LocalMediaItem(
|
||||
var id:String,
|
||||
var serverAddress:String?,
|
||||
var name: String,
|
||||
var mediaType:String,
|
||||
var folderId:String,
|
||||
var contentUrl:String,
|
||||
var simplePath: String,
|
||||
var basePath:String,
|
||||
var absolutePath:String,
|
||||
var audioTracks:MutableList<AudioTrack>,
|
||||
var localFiles:MutableList<LocalFile>,
|
||||
|
@ -126,10 +135,10 @@ data class LocalMediaItem(
|
|||
if (mediaType == "book") {
|
||||
var chapters = getAudiobookChapters()
|
||||
var book = Book(mediaMetadata as BookMetadata, coverAbsolutePath, mutableListOf(), mutableListOf(), chapters,audioTracks,getTotalSize(),getDuration())
|
||||
return LocalLibraryItem(id, null, folderId, absolutePath, contentUrl, false,mediaType, book, localFiles, coverContentUrl, coverAbsolutePath,true)
|
||||
return LocalLibraryItem(id,serverAddress, null, folderId, basePath,absolutePath, contentUrl, false,mediaType, book, localFiles, coverContentUrl, coverAbsolutePath,true)
|
||||
} else {
|
||||
var podcast = Podcast(mediaMetadata as PodcastMetadata, coverAbsolutePath, mutableListOf(), mutableListOf(), false)
|
||||
return LocalLibraryItem(id, null, folderId, absolutePath, contentUrl, false, mediaType, podcast,localFiles,coverContentUrl, coverAbsolutePath, true)
|
||||
return LocalLibraryItem(id,serverAddress, null, folderId, basePath,absolutePath, contentUrl, false, mediaType, podcast,localFiles,coverContentUrl, coverAbsolutePath, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,6 +148,7 @@ data class LocalFile(
|
|||
var id:String,
|
||||
var filename:String?,
|
||||
var contentUrl:String,
|
||||
var basePath:String,
|
||||
var absolutePath:String,
|
||||
var simplePath:String,
|
||||
var mimeType:String?,
|
||||
|
@ -155,6 +165,7 @@ data class LocalFolder(
|
|||
var id:String,
|
||||
var name:String,
|
||||
var contentUrl:String,
|
||||
var basePath:String,
|
||||
var absolutePath:String,
|
||||
var simplePath:String,
|
||||
var storageType:String,
|
||||
|
|
|
@ -97,7 +97,7 @@ class FolderScanner(var ctx: Context) {
|
|||
|
||||
var localFileId = DeviceManager.getBase64Id(file.id)
|
||||
|
||||
var localFile = LocalFile(localFileId,filename,file.uri.toString(),file.getAbsolutePath(ctx),file.getSimplePath(ctx),mimeType,file.length())
|
||||
var localFile = LocalFile(localFileId,filename,file.uri.toString(),file.getBasePath(ctx), file.getAbsolutePath(ctx),file.getSimplePath(ctx),mimeType,file.length())
|
||||
localFiles.add(localFile)
|
||||
|
||||
Log.d(tag, "File attributes Id:${localFileId}|ContentUrl:${localFile.contentUrl}|isDownloadsDocument:${file.isDownloadsDocument}")
|
||||
|
@ -134,7 +134,7 @@ class FolderScanner(var ctx: Context) {
|
|||
audioTrackToAdd = existingAudioTrack
|
||||
} else {
|
||||
// Create new audio track
|
||||
var track = AudioTrack(index, startOffset, audioProbeResult.duration, filename, localFile.contentUrl, mimeType, null, true, localFileId, audioProbeResult)
|
||||
var track = AudioTrack(index, startOffset, audioProbeResult.duration, filename, localFile.contentUrl, mimeType, null, true, localFileId, audioProbeResult, null)
|
||||
audioTrackToAdd = track
|
||||
}
|
||||
|
||||
|
@ -186,7 +186,7 @@ class FolderScanner(var ctx: Context) {
|
|||
Log.d(tag, "Found local media item named $itemFolderName with ${audioTracks.size} tracks and ${localFiles.size} local files")
|
||||
mediaItemsAdded++
|
||||
|
||||
var localMediaItem = LocalMediaItem(itemId, itemFolderName, localFolder.mediaType, localFolder.id, itemFolder.uri.toString(), itemFolder.getSimplePath(ctx), itemFolder.getAbsolutePath(ctx),audioTracks,localFiles,coverContentUrl,coverAbsolutePath)
|
||||
var localMediaItem = LocalMediaItem(itemId,null, itemFolderName, localFolder.mediaType, localFolder.id, itemFolder.uri.toString(), itemFolder.getSimplePath(ctx), itemFolder.getBasePath(ctx), itemFolder.getAbsolutePath(ctx),audioTracks,localFiles,coverContentUrl,coverAbsolutePath)
|
||||
var localLibraryItem = localMediaItem.getLocalLibraryItem()
|
||||
localLibraryItems.add(localLibraryItem)
|
||||
}
|
||||
|
@ -209,10 +209,14 @@ class FolderScanner(var ctx: Context) {
|
|||
fun scanDownloadItem(downloadItem: AbsDownloader.DownloadItem):LocalLibraryItem? {
|
||||
var folderDf = DocumentFileCompat.fromUri(ctx, Uri.parse(downloadItem.localFolder.contentUrl))
|
||||
var foldersFound = folderDf?.search(false, DocumentFileType.FOLDER) ?: mutableListOf()
|
||||
var itemFolderUrl:String = ""
|
||||
var itemFolderUrl = ""
|
||||
var itemFolderBasePath = ""
|
||||
var itemFolderAbsolutePath = ""
|
||||
foldersFound.forEach {
|
||||
if (it.name == downloadItem.itemTitle) {
|
||||
itemFolderUrl = it.uri.toString()
|
||||
itemFolderBasePath = it.getBasePath(ctx)
|
||||
itemFolderAbsolutePath = it.getAbsolutePath(ctx)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,7 +236,7 @@ class FolderScanner(var ctx: Context) {
|
|||
var filesFound = df.search(false, DocumentFileType.FILE, arrayOf("audio/*", "image/*"))
|
||||
Log.d(tag, "scanDownloadItem ${filesFound.size} files found in ${downloadItem.itemFolderPath}")
|
||||
|
||||
var localLibraryItem = LocalLibraryItem("local_${downloadItem.id}", downloadItem.id, downloadItem.localFolder.id, downloadItem.itemFolderPath,itemFolderUrl, false, downloadItem.mediaType, downloadItem.media, mutableListOf(), null, null, true)
|
||||
var localLibraryItem = LocalLibraryItem("local_${downloadItem.id}", downloadItem.serverAddress, downloadItem.id, downloadItem.localFolder.id, itemFolderBasePath, itemFolderAbsolutePath, itemFolderUrl, false, downloadItem.mediaType, downloadItem.media, mutableListOf(), null, null, true)
|
||||
|
||||
var localFiles:MutableList<LocalFile> = mutableListOf()
|
||||
var audioTracks:MutableList<AudioTrack> = mutableListOf()
|
||||
|
@ -247,7 +251,7 @@ class FolderScanner(var ctx: Context) {
|
|||
var audioTrackFromServer = itemPart.audioTrack
|
||||
|
||||
var localFileId = DeviceManager.getBase64Id(docFile.id)
|
||||
var localFile = LocalFile(localFileId,docFile.name,docFile.uri.toString(),docFile.getAbsolutePath(ctx),docFile.getSimplePath(ctx),docFile.mimeType,docFile.length())
|
||||
var localFile = LocalFile(localFileId,docFile.name,docFile.uri.toString(),docFile.getBasePath(ctx),docFile.getAbsolutePath(ctx),docFile.getSimplePath(ctx),docFile.mimeType,docFile.length())
|
||||
localFiles.add(localFile)
|
||||
|
||||
// TODO: Make asynchronous
|
||||
|
@ -257,11 +261,11 @@ class FolderScanner(var ctx: Context) {
|
|||
Log.d(tag, "Probe Result DATA ${audioProbeResult.duration} | ${audioProbeResult.size} | ${audioProbeResult.title} | ${audioProbeResult.artist}")
|
||||
|
||||
// Create new audio track
|
||||
var track = AudioTrack(audioTrackFromServer?.index ?: 0, audioTrackFromServer?.startOffset ?: 0.0, audioProbeResult.duration, localFile.filename ?: "", localFile.contentUrl, localFile.mimeType ?: "", null, true, localFileId, audioProbeResult)
|
||||
var track = AudioTrack(audioTrackFromServer?.index ?: -1, audioTrackFromServer?.startOffset ?: 0.0, audioProbeResult.duration, localFile.filename ?: "", localFile.contentUrl, localFile.mimeType ?: "", null, true, localFileId, audioProbeResult, audioTrackFromServer?.index ?: -1)
|
||||
audioTracks.add(track)
|
||||
} else { // Cover image
|
||||
var localFileId = DeviceManager.getBase64Id(docFile.id)
|
||||
var localFile = LocalFile(localFileId,docFile.name,docFile.uri.toString(),docFile.getAbsolutePath(ctx),docFile.getSimplePath(ctx),docFile.mimeType,docFile.length())
|
||||
var localFile = LocalFile(localFileId,docFile.name,docFile.uri.toString(),docFile.getBasePath(ctx),docFile.getAbsolutePath(ctx),docFile.getSimplePath(ctx),docFile.mimeType,docFile.length())
|
||||
localFiles.add(localFile)
|
||||
|
||||
localLibraryItem.coverAbsolutePath = localFile.absolutePath
|
||||
|
@ -274,6 +278,19 @@ class FolderScanner(var ctx: Context) {
|
|||
return null
|
||||
}
|
||||
|
||||
audioTracks.sortBy { it.index }
|
||||
|
||||
var indexCheck = 1
|
||||
var startOffset = 0.0
|
||||
audioTracks.forEach { audioTrack ->
|
||||
if (audioTrack.index != indexCheck || audioTrack.startOffset != startOffset) {
|
||||
audioTrack.index = indexCheck
|
||||
audioTrack.startOffset = startOffset
|
||||
}
|
||||
indexCheck++
|
||||
startOffset += audioTrack.duration
|
||||
}
|
||||
|
||||
localLibraryItem.media.setAudioTracks(audioTracks)
|
||||
localLibraryItem.localFiles = localFiles
|
||||
|
||||
|
@ -329,7 +346,7 @@ class FolderScanner(var ctx: Context) {
|
|||
|
||||
if (existingLocalFile == null || (existingLocalFile.isAudioFile() && forceAudioProbe)) {
|
||||
|
||||
var localFile = existingLocalFile ?: LocalFile(localFileId,docFile.name,docFile.uri.toString(),docFile.getAbsolutePath(ctx),docFile.getSimplePath(ctx),docFile.mimeType,docFile.length())
|
||||
var localFile = existingLocalFile ?: LocalFile(localFileId,docFile.name,docFile.uri.toString(),docFile.getBasePath(ctx), docFile.getAbsolutePath(ctx),docFile.getSimplePath(ctx),docFile.mimeType,docFile.length())
|
||||
if (existingLocalFile == null) {
|
||||
localLibraryItem.localFiles.add(localFile)
|
||||
Log.d(tag, "scanLocalLibraryItem new file found ${localFile.filename}")
|
||||
|
@ -350,7 +367,7 @@ class FolderScanner(var ctx: Context) {
|
|||
// Create new audio track
|
||||
var lastTrack = existingAudioTracks.lastOrNull()
|
||||
var startOffset = (lastTrack?.startOffset ?: 0.0) + (lastTrack?.duration ?: 0.0)
|
||||
var track = AudioTrack(existingAudioTracks.size, startOffset, audioProbeResult.duration, localFile.filename ?: "", localFile.contentUrl, localFile.mimeType ?: "", null, true, localFileId, audioProbeResult)
|
||||
var track = AudioTrack(existingAudioTracks.size, startOffset, audioProbeResult.duration, localFile.filename ?: "", localFile.contentUrl, localFile.mimeType ?: "", null, true, localFileId, audioProbeResult, null)
|
||||
localLibraryItem.media.addAudioTrack(track)
|
||||
wasUpdated = true
|
||||
} else {
|
||||
|
|
|
@ -88,6 +88,11 @@ class AbsAudioPlayer : Plugin() {
|
|||
var libraryItemId = call.getString("libraryItemId", "").toString()
|
||||
var playWhenReady = call.getBoolean("playWhenReady") == true
|
||||
|
||||
if (libraryItemId.isEmpty()) {
|
||||
Log.e(tag, "Invalid call to play library item no library item id")
|
||||
return call.resolve()
|
||||
}
|
||||
|
||||
if (libraryItemId.startsWith("local")) { // Play local media item
|
||||
DeviceManager.dbManager.getLocalLibraryItem(libraryItemId)?.let {
|
||||
Handler(Looper.getMainLooper()).post() {
|
||||
|
|
|
@ -62,6 +62,7 @@ class AbsDownloader : Plugin() {
|
|||
|
||||
data class DownloadItem(
|
||||
val id: String,
|
||||
val serverAddress:String,
|
||||
val mediaType: String,
|
||||
val itemFolderPath:String,
|
||||
val localFolder: LocalFolder,
|
||||
|
@ -142,7 +143,7 @@ class AbsDownloader : Plugin() {
|
|||
var tracks = libraryItem.media.getAudioTracks()
|
||||
Log.d(tag, "Starting library item download with ${tracks.size} tracks")
|
||||
var itemFolderPath = localFolder.absolutePath + "/" + bookTitle
|
||||
var downloadItem = DownloadItem(libraryItem.id, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, libraryItem.media, mutableListOf())
|
||||
var downloadItem = DownloadItem(libraryItem.id, DeviceManager.serverAddress, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, libraryItem.media, mutableListOf())
|
||||
|
||||
// Create download item part for each audio track
|
||||
tracks.forEach { audioTrack ->
|
||||
|
|
|
@ -25,6 +25,7 @@ import kotlinx.coroutines.launch
|
|||
@CapacitorPlugin(name = "AbsFileSystem")
|
||||
class AbsFileSystem : Plugin() {
|
||||
private val TAG = "AbsFileSystem"
|
||||
private val tag = "AbsFileSystem"
|
||||
|
||||
lateinit var mainActivity: MainActivity
|
||||
|
||||
|
@ -70,9 +71,10 @@ class AbsFileSystem : Plugin() {
|
|||
var absolutePath = folder.getAbsolutePath(activity)
|
||||
var storageType = folder.getStorageType(activity)
|
||||
var simplePath = folder.getSimplePath(activity)
|
||||
var basePath = folder.getBasePath(activity)
|
||||
var folderId = android.util.Base64.encodeToString(folder.id.toByteArray(), android.util.Base64.DEFAULT)
|
||||
|
||||
var localFolder = LocalFolder(folderId, folder.name ?: "", folder.uri.toString(),absolutePath, simplePath, storageType.toString(), mediaType)
|
||||
var localFolder = LocalFolder(folderId, folder.name ?: "", folder.uri.toString(),basePath,absolutePath, simplePath, storageType.toString(), mediaType)
|
||||
|
||||
DeviceManager.dbManager.saveLocalFolder(localFolder)
|
||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(localFolder)))
|
||||
|
@ -188,34 +190,44 @@ class AbsFileSystem : Plugin() {
|
|||
}
|
||||
|
||||
@PluginMethod
|
||||
fun delete(call: PluginCall) {
|
||||
var url = call.data.getString("url", "").toString()
|
||||
var coverUrl = call.data.getString("coverUrl", "").toString()
|
||||
var folderUrl = call.data.getString("folderUrl", "").toString()
|
||||
fun deleteItem(call: PluginCall) {
|
||||
var localLibraryItemId = call.data.getString("id", "").toString()
|
||||
var absolutePath = call.data.getString("absolutePath", "").toString()
|
||||
var contentUrl = call.data.getString("contentUrl", "").toString()
|
||||
Log.d(tag, "deleteItem $absolutePath | $contentUrl")
|
||||
|
||||
if (folderUrl != "") {
|
||||
Log.d(TAG, "CALLED DELETE FOLDER: $folderUrl")
|
||||
var folder = DocumentFileCompat.fromUri(context, Uri.parse(folderUrl))
|
||||
var success = folder?.deleteRecursively(context)
|
||||
var jsobj = JSObject()
|
||||
jsobj.put("success", success)
|
||||
call.resolve()
|
||||
} else {
|
||||
// Older audiobooks did not store a folder url, use cover and audiobook url
|
||||
var abExists = checkUriExists(Uri.parse(url))
|
||||
if (abExists) {
|
||||
var abfile = DocumentFileCompat.fromUri(context, Uri.parse(url))
|
||||
abfile?.delete()
|
||||
}
|
||||
|
||||
var coverExists = checkUriExists(Uri.parse(coverUrl))
|
||||
if (coverExists) {
|
||||
var coverfile = DocumentFileCompat.fromUri(context, Uri.parse(coverUrl))
|
||||
coverfile?.delete()
|
||||
}
|
||||
var docfile = DocumentFileCompat.fromUri(mainActivity, Uri.parse(contentUrl))
|
||||
var success = docfile?.delete() == true
|
||||
if (success) {
|
||||
DeviceManager.dbManager.removeLocalLibraryItem(localLibraryItemId)
|
||||
}
|
||||
call.resolve(JSObject("{\"success\":$success}"))
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun deleteTrackFromItem(call: PluginCall) {
|
||||
var localLibraryItemId = call.data.getString("id", "").toString()
|
||||
var trackLocalFileId = call.data.getString("trackLocalFileId", "").toString()
|
||||
var contentUrl = call.data.getString("trackContentUrl", "").toString()
|
||||
Log.d(tag, "deleteTrackFromItem $contentUrl")
|
||||
|
||||
var localLibraryItem = DeviceManager.dbManager.getLocalLibraryItem(localLibraryItemId)
|
||||
if (localLibraryItem == null) {
|
||||
Log.e(tag, "deleteTrackFromItem: LLI does not exist $localLibraryItemId")
|
||||
return call.resolve(JSObject("{\"success\":false}"))
|
||||
}
|
||||
|
||||
var docfile = DocumentFileCompat.fromUri(mainActivity, Uri.parse(contentUrl))
|
||||
var success = docfile?.delete() == true
|
||||
if (success) {
|
||||
localLibraryItem?.media?.removeAudioTrack(trackLocalFileId)
|
||||
localLibraryItem?.removeLocalFile(trackLocalFileId)
|
||||
DeviceManager.dbManager.saveLocalLibraryItem(localLibraryItem)
|
||||
call.resolve(JSObject(jacksonObjectMapper().writeValueAsString(localLibraryItem)))
|
||||
} else {
|
||||
call.resolve(JSObject("{\"success\":false}"))
|
||||
}
|
||||
}
|
||||
|
||||
fun checkUriExists(uri: Uri?): Boolean {
|
||||
if (uri == null) return false
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue