mirror of
https://github.com/advplyr/audiobookshelf-app.git
synced 2025-07-15 08:24:50 +02:00
Update capacitor version, kotlin version, android dependencies, refactor some folders
This commit is contained in:
parent
b2d3edca81
commit
a8c66ff808
26 changed files with 1034 additions and 519 deletions
|
@ -22,6 +22,8 @@ kotlin {
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
|
||||||
|
|
||||||
|
namespace 'com.audiobookshelf.app'
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
viewBinding true
|
viewBinding true
|
||||||
}
|
}
|
||||||
|
@ -78,6 +80,7 @@ configurations.all {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation "androidx.core:core-splashscreen:$coreSplashScreenVersion"
|
||||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||||
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion"
|
||||||
implementation project(':capacitor-android')
|
implementation project(':capacitor-android')
|
||||||
|
@ -121,7 +124,7 @@ dependencies {
|
||||||
implementation 'io.github.pilgr:paperdb:2.7.2'
|
implementation 'io.github.pilgr:paperdb:2.7.2'
|
||||||
|
|
||||||
// Simple Storage
|
// Simple Storage
|
||||||
implementation "com.anggrayudi:storage:0.14.0"
|
implementation "com.anggrayudi:storage:1.5.4"
|
||||||
|
|
||||||
// OK HTTP
|
// OK HTTP
|
||||||
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
|
implementation 'com.squareup.okhttp3:okhttp:4.9.2'
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_11
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,8 +13,8 @@ dependencies {
|
||||||
implementation project(':capacitor-dialog')
|
implementation project(':capacitor-dialog')
|
||||||
implementation project(':capacitor-haptics')
|
implementation project(':capacitor-haptics')
|
||||||
implementation project(':capacitor-network')
|
implementation project(':capacitor-network')
|
||||||
|
implementation project(':capacitor-preferences')
|
||||||
implementation project(':capacitor-status-bar')
|
implementation project(':capacitor-status-bar')
|
||||||
implementation project(':capacitor-storage')
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:dist="http://schemas.android.com/apk/distribution"
|
xmlns:dist="http://schemas.android.com/apk/distribution"
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="com.audiobookshelf.app"
|
|
||||||
android:installLocation="preferExternal" >
|
android:installLocation="preferExternal" >
|
||||||
|
|
||||||
<!-- Permissions -->
|
<!-- Permissions -->
|
||||||
|
|
|
@ -16,11 +16,11 @@
|
||||||
"classpath": "com.capacitorjs.plugins.network.NetworkPlugin"
|
"classpath": "com.capacitorjs.plugins.network.NetworkPlugin"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "@capacitor/status-bar",
|
"pkg": "@capacitor/preferences",
|
||||||
"classpath": "com.capacitorjs.plugins.statusbar.StatusBarPlugin"
|
"classpath": "com.capacitorjs.plugins.preferences.PreferencesPlugin"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"pkg": "@capacitor/storage",
|
"pkg": "@capacitor/status-bar",
|
||||||
"classpath": "com.capacitorjs.plugins.storage.StoragePlugin"
|
"classpath": "com.capacitorjs.plugins.statusbar.StatusBarPlugin"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -13,7 +13,7 @@ import androidx.core.app.ActivityCompat
|
||||||
import com.anggrayudi.storage.SimpleStorage
|
import com.anggrayudi.storage.SimpleStorage
|
||||||
import com.anggrayudi.storage.SimpleStorageHelper
|
import com.anggrayudi.storage.SimpleStorageHelper
|
||||||
import com.audiobookshelf.app.data.AbsDatabase
|
import com.audiobookshelf.app.data.AbsDatabase
|
||||||
import com.audiobookshelf.app.data.DbManager
|
import com.audiobookshelf.app.managers.DbManager
|
||||||
import com.audiobookshelf.app.player.PlayerNotificationService
|
import com.audiobookshelf.app.player.PlayerNotificationService
|
||||||
import com.audiobookshelf.app.plugins.AbsAudioPlayer
|
import com.audiobookshelf.app.plugins.AbsAudioPlayer
|
||||||
import com.audiobookshelf.app.plugins.AbsDownloader
|
import com.audiobookshelf.app.plugins.AbsDownloader
|
||||||
|
@ -51,11 +51,17 @@ class MainActivity : BridgeActivity() {
|
||||||
// .detectLeakedClosableObjects()
|
// .detectLeakedClosableObjects()
|
||||||
// .penaltyLog()
|
// .penaltyLog()
|
||||||
// .build())
|
// .build())
|
||||||
|
DbManager.initialize(applicationContext)
|
||||||
|
|
||||||
|
registerPlugin(AbsAudioPlayer::class.java)
|
||||||
|
registerPlugin(AbsDownloader::class.java)
|
||||||
|
registerPlugin(AbsFileSystem::class.java)
|
||||||
|
registerPlugin(AbsDatabase::class.java)
|
||||||
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.d(tag, "onCreate")
|
Log.d(tag, "onCreate")
|
||||||
|
|
||||||
DbManager.initialize(applicationContext)
|
|
||||||
|
|
||||||
val permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
val permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
|
||||||
if (permission != PackageManager.PERMISSION_GRANTED) {
|
if (permission != PackageManager.PERMISSION_GRANTED) {
|
||||||
|
@ -63,11 +69,6 @@ class MainActivity : BridgeActivity() {
|
||||||
PERMISSIONS_ALL,
|
PERMISSIONS_ALL,
|
||||||
REQUEST_PERMISSIONS)
|
REQUEST_PERMISSIONS)
|
||||||
}
|
}
|
||||||
|
|
||||||
registerPlugin(AbsAudioPlayer::class.java)
|
|
||||||
registerPlugin(AbsDownloader::class.java)
|
|
||||||
registerPlugin(AbsFileSystem::class.java)
|
|
||||||
registerPlugin(AbsDatabase::class.java)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|
|
@ -2,6 +2,7 @@ package com.audiobookshelf.app.device
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.audiobookshelf.app.data.*
|
import com.audiobookshelf.app.data.*
|
||||||
|
import com.audiobookshelf.app.managers.DbManager
|
||||||
import com.audiobookshelf.app.player.PlayerNotificationService
|
import com.audiobookshelf.app.player.PlayerNotificationService
|
||||||
|
|
||||||
interface WidgetEventEmitter {
|
interface WidgetEventEmitter {
|
||||||
|
@ -11,7 +12,7 @@ interface WidgetEventEmitter {
|
||||||
object DeviceManager {
|
object DeviceManager {
|
||||||
const val tag = "DeviceManager"
|
const val tag = "DeviceManager"
|
||||||
|
|
||||||
val dbManager:DbManager = DbManager()
|
val dbManager: DbManager = DbManager()
|
||||||
var deviceData:DeviceData = dbManager.getDeviceData()
|
var deviceData:DeviceData = dbManager.getDeviceData()
|
||||||
var serverConnectionConfig: ServerConnectionConfig? = null
|
var serverConnectionConfig: ServerConnectionConfig? = null
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.arthenica.ffmpegkit.FFmpegKitConfig
|
||||||
import com.arthenica.ffmpegkit.FFprobeKit
|
import com.arthenica.ffmpegkit.FFprobeKit
|
||||||
import com.arthenica.ffmpegkit.Level
|
import com.arthenica.ffmpegkit.Level
|
||||||
import com.audiobookshelf.app.data.*
|
import com.audiobookshelf.app.data.*
|
||||||
|
import com.audiobookshelf.app.models.DownloadItem
|
||||||
import com.audiobookshelf.app.plugins.AbsDownloader
|
import com.audiobookshelf.app.plugins.AbsDownloader
|
||||||
import com.fasterxml.jackson.core.json.JsonReadFeature
|
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
@ -83,7 +84,7 @@ class FolderScanner(var ctx: Context) {
|
||||||
Log.d(tag, "Folder $${localFolder.name} scan Results: $mediaItemsAdded Added | $mediaItemsUpdated Updated | $mediaItemsRemoved Removed | $mediaItemsUpToDate Up-to-date")
|
Log.d(tag, "Folder $${localFolder.name} scan Results: $mediaItemsAdded Added | $mediaItemsUpdated Updated | $mediaItemsRemoved Removed | $mediaItemsUpToDate Up-to-date")
|
||||||
|
|
||||||
return if (mediaItemsAdded > 0 || mediaItemsUpdated > 0 || mediaItemsRemoved > 0) {
|
return if (mediaItemsAdded > 0 || mediaItemsUpdated > 0 || mediaItemsRemoved > 0) {
|
||||||
var folderLibraryItems = DeviceManager.dbManager.getLocalLibraryItemsInFolder(localFolder.id) // Get all local media items
|
val folderLibraryItems = DeviceManager.dbManager.getLocalLibraryItemsInFolder(localFolder.id) // Get all local media items
|
||||||
FolderScanResult(mediaItemsAdded, mediaItemsUpdated, mediaItemsRemoved, mediaItemsUpToDate, localFolder, folderLibraryItems)
|
FolderScanResult(mediaItemsAdded, mediaItemsUpdated, mediaItemsRemoved, mediaItemsUpToDate, localFolder, folderLibraryItems)
|
||||||
} else {
|
} else {
|
||||||
Log.d(tag, "No Media Items to save")
|
Log.d(tag, "No Media Items to save")
|
||||||
|
@ -91,7 +92,7 @@ class FolderScanner(var ctx: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun scanLibraryItemFolder(itemFolder:DocumentFile, localFolder:LocalFolder, existingItem:LocalLibraryItem?, forceAudioProbe:Boolean):ItemScanResult {
|
private fun scanLibraryItemFolder(itemFolder:DocumentFile, localFolder:LocalFolder, existingItem:LocalLibraryItem?, forceAudioProbe:Boolean):ItemScanResult {
|
||||||
val itemFolderName = itemFolder.name ?: ""
|
val itemFolderName = itemFolder.name ?: ""
|
||||||
val itemId = getLocalLibraryItemId(itemFolder.id)
|
val itemId = getLocalLibraryItemId(itemFolder.id)
|
||||||
|
|
||||||
|
@ -219,7 +220,7 @@ class FolderScanner(var ctx: Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Scan item after download and create local library item
|
// Scan item after download and create local library item
|
||||||
fun scanDownloadItem(downloadItem: AbsDownloader.DownloadItem):DownloadItemScanResult? {
|
fun scanDownloadItem(downloadItem: DownloadItem):DownloadItemScanResult? {
|
||||||
val folderDf = DocumentFileCompat.fromUri(ctx, Uri.parse(downloadItem.localFolder.contentUrl))
|
val folderDf = DocumentFileCompat.fromUri(ctx, Uri.parse(downloadItem.localFolder.contentUrl))
|
||||||
val foldersFound = folderDf?.search(false, DocumentFileType.FOLDER) ?: mutableListOf()
|
val foldersFound = folderDf?.search(false, DocumentFileType.FOLDER) ?: mutableListOf()
|
||||||
|
|
||||||
|
@ -286,11 +287,8 @@ class FolderScanner(var ctx: Context) {
|
||||||
val localFile = LocalFile(localFileId,docFile.name,docFile.uri.toString(),docFile.getBasePath(ctx),docFile.getAbsolutePath(ctx),docFile.getSimplePath(ctx),docFile.mimeType,docFile.length())
|
val localFile = LocalFile(localFileId,docFile.name,docFile.uri.toString(),docFile.getBasePath(ctx),docFile.getAbsolutePath(ctx),docFile.getSimplePath(ctx),docFile.mimeType,docFile.length())
|
||||||
localLibraryItem.localFiles.add(localFile)
|
localLibraryItem.localFiles.add(localFile)
|
||||||
|
|
||||||
// TODO: Make asynchronous
|
|
||||||
val audioProbeResult = probeAudioFile(localFile.absolutePath)
|
|
||||||
|
|
||||||
// Create new audio track
|
// Create new audio track
|
||||||
val track = AudioTrack(audioTrackFromServer.index, audioTrackFromServer.startOffset, audioProbeResult?.duration ?: 0.0, localFile.filename ?: "", localFile.contentUrl, localFile.mimeType ?: "", null, true, localFileId, audioProbeResult, audioTrackFromServer.index)
|
val track = AudioTrack(audioTrackFromServer.index, audioTrackFromServer.startOffset, audioTrackFromServer.duration, localFile.filename ?: "", localFile.contentUrl, localFile.mimeType ?: "", null, true, localFileId, null, audioTrackFromServer.index)
|
||||||
audioTracks.add(track)
|
audioTracks.add(track)
|
||||||
|
|
||||||
Log.d(tag, "scanDownloadItem: Created Audio Track with index ${track.index} from local file ${localFile.absolutePath}")
|
Log.d(tag, "scanDownloadItem: Created Audio Track with index ${track.index} from local file ${localFile.absolutePath}")
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
package com.audiobookshelf.app.data
|
package com.audiobookshelf.app.managers
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.audiobookshelf.app.plugins.AbsDownloader
|
import com.audiobookshelf.app.data.*
|
||||||
|
import com.audiobookshelf.app.models.DownloadItem
|
||||||
import io.paperdb.Paper
|
import io.paperdb.Paper
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@ -23,14 +24,14 @@ class DbManager {
|
||||||
fun getDeviceData(): DeviceData {
|
fun getDeviceData(): DeviceData {
|
||||||
return Paper.book("device").read("data") ?: DeviceData(mutableListOf(), null, null, DeviceSettings.default())
|
return Paper.book("device").read("data") ?: DeviceData(mutableListOf(), null, null, DeviceSettings.default())
|
||||||
}
|
}
|
||||||
fun saveDeviceData(deviceData:DeviceData) {
|
fun saveDeviceData(deviceData: DeviceData) {
|
||||||
Paper.book("device").write("data", deviceData)
|
Paper.book("device").write("data", deviceData)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLocalLibraryItems(mediaType:String? = null):MutableList<LocalLibraryItem> {
|
fun getLocalLibraryItems(mediaType:String? = null):MutableList<LocalLibraryItem> {
|
||||||
val localLibraryItems:MutableList<LocalLibraryItem> = mutableListOf()
|
val localLibraryItems:MutableList<LocalLibraryItem> = mutableListOf()
|
||||||
Paper.book("localLibraryItems").allKeys.forEach {
|
Paper.book("localLibraryItems").allKeys.forEach {
|
||||||
val localLibraryItem:LocalLibraryItem? = Paper.book("localLibraryItems").read(it)
|
val localLibraryItem: LocalLibraryItem? = Paper.book("localLibraryItems").read(it)
|
||||||
if (localLibraryItem != null && (mediaType.isNullOrEmpty() || mediaType == localLibraryItem.mediaType)) {
|
if (localLibraryItem != null && (mediaType.isNullOrEmpty() || mediaType == localLibraryItem.mediaType)) {
|
||||||
localLibraryItems.add(localLibraryItem)
|
localLibraryItems.add(localLibraryItem)
|
||||||
}
|
}
|
||||||
|
@ -45,16 +46,16 @@ class DbManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLocalLibraryItemByLId(libraryItemId:String):LocalLibraryItem? {
|
fun getLocalLibraryItemByLId(libraryItemId:String): LocalLibraryItem? {
|
||||||
return getLocalLibraryItems().find { it.libraryItemId == libraryItemId }
|
return getLocalLibraryItems().find { it.libraryItemId == libraryItemId }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLocalLibraryItem(localLibraryItemId:String):LocalLibraryItem? {
|
fun getLocalLibraryItem(localLibraryItemId:String): LocalLibraryItem? {
|
||||||
return Paper.book("localLibraryItems").read(localLibraryItemId)
|
return Paper.book("localLibraryItems").read(localLibraryItemId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLocalLibraryItemWithEpisode(podcastEpisodeId:String):LibraryItemWithEpisode? {
|
fun getLocalLibraryItemWithEpisode(podcastEpisodeId:String): LibraryItemWithEpisode? {
|
||||||
var podcastEpisode:PodcastEpisode? = null
|
var podcastEpisode: PodcastEpisode? = null
|
||||||
val localLibraryItem = getLocalLibraryItems("podcast").find { localLibraryItem ->
|
val localLibraryItem = getLocalLibraryItems("podcast").find { localLibraryItem ->
|
||||||
val podcast = localLibraryItem.media as Podcast
|
val podcast = localLibraryItem.media as Podcast
|
||||||
podcastEpisode = podcast.episodes?.find { it.id == podcastEpisodeId }
|
podcastEpisode = podcast.episodes?.find { it.id == podcastEpisodeId }
|
||||||
|
@ -77,15 +78,15 @@ class DbManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveLocalLibraryItem(localLibraryItem:LocalLibraryItem) {
|
fun saveLocalLibraryItem(localLibraryItem: LocalLibraryItem) {
|
||||||
Paper.book("localLibraryItems").write(localLibraryItem.id, localLibraryItem)
|
Paper.book("localLibraryItems").write(localLibraryItem.id, localLibraryItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveLocalFolder(localFolder:LocalFolder) {
|
fun saveLocalFolder(localFolder: LocalFolder) {
|
||||||
Paper.book("localFolders").write(localFolder.id,localFolder)
|
Paper.book("localFolders").write(localFolder.id,localFolder)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getLocalFolder(folderId:String):LocalFolder? {
|
fun getLocalFolder(folderId:String): LocalFolder? {
|
||||||
return Paper.book("localFolders").read(folderId)
|
return Paper.book("localFolders").read(folderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,7 +108,7 @@ class DbManager {
|
||||||
Paper.book("localFolders").delete(folderId)
|
Paper.book("localFolders").delete(folderId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveDownloadItem(downloadItem: AbsDownloader.DownloadItem) {
|
fun saveDownloadItem(downloadItem: DownloadItem) {
|
||||||
Paper.book("downloadItems").write(downloadItem.id, downloadItem)
|
Paper.book("downloadItems").write(downloadItem.id, downloadItem)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,21 +116,21 @@ class DbManager {
|
||||||
Paper.book("downloadItems").delete(downloadItemId)
|
Paper.book("downloadItems").delete(downloadItemId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDownloadItems():List<AbsDownloader.DownloadItem> {
|
fun getDownloadItems():List<DownloadItem> {
|
||||||
val downloadItems:MutableList<AbsDownloader.DownloadItem> = mutableListOf()
|
val downloadItems:MutableList<DownloadItem> = mutableListOf()
|
||||||
Paper.book("downloadItems").allKeys.forEach { downloadItemId ->
|
Paper.book("downloadItems").allKeys.forEach { downloadItemId ->
|
||||||
Paper.book("downloadItems").read<AbsDownloader.DownloadItem>(downloadItemId)?.let {
|
Paper.book("downloadItems").read<DownloadItem>(downloadItemId)?.let {
|
||||||
downloadItems.add(it)
|
downloadItems.add(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return downloadItems
|
return downloadItems
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveLocalMediaProgress(mediaProgress:LocalMediaProgress) {
|
fun saveLocalMediaProgress(mediaProgress: LocalMediaProgress) {
|
||||||
Paper.book("localMediaProgress").write(mediaProgress.id,mediaProgress)
|
Paper.book("localMediaProgress").write(mediaProgress.id,mediaProgress)
|
||||||
}
|
}
|
||||||
// For books this will just be the localLibraryItemId for podcast episodes this will be "{localLibraryItemId}-{episodeId}"
|
// For books this will just be the localLibraryItemId for podcast episodes this will be "{localLibraryItemId}-{episodeId}"
|
||||||
fun getLocalMediaProgress(localMediaProgressId:String):LocalMediaProgress? {
|
fun getLocalMediaProgress(localMediaProgressId:String): LocalMediaProgress? {
|
||||||
return Paper.book("localMediaProgress").read(localMediaProgressId)
|
return Paper.book("localMediaProgress").read(localMediaProgressId)
|
||||||
}
|
}
|
||||||
fun getAllLocalMediaProgress():List<LocalMediaProgress> {
|
fun getAllLocalMediaProgress():List<LocalMediaProgress> {
|
||||||
|
@ -236,18 +237,18 @@ class DbManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveLocalPlaybackSession(playbackSession:PlaybackSession) {
|
fun saveLocalPlaybackSession(playbackSession: PlaybackSession) {
|
||||||
Paper.book("localPlaybackSession").write(playbackSession.id,playbackSession)
|
Paper.book("localPlaybackSession").write(playbackSession.id,playbackSession)
|
||||||
}
|
}
|
||||||
fun getLocalPlaybackSession(playbackSessionId:String):PlaybackSession? {
|
fun getLocalPlaybackSession(playbackSessionId:String): PlaybackSession? {
|
||||||
return Paper.book("localPlaybackSession").read(playbackSessionId)
|
return Paper.book("localPlaybackSession").read(playbackSessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun saveMediaItemHistory(mediaItemHistory:MediaItemHistory) {
|
fun saveMediaItemHistory(mediaItemHistory: MediaItemHistory) {
|
||||||
Paper.book("mediaItemHistory").write(mediaItemHistory.id,mediaItemHistory)
|
Paper.book("mediaItemHistory").write(mediaItemHistory.id,mediaItemHistory)
|
||||||
}
|
}
|
||||||
fun getMediaItemHistory(id:String):MediaItemHistory? {
|
fun getMediaItemHistory(id:String): MediaItemHistory? {
|
||||||
return Paper.book("mediaItemHistory").read(id)
|
return Paper.book("mediaItemHistory").read(id)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -0,0 +1,234 @@
|
||||||
|
package com.audiobookshelf.app.managers
|
||||||
|
|
||||||
|
import android.app.DownloadManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.documentfile.provider.DocumentFile
|
||||||
|
import com.anggrayudi.storage.callback.FileCallback
|
||||||
|
import com.anggrayudi.storage.file.DocumentFileCompat
|
||||||
|
import com.anggrayudi.storage.file.MimeType
|
||||||
|
import com.anggrayudi.storage.file.getAbsolutePath
|
||||||
|
import com.anggrayudi.storage.file.moveFileTo
|
||||||
|
import com.anggrayudi.storage.media.FileDescription
|
||||||
|
import com.audiobookshelf.app.MainActivity
|
||||||
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
|
import com.audiobookshelf.app.device.FolderScanner
|
||||||
|
import com.audiobookshelf.app.models.DownloadItem
|
||||||
|
import com.audiobookshelf.app.models.DownloadItemPart
|
||||||
|
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||||
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
|
import com.getcapacitor.JSObject
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class DownloadItemManager(var downloadManager:DownloadManager, var folderScanner: FolderScanner, var mainActivity: MainActivity, var clientEventEmitter:DownloadEventEmitter) {
|
||||||
|
val tag = "DownloadItemManager"
|
||||||
|
private val maxSimultaneousDownloads = 5
|
||||||
|
private var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||||
|
|
||||||
|
enum class DownloadCheckStatus {
|
||||||
|
InProgress,
|
||||||
|
Successful,
|
||||||
|
Failed
|
||||||
|
}
|
||||||
|
|
||||||
|
var downloadItemQueue: MutableList<DownloadItem> = mutableListOf()
|
||||||
|
var currentDownloadItemParts: MutableList<DownloadItemPart> = mutableListOf()
|
||||||
|
|
||||||
|
interface DownloadEventEmitter {
|
||||||
|
fun onDownloadItem(downloadItem:DownloadItem)
|
||||||
|
fun onDownloadItemPartUpdate(downloadItemPart:DownloadItemPart)
|
||||||
|
fun onDownloadItemComplete(jsobj:JSObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
var isDownloading:Boolean = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addDownloadItem(downloadItem:DownloadItem) {
|
||||||
|
DeviceManager.dbManager.saveDownloadItem(downloadItem)
|
||||||
|
Log.i(tag, "Add download item ${downloadItem.media.metadata.title}")
|
||||||
|
|
||||||
|
downloadItemQueue.add(downloadItem)
|
||||||
|
clientEventEmitter.onDownloadItem(downloadItem)
|
||||||
|
checkUpdateDownloadQueue()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkUpdateDownloadQueue() {
|
||||||
|
for (downloadItem in downloadItemQueue) {
|
||||||
|
val numPartsToGet = maxSimultaneousDownloads - currentDownloadItemParts.size
|
||||||
|
val nextDownloadItemParts = downloadItem.getNextDownloadItemParts(numPartsToGet)
|
||||||
|
Log.d(tag, "checkUpdateDownloadQueue: numPartsToGet=$numPartsToGet, nextDownloadItemParts=${nextDownloadItemParts.size}")
|
||||||
|
|
||||||
|
if (nextDownloadItemParts.size > 0) {
|
||||||
|
nextDownloadItemParts.forEach {
|
||||||
|
val dlRequest = it.getDownloadRequest()
|
||||||
|
val downloadId = downloadManager.enqueue(dlRequest)
|
||||||
|
it.downloadId = downloadId
|
||||||
|
Log.d(tag, "checkUpdateDownloadQueue: Starting download item part, downloadId=$downloadId")
|
||||||
|
currentDownloadItemParts.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentDownloadItemParts.size >= maxSimultaneousDownloads) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentDownloadItemParts.size > 0) startWatchingDownloads()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startWatchingDownloads() {
|
||||||
|
if (isDownloading) return // Already watching
|
||||||
|
|
||||||
|
GlobalScope.launch(Dispatchers.IO) {
|
||||||
|
Log.d(tag, "Starting watching downloads")
|
||||||
|
isDownloading = true
|
||||||
|
|
||||||
|
while (currentDownloadItemParts.size > 0) {
|
||||||
|
|
||||||
|
val itemParts = currentDownloadItemParts.filter { !it.isMoving }.map { it }
|
||||||
|
for (downloadItemPart in itemParts) {
|
||||||
|
val downloadCheckStatus = checkDownloadItemPart(downloadItemPart)
|
||||||
|
clientEventEmitter.onDownloadItemPartUpdate(downloadItemPart)
|
||||||
|
|
||||||
|
// Will move to final destination, remove current item parts, and check if download item is finished
|
||||||
|
handleDownloadItemPartCheck(downloadCheckStatus, downloadItemPart)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentDownloadItemParts.size < maxSimultaneousDownloads) {
|
||||||
|
checkUpdateDownloadQueue()
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(500)
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(tag, "Finished watching downloads")
|
||||||
|
isDownloading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkDownloadItemPart(downloadItemPart:DownloadItemPart):DownloadCheckStatus {
|
||||||
|
val downloadId = downloadItemPart.downloadId ?: return DownloadCheckStatus.Failed
|
||||||
|
|
||||||
|
val query = DownloadManager.Query().setFilterById(downloadId)
|
||||||
|
downloadManager.query(query).use {
|
||||||
|
if (it.moveToFirst()) {
|
||||||
|
val bytesColumnIndex = it.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)
|
||||||
|
val statusColumnIndex = it.getColumnIndex(DownloadManager.COLUMN_STATUS)
|
||||||
|
val bytesDownloadedColumnIndex = it.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)
|
||||||
|
|
||||||
|
val totalBytes = if (bytesColumnIndex >= 0) it.getInt(bytesColumnIndex) else 0
|
||||||
|
val downloadStatus = if (statusColumnIndex >= 0) it.getInt(statusColumnIndex) else 0
|
||||||
|
val bytesDownloadedSoFar = if (bytesDownloadedColumnIndex >= 0) it.getInt(bytesDownloadedColumnIndex) else 0
|
||||||
|
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} bytes $totalBytes | bytes dled $bytesDownloadedSoFar | downloadStatus $downloadStatus")
|
||||||
|
|
||||||
|
if (downloadStatus == DownloadManager.STATUS_SUCCESSFUL) {
|
||||||
|
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Successful")
|
||||||
|
downloadItemPart.completed = true
|
||||||
|
return DownloadCheckStatus.Successful
|
||||||
|
} else if (downloadStatus == DownloadManager.STATUS_FAILED) {
|
||||||
|
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Failed")
|
||||||
|
downloadItemPart.completed = true
|
||||||
|
downloadItemPart.failed = true
|
||||||
|
return DownloadCheckStatus.Failed
|
||||||
|
} else {
|
||||||
|
//update progress
|
||||||
|
val percentProgress = if (totalBytes > 0) ((bytesDownloadedSoFar * 100L) / totalBytes) else 0
|
||||||
|
Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Progress = $percentProgress%")
|
||||||
|
downloadItemPart.progress = percentProgress
|
||||||
|
return DownloadCheckStatus.InProgress
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.d(tag, "Download ${downloadItemPart.filename} not found in dlmanager")
|
||||||
|
downloadItemPart.completed = true
|
||||||
|
downloadItemPart.failed = true
|
||||||
|
return DownloadCheckStatus.Failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleDownloadItemPartCheck(downloadCheckStatus:DownloadCheckStatus, downloadItemPart:DownloadItemPart) {
|
||||||
|
val downloadItem = downloadItemQueue.find { it.id == downloadItemPart.downloadItemId }
|
||||||
|
if (downloadItem == null) {
|
||||||
|
Log.e(tag, "Download item part finished but download item not found ${downloadItemPart.filename}")
|
||||||
|
currentDownloadItemParts.remove(downloadItemPart)
|
||||||
|
} else if (downloadCheckStatus == DownloadCheckStatus.Successful) {
|
||||||
|
|
||||||
|
val file = DocumentFileCompat.fromUri(mainActivity, downloadItemPart.destinationUri)
|
||||||
|
Log.d(tag, "DOWNLOAD: DESTINATION URI ${downloadItemPart.destinationUri}")
|
||||||
|
|
||||||
|
val fcb = object : FileCallback() {
|
||||||
|
override fun onPrepare() {
|
||||||
|
Log.d(tag, "DOWNLOAD: PREPARING MOVE FILE")
|
||||||
|
}
|
||||||
|
override fun onFailed(errorCode: ErrorCode) {
|
||||||
|
Log.e(tag, "DOWNLOAD: FAILED TO MOVE FILE $errorCode")
|
||||||
|
downloadItemPart.failed = true
|
||||||
|
downloadItemPart.isMoving = false
|
||||||
|
file?.delete()
|
||||||
|
checkDownloadItemFinished(downloadItem)
|
||||||
|
currentDownloadItemParts.remove(downloadItemPart)
|
||||||
|
}
|
||||||
|
override fun onCompleted(result:Any) {
|
||||||
|
Log.d(tag, "DOWNLOAD: FILE MOVE COMPLETED")
|
||||||
|
val resultDocFile = result as DocumentFile
|
||||||
|
Log.d(tag, "DOWNLOAD: COMPLETED FILE INFO ${resultDocFile.getAbsolutePath(mainActivity)}")
|
||||||
|
|
||||||
|
// Rename to fix appended .mp4 on m4b files
|
||||||
|
// REF: https://github.com/anggrayudi/SimpleStorage/issues/94
|
||||||
|
resultDocFile.renameTo(downloadItemPart.filename)
|
||||||
|
|
||||||
|
downloadItemPart.moved = true
|
||||||
|
downloadItemPart.isMoving = false
|
||||||
|
checkDownloadItemFinished(downloadItem)
|
||||||
|
currentDownloadItemParts.remove(downloadItemPart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val localFolderFile = DocumentFileCompat.fromUri(mainActivity, Uri.parse(downloadItemPart.localFolderUrl))
|
||||||
|
if (localFolderFile == null) {
|
||||||
|
// fAILED
|
||||||
|
downloadItemPart.failed = true
|
||||||
|
Log.e(tag, "Local Folder File from uri is null")
|
||||||
|
checkDownloadItemFinished(downloadItem)
|
||||||
|
currentDownloadItemParts.remove(downloadItemPart)
|
||||||
|
} else {
|
||||||
|
downloadItemPart.isMoving = true
|
||||||
|
val mimetype = if (downloadItemPart.audioTrack != null) MimeType.AUDIO else MimeType.IMAGE
|
||||||
|
val fileDescription = FileDescription(downloadItemPart.filename, downloadItemPart.itemTitle, mimetype)
|
||||||
|
file?.moveFileTo(mainActivity, localFolderFile, fileDescription, fcb)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (downloadCheckStatus != DownloadCheckStatus.InProgress) {
|
||||||
|
checkDownloadItemFinished(downloadItem)
|
||||||
|
currentDownloadItemParts.remove(downloadItemPart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkDownloadItemFinished(downloadItem:DownloadItem) {
|
||||||
|
if (downloadItem.isDownloadFinished) {
|
||||||
|
Log.i(tag, "Download Item finished ${downloadItem.media.metadata.title}")
|
||||||
|
|
||||||
|
val downloadItemScanResult = folderScanner.scanDownloadItem(downloadItem)
|
||||||
|
Log.d(tag, "Item download complete ${downloadItem.itemTitle} | local library item id: ${downloadItemScanResult?.localLibraryItem?.id}")
|
||||||
|
|
||||||
|
val jsobj = JSObject()
|
||||||
|
jsobj.put("libraryItemId", downloadItem.id)
|
||||||
|
jsobj.put("localFolderId", downloadItem.localFolder.id)
|
||||||
|
|
||||||
|
downloadItemScanResult?.localLibraryItem?.let { localLibraryItem ->
|
||||||
|
jsobj.put("localLibraryItem", JSObject(jacksonMapper.writeValueAsString(localLibraryItem)))
|
||||||
|
}
|
||||||
|
downloadItemScanResult?.localMediaProgress?.let { localMediaProgress ->
|
||||||
|
jsobj.put("localMediaProgress", JSObject(jacksonMapper.writeValueAsString(localMediaProgress)))
|
||||||
|
}
|
||||||
|
|
||||||
|
clientEventEmitter.onDownloadItemComplete(jsobj)
|
||||||
|
downloadItemQueue.remove(downloadItem)
|
||||||
|
DeviceManager.dbManager.removeDownloadItem(downloadItem.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
package com.audiobookshelf.app.models
|
||||||
|
|
||||||
|
import com.audiobookshelf.app.data.LocalFolder
|
||||||
|
import com.audiobookshelf.app.data.MediaProgress
|
||||||
|
import com.audiobookshelf.app.data.MediaType
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
|
||||||
|
data class DownloadItem(
|
||||||
|
val id: String,
|
||||||
|
val libraryItemId:String,
|
||||||
|
val episodeId:String?,
|
||||||
|
val userMediaProgress: MediaProgress?,
|
||||||
|
val serverConnectionConfigId:String,
|
||||||
|
val serverAddress:String,
|
||||||
|
val serverUserId:String,
|
||||||
|
val mediaType: String,
|
||||||
|
val itemFolderPath:String,
|
||||||
|
val localFolder: LocalFolder,
|
||||||
|
val itemTitle: String,
|
||||||
|
val media: MediaType,
|
||||||
|
val downloadItemParts: MutableList<DownloadItemPart>
|
||||||
|
) {
|
||||||
|
@get:JsonIgnore
|
||||||
|
val isDownloadFinished get() = !downloadItemParts.any { !it.completed || it.isMoving }
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun getTotalFileSize(): Long {
|
||||||
|
var totalSize = 0L
|
||||||
|
downloadItemParts.forEach { totalSize += it.fileSize }
|
||||||
|
return totalSize
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun getNextDownloadItemParts(limit:Int): MutableList<DownloadItemPart> {
|
||||||
|
val itemParts = mutableListOf<DownloadItemPart>()
|
||||||
|
if (limit == 0) return itemParts
|
||||||
|
|
||||||
|
for (it in downloadItemParts) {
|
||||||
|
if (!it.completed && it.downloadId == null) {
|
||||||
|
itemParts.add(it)
|
||||||
|
if (itemParts.size > limit) break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemParts
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package com.audiobookshelf.app.models
|
||||||
|
|
||||||
|
import android.app.DownloadManager
|
||||||
|
import android.net.Uri
|
||||||
|
import android.os.Environment
|
||||||
|
import android.util.Log
|
||||||
|
import com.audiobookshelf.app.data.AudioTrack
|
||||||
|
import com.audiobookshelf.app.data.LocalFolder
|
||||||
|
import com.audiobookshelf.app.data.PodcastEpisode
|
||||||
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
data class DownloadItemPart(
|
||||||
|
val id: String,
|
||||||
|
val downloadItemId: String,
|
||||||
|
val filename: String,
|
||||||
|
val finalDestinationPath:String,
|
||||||
|
val itemTitle: String,
|
||||||
|
val serverPath: String,
|
||||||
|
val localFolderName: String,
|
||||||
|
val localFolderUrl: String,
|
||||||
|
val localFolderId: String,
|
||||||
|
val audioTrack: AudioTrack?,
|
||||||
|
val episode: PodcastEpisode?,
|
||||||
|
var completed:Boolean,
|
||||||
|
var moved:Boolean,
|
||||||
|
var isMoving:Boolean,
|
||||||
|
var failed:Boolean,
|
||||||
|
@JsonIgnore val uri: Uri,
|
||||||
|
@JsonIgnore val destinationUri: Uri,
|
||||||
|
@JsonIgnore val finalDestinationUri: Uri,
|
||||||
|
var downloadId: Long?,
|
||||||
|
var progress: Long
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun make(downloadItemId:String, filename:String, destinationFile: File, finalDestinationFile: File, itemTitle:String, serverPath:String, localFolder: LocalFolder, audioTrack: AudioTrack?, episode: PodcastEpisode?) :DownloadItemPart {
|
||||||
|
val destinationUri = Uri.fromFile(destinationFile)
|
||||||
|
val finalDestinationUri = Uri.fromFile(finalDestinationFile)
|
||||||
|
|
||||||
|
var downloadUrl = "${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}"
|
||||||
|
if (serverPath.endsWith("/cover")) downloadUrl += "&format=jpeg" // For cover images force to jpeg
|
||||||
|
val downloadUri = Uri.parse(downloadUrl)
|
||||||
|
Log.d("DownloadItemPart", "Audio File Destination Uri: $destinationUri | Final Destination Uri: $finalDestinationUri | Download URI $downloadUri")
|
||||||
|
return DownloadItemPart(
|
||||||
|
id = DeviceManager.getBase64Id(finalDestinationFile.absolutePath),
|
||||||
|
downloadItemId,
|
||||||
|
filename = filename,
|
||||||
|
finalDestinationPath = finalDestinationFile.absolutePath,
|
||||||
|
itemTitle = itemTitle,
|
||||||
|
serverPath = serverPath,
|
||||||
|
localFolderName = localFolder.name,
|
||||||
|
localFolderUrl = localFolder.contentUrl,
|
||||||
|
localFolderId = localFolder.id,
|
||||||
|
audioTrack = audioTrack,
|
||||||
|
episode = episode,
|
||||||
|
completed = false,
|
||||||
|
moved = false,
|
||||||
|
isMoving = false,
|
||||||
|
failed = false,
|
||||||
|
uri = downloadUri,
|
||||||
|
destinationUri = destinationUri,
|
||||||
|
finalDestinationUri = finalDestinationUri,
|
||||||
|
downloadId = null,
|
||||||
|
progress = 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@get:JsonIgnore
|
||||||
|
val fileSize get() = audioTrack?.metadata?.size ?: 0
|
||||||
|
|
||||||
|
@JsonIgnore
|
||||||
|
fun getDownloadRequest(): DownloadManager.Request {
|
||||||
|
val dlRequest = DownloadManager.Request(uri)
|
||||||
|
dlRequest.setTitle(filename)
|
||||||
|
dlRequest.setDescription("Downloading to $localFolderName for book $itemTitle")
|
||||||
|
dlRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
|
||||||
|
dlRequest.setDestinationUri(destinationUri)
|
||||||
|
return dlRequest
|
||||||
|
}
|
||||||
|
}
|
|
@ -31,6 +31,7 @@ import com.audiobookshelf.app.R
|
||||||
import com.audiobookshelf.app.data.*
|
import com.audiobookshelf.app.data.*
|
||||||
import com.audiobookshelf.app.data.DeviceInfo
|
import com.audiobookshelf.app.data.DeviceInfo
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
|
import com.audiobookshelf.app.managers.DbManager
|
||||||
import com.audiobookshelf.app.media.MediaManager
|
import com.audiobookshelf.app.media.MediaManager
|
||||||
import com.audiobookshelf.app.server.ApiHandler
|
import com.audiobookshelf.app.server.ApiHandler
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||||
|
@ -49,8 +50,8 @@ import kotlin.concurrent.schedule
|
||||||
|
|
||||||
|
|
||||||
const val SLEEP_TIMER_WAKE_UP_EXPIRATION = 120000L // 2m
|
const val SLEEP_TIMER_WAKE_UP_EXPIRATION = 120000L // 2m
|
||||||
const val PLAYER_CAST = "cast-player";
|
const val PLAYER_CAST = "cast-player"
|
||||||
const val PLAYER_EXO = "exo-player";
|
const val PLAYER_EXO = "exo-player"
|
||||||
|
|
||||||
class PlayerNotificationService : MediaBrowserServiceCompat() {
|
class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
|
|
||||||
|
@ -61,6 +62,8 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
var isSwitchingPlayer = false // Used when switching between cast player and exoplayer
|
var isSwitchingPlayer = false // Used when switching between cast player and exoplayer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val tag = "PlayerNotificationService"
|
||||||
|
|
||||||
interface ClientEventEmitter {
|
interface ClientEventEmitter {
|
||||||
fun onPlaybackSession(playbackSession:PlaybackSession)
|
fun onPlaybackSession(playbackSession:PlaybackSession)
|
||||||
fun onPlaybackClosed()
|
fun onPlaybackClosed()
|
||||||
|
@ -76,8 +79,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() {
|
||||||
fun onNetworkMeteredChanged(isUnmetered:Boolean)
|
fun onNetworkMeteredChanged(isUnmetered:Boolean)
|
||||||
fun onMediaItemHistoryUpdated(mediaItemHistory:MediaItemHistory)
|
fun onMediaItemHistoryUpdated(mediaItemHistory:MediaItemHistory)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val tag = "PlayerService"
|
|
||||||
private val binder = LocalBinder()
|
private val binder = LocalBinder()
|
||||||
|
|
||||||
var clientEventEmitter:ClientEventEmitter? = null
|
var clientEventEmitter:ClientEventEmitter? = null
|
||||||
|
|
|
@ -10,12 +10,15 @@ import androidx.documentfile.provider.DocumentFile
|
||||||
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.anggrayudi.storage.media.MediaStoreCompat
|
||||||
import com.audiobookshelf.app.MainActivity
|
import com.audiobookshelf.app.MainActivity
|
||||||
import com.audiobookshelf.app.data.*
|
import com.audiobookshelf.app.data.*
|
||||||
import com.audiobookshelf.app.device.DeviceManager
|
import com.audiobookshelf.app.device.DeviceManager
|
||||||
import com.audiobookshelf.app.device.FolderScanner
|
import com.audiobookshelf.app.device.FolderScanner
|
||||||
|
import com.audiobookshelf.app.models.DownloadItem
|
||||||
|
import com.audiobookshelf.app.models.DownloadItemPart
|
||||||
import com.audiobookshelf.app.server.ApiHandler
|
import com.audiobookshelf.app.server.ApiHandler
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
import com.audiobookshelf.app.managers.DownloadItemManager
|
||||||
import com.fasterxml.jackson.core.json.JsonReadFeature
|
import com.fasterxml.jackson.core.json.JsonReadFeature
|
||||||
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||||
import com.getcapacitor.JSObject
|
import com.getcapacitor.JSObject
|
||||||
|
@ -32,99 +35,34 @@ import java.io.File
|
||||||
@CapacitorPlugin(name = "AbsDownloader")
|
@CapacitorPlugin(name = "AbsDownloader")
|
||||||
class AbsDownloader : Plugin() {
|
class AbsDownloader : Plugin() {
|
||||||
private val tag = "AbsDownloader"
|
private val tag = "AbsDownloader"
|
||||||
var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
private var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature())
|
||||||
|
|
||||||
lateinit var mainActivity: MainActivity
|
lateinit var mainActivity: MainActivity
|
||||||
lateinit var downloadManager: DownloadManager
|
lateinit var downloadManager: DownloadManager
|
||||||
lateinit var apiHandler: ApiHandler
|
lateinit var apiHandler: ApiHandler
|
||||||
lateinit var folderScanner: FolderScanner
|
lateinit var folderScanner: FolderScanner
|
||||||
|
lateinit var downloadItemManager: DownloadItemManager
|
||||||
|
|
||||||
data class DownloadItemPart(
|
private var downloadQueue: MutableList<DownloadItem> = mutableListOf()
|
||||||
val id: String,
|
|
||||||
val filename: String,
|
|
||||||
val finalDestinationPath:String,
|
|
||||||
val itemTitle: String,
|
|
||||||
val serverPath: String,
|
|
||||||
val localFolderName: String,
|
|
||||||
val localFolderUrl: String,
|
|
||||||
val localFolderId: String,
|
|
||||||
val audioTrack: AudioTrack?,
|
|
||||||
val episode:PodcastEpisode?,
|
|
||||||
var completed:Boolean,
|
|
||||||
var moved:Boolean,
|
|
||||||
var failed:Boolean,
|
|
||||||
@JsonIgnore val uri: Uri,
|
|
||||||
@JsonIgnore val destinationUri: Uri,
|
|
||||||
@JsonIgnore val finalDestinationUri: Uri,
|
|
||||||
var downloadId: Long?,
|
|
||||||
var progress: Long
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
fun make(filename:String, destinationFile:File, finalDestinationFile:File, itemTitle:String, serverPath:String, localFolder:LocalFolder, audioTrack:AudioTrack?, episode:PodcastEpisode?) :DownloadItemPart {
|
|
||||||
val destinationUri = Uri.fromFile(destinationFile)
|
|
||||||
val finalDestinationUri = Uri.fromFile(finalDestinationFile)
|
|
||||||
|
|
||||||
var downloadUrl = "${DeviceManager.serverAddress}${serverPath}?token=${DeviceManager.token}"
|
val clientEventEmitter = (object : DownloadItemManager.DownloadEventEmitter {
|
||||||
if (serverPath.endsWith("/cover")) downloadUrl += "&format=jpeg" // For cover images force to jpeg
|
override fun onDownloadItem(downloadItem:DownloadItem) {
|
||||||
val downloadUri = Uri.parse(downloadUrl)
|
notifyListeners("onDownloadItem", JSObject(jacksonMapper.writeValueAsString(downloadItem)))
|
||||||
Log.d("DownloadItemPart", "Audio File Destination Uri: $destinationUri | Final Destination Uri: $finalDestinationUri | Download URI $downloadUri")
|
|
||||||
return DownloadItemPart(
|
|
||||||
id = DeviceManager.getBase64Id(finalDestinationFile.absolutePath),
|
|
||||||
filename = filename, finalDestinationFile.absolutePath,
|
|
||||||
itemTitle = itemTitle,
|
|
||||||
serverPath = serverPath,
|
|
||||||
localFolderName = localFolder.name,
|
|
||||||
localFolderUrl = localFolder.contentUrl,
|
|
||||||
localFolderId = localFolder.id,
|
|
||||||
audioTrack = audioTrack,
|
|
||||||
episode = episode,
|
|
||||||
completed = false,
|
|
||||||
moved = false,
|
|
||||||
failed = false,
|
|
||||||
uri = downloadUri,
|
|
||||||
destinationUri = destinationUri,
|
|
||||||
finalDestinationUri = finalDestinationUri,
|
|
||||||
downloadId = null,
|
|
||||||
progress = 0
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
override fun onDownloadItemPartUpdate(downloadItemPart:DownloadItemPart) {
|
||||||
|
notifyListeners("onDownloadItemPartUpdate", JSObject(jacksonMapper.writeValueAsString(downloadItemPart)))
|
||||||
}
|
}
|
||||||
|
override fun onDownloadItemComplete(jsobj:JSObject) {
|
||||||
@JsonIgnore
|
notifyListeners("onItemDownloadComplete", jsobj)
|
||||||
fun getDownloadRequest(): DownloadManager.Request {
|
|
||||||
val dlRequest = DownloadManager.Request(uri)
|
|
||||||
dlRequest.setTitle(filename)
|
|
||||||
dlRequest.setDescription("Downloading to $localFolderName for book $itemTitle")
|
|
||||||
dlRequest.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE)
|
|
||||||
dlRequest.setDestinationUri(destinationUri)
|
|
||||||
return dlRequest
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
|
||||||
data class DownloadItem(
|
|
||||||
val id: String,
|
|
||||||
val libraryItemId:String,
|
|
||||||
val episodeId:String?,
|
|
||||||
val userMediaProgress:MediaProgress?,
|
|
||||||
val serverConnectionConfigId:String,
|
|
||||||
val serverAddress:String,
|
|
||||||
val serverUserId:String,
|
|
||||||
val mediaType: String,
|
|
||||||
val itemFolderPath:String,
|
|
||||||
val localFolder: LocalFolder,
|
|
||||||
val itemTitle: String,
|
|
||||||
val media:MediaType,
|
|
||||||
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)
|
folderScanner = FolderScanner(mainActivity)
|
||||||
apiHandler = ApiHandler(mainActivity)
|
apiHandler = ApiHandler(mainActivity)
|
||||||
|
downloadItemManager = DownloadItemManager(downloadManager, folderScanner, mainActivity, clientEventEmitter)
|
||||||
Log.d(tag, "Build SDK ${Build.VERSION.SDK_INT}")
|
Log.d(tag, "Build SDK ${Build.VERSION.SDK_INT}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,13 +142,16 @@ class AbsDownloader : Plugin() {
|
||||||
|
|
||||||
private fun startLibraryItemDownload(libraryItem: LibraryItem, localFolder: LocalFolder, episode:PodcastEpisode?) {
|
private fun startLibraryItemDownload(libraryItem: LibraryItem, localFolder: LocalFolder, episode:PodcastEpisode?) {
|
||||||
val tempFolderPath = mainActivity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
|
val tempFolderPath = mainActivity.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
// val tempFolderPath = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
|
||||||
|
|
||||||
|
Log.d(tag, "downloadCacheDirectory=$tempFolderPath")
|
||||||
|
|
||||||
if (libraryItem.mediaType == "book") {
|
if (libraryItem.mediaType == "book") {
|
||||||
val bookTitle = cleanStringForFileSystem(libraryItem.media.metadata.title)
|
val bookTitle = cleanStringForFileSystem(libraryItem.media.metadata.title)
|
||||||
|
|
||||||
val tracks = libraryItem.media.getAudioTracks()
|
val tracks = libraryItem.media.getAudioTracks()
|
||||||
Log.d(tag, "Starting library item download with ${tracks.size} tracks")
|
Log.d(tag, "Starting library item download with ${tracks.size} tracks")
|
||||||
val itemFolderPath = localFolder.absolutePath + "/" + bookTitle
|
val itemFolderPath = "${localFolder.absolutePath}/$bookTitle"
|
||||||
val downloadItem = DownloadItem(libraryItem.id, libraryItem.id, null, libraryItem.userMediaProgress,DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, libraryItem.media, mutableListOf())
|
val downloadItem = DownloadItem(libraryItem.id, libraryItem.id, null, libraryItem.userMediaProgress,DeviceManager.serverConnectionConfig?.id ?: "", DeviceManager.serverAddress, DeviceManager.serverUserId, libraryItem.mediaType, itemFolderPath, localFolder, bookTitle, libraryItem.media, mutableListOf())
|
||||||
|
|
||||||
// Create download item part for each audio track
|
// Create download item part for each audio track
|
||||||
|
@ -222,43 +163,54 @@ class AbsDownloader : Plugin() {
|
||||||
val finalDestinationFile = File("$itemFolderPath/$destinationFilename")
|
val finalDestinationFile = File("$itemFolderPath/$destinationFilename")
|
||||||
val destinationFile = File("$tempFolderPath/$destinationFilename")
|
val destinationFile = File("$tempFolderPath/$destinationFilename")
|
||||||
|
|
||||||
|
if (destinationFile.exists()) {
|
||||||
|
Log.d(tag, "TEMP Audio file already exists, removing it from ${destinationFile.absolutePath}")
|
||||||
|
destinationFile.delete()
|
||||||
|
}
|
||||||
|
|
||||||
if (finalDestinationFile.exists()) {
|
if (finalDestinationFile.exists()) {
|
||||||
Log.d(tag, "Audio file already exists, removing it from ${finalDestinationFile.absolutePath}")
|
Log.d(tag, "Audio file already exists, removing it from ${finalDestinationFile.absolutePath}")
|
||||||
finalDestinationFile.delete()
|
finalDestinationFile.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadItemPart = DownloadItemPart.make(destinationFilename,destinationFile,finalDestinationFile,bookTitle,serverPath,localFolder,audioTrack,null)
|
val downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,bookTitle,serverPath,localFolder,audioTrack,null)
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
|
|
||||||
val dlRequest = downloadItemPart.getDownloadRequest()
|
// val dlRequest = downloadItemPart.getDownloadRequest()
|
||||||
val downloadId = downloadManager.enqueue(dlRequest)
|
// val downloadId = downloadManager.enqueue(dlRequest)
|
||||||
downloadItemPart.downloadId = downloadId
|
// downloadItemPart.downloadId = downloadId
|
||||||
}
|
}
|
||||||
|
|
||||||
if (downloadItem.downloadItemParts.isNotEmpty()) {
|
if (downloadItem.downloadItemParts.isNotEmpty()) {
|
||||||
// Add cover download item
|
// Add cover download item
|
||||||
if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) {
|
if (libraryItem.media.coverPath != null && libraryItem.media.coverPath?.isNotEmpty() == true) {
|
||||||
val serverPath = "/api/items/${libraryItem.id}/cover"
|
val serverPath = "/api/items/${libraryItem.id}/cover"
|
||||||
val destinationFilename = "cover.jpg"
|
val destinationFilename = "cover-${libraryItem.id}.jpg"
|
||||||
val destinationFile = File("$tempFolderPath/$destinationFilename")
|
val destinationFile = File("$tempFolderPath/$destinationFilename")
|
||||||
val finalDestinationFile = File("$itemFolderPath/$destinationFilename")
|
val finalDestinationFile = File("$itemFolderPath/$destinationFilename")
|
||||||
|
|
||||||
|
if (destinationFile.exists()) {
|
||||||
|
Log.d(tag, "TEMP Audio file already exists, removing it from ${destinationFile.absolutePath}")
|
||||||
|
destinationFile.delete()
|
||||||
|
}
|
||||||
|
|
||||||
if (finalDestinationFile.exists()) {
|
if (finalDestinationFile.exists()) {
|
||||||
Log.d(tag, "Cover already exists, removing it from ${finalDestinationFile.absolutePath}")
|
Log.d(tag, "Cover already exists, removing it from ${finalDestinationFile.absolutePath}")
|
||||||
finalDestinationFile.delete()
|
finalDestinationFile.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
val downloadItemPart = DownloadItemPart.make(destinationFilename,destinationFile,finalDestinationFile,bookTitle,serverPath,localFolder,null,null)
|
val downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,bookTitle,serverPath,localFolder,null,null)
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
|
|
||||||
val dlRequest = downloadItemPart.getDownloadRequest()
|
// val dlRequest = downloadItemPart.getDownloadRequest()
|
||||||
val downloadId = downloadManager.enqueue(dlRequest)
|
// val downloadId = downloadManager.enqueue(dlRequest)
|
||||||
downloadItemPart.downloadId = downloadId
|
// downloadItemPart.downloadId = downloadId
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadQueue.add(downloadItem)
|
// downloadQueue.add(downloadItem)
|
||||||
startWatchingDownloads(downloadItem)
|
// startWatchingDownloads(downloadItem)
|
||||||
DeviceManager.dbManager.saveDownloadItem(downloadItem)
|
// DeviceManager.dbManager.saveDownloadItem(downloadItem)
|
||||||
|
downloadItemManager.addDownloadItem(downloadItem)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Podcast episode download
|
// Podcast episode download
|
||||||
|
@ -281,7 +233,7 @@ class AbsDownloader : Plugin() {
|
||||||
finalDestinationFile.delete()
|
finalDestinationFile.delete()
|
||||||
}
|
}
|
||||||
|
|
||||||
var downloadItemPart = DownloadItemPart.make(destinationFilename,destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,audioTrack,episode)
|
var downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,audioTrack,episode)
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
|
|
||||||
var dlRequest = downloadItemPart.getDownloadRequest()
|
var dlRequest = downloadItemPart.getDownloadRequest()
|
||||||
|
@ -298,7 +250,7 @@ class AbsDownloader : Plugin() {
|
||||||
if (finalDestinationFile.exists()) {
|
if (finalDestinationFile.exists()) {
|
||||||
Log.d(tag, "Podcast cover already exists - not downloading cover again")
|
Log.d(tag, "Podcast cover already exists - not downloading cover again")
|
||||||
} else {
|
} else {
|
||||||
downloadItemPart = DownloadItemPart.make(destinationFilename,destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,null,null)
|
downloadItemPart = DownloadItemPart.make(downloadItem.id, destinationFilename,destinationFile,finalDestinationFile,podcastTitle,serverPath,localFolder,null,null)
|
||||||
downloadItem.downloadItemParts.add(downloadItemPart)
|
downloadItem.downloadItemParts.add(downloadItemPart)
|
||||||
|
|
||||||
dlRequest = downloadItemPart.getDownloadRequest()
|
dlRequest = downloadItemPart.getDownloadRequest()
|
||||||
|
|
|
@ -85,7 +85,14 @@ class AbsFileSystem : Plugin() {
|
||||||
call.resolve(JSObject(jacksonMapper.writeValueAsString(localFolder)))
|
call.resolve(JSObject(jacksonMapper.writeValueAsString(localFolder)))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStorageAccessDenied(requestCode: Int, folder: DocumentFile?, storageType: StorageType) {
|
override fun onStorageAccessDenied(
|
||||||
|
requestCode: Int,
|
||||||
|
folder: DocumentFile?,
|
||||||
|
storageType: StorageType,
|
||||||
|
storageId: String
|
||||||
|
) {
|
||||||
|
Log.e(tag, "Storage Access Denied ${folder?.getAbsolutePath(mainActivity)}")
|
||||||
|
|
||||||
val jsobj = JSObject()
|
val jsobj = JSObject()
|
||||||
if (requestCode == REQUEST_CODE_SELECT_FOLDER) {
|
if (requestCode == REQUEST_CODE_SELECT_FOLDER) {
|
||||||
|
|
||||||
|
@ -99,7 +106,7 @@ class AbsFileSystem : Plugin() {
|
||||||
call.resolve(jsobj)
|
call.resolve(jsobj)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.setPositiveButton("Allow.") { _, _ -> mainActivity.storageHelper.requestStorageAccess(REQUEST_CODE_SDCARD_ACCESS, storageType) }
|
builder.setPositiveButton("Allow.") { _, _ -> mainActivity.storageHelper.requestStorageAccess(REQUEST_CODE_SDCARD_ACCESS, initialPath = FileFullPath(mainActivity, storageId, "")) }
|
||||||
builder.show()
|
builder.show()
|
||||||
} else {
|
} else {
|
||||||
Log.d(TAG, "STORAGE ACCESS DENIED $requestCode")
|
Log.d(TAG, "STORAGE ACCESS DENIED $requestCode")
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
<item name="colorAccent">@color/colorAccent</item>
|
<item name="colorAccent">@color/colorAccent</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.NoActionBar">
|
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||||
<item name="windowActionBar">false</item>
|
<item name="windowActionBar">false</item>
|
||||||
<item name="windowNoTitle">true</item>
|
<item name="windowNoTitle">true</item>
|
||||||
<item name="android:background">@null</item>
|
<item name="android:background">@null</item>
|
||||||
|
@ -18,7 +18,7 @@
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<style name="AppTheme.NoActionBarLaunch" parent="AppTheme.NoActionBar">
|
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
||||||
<!-- <item name="android:background">@drawable/screen</item>-->
|
<!-- <item name="android:background">@drawable/screen</item>-->
|
||||||
</style>
|
</style>
|
||||||
<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
|
<style name="Widget.Android.AppWidget.Container" parent="android:Widget">
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.kotlin_version = '1.5.30'
|
ext.kotlin_version = '1.7.20'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.google.gms:google-services:4.3.10'
|
classpath 'com.google.gms:google-services:4.3.13'
|
||||||
classpath 'com.android.tools.build:gradle:7.2.2'
|
classpath 'com.android.tools.build:gradle:7.4.0'
|
||||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
|
||||||
// NOTE: Do not place your application dependencies here; they belong
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
|
@ -29,3 +29,4 @@ allprojects {
|
||||||
task clean(type: Delete) {
|
task clean(type: Delete) {
|
||||||
delete rootProject.buildDir
|
delete rootProject.buildDir
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,8 +14,8 @@ project(':capacitor-haptics').projectDir = new File('../node_modules/@capacitor/
|
||||||
include ':capacitor-network'
|
include ':capacitor-network'
|
||||||
project(':capacitor-network').projectDir = new File('../node_modules/@capacitor/network/android')
|
project(':capacitor-network').projectDir = new File('../node_modules/@capacitor/network/android')
|
||||||
|
|
||||||
|
include ':capacitor-preferences'
|
||||||
|
project(':capacitor-preferences').projectDir = new File('../node_modules/@capacitor/preferences/android')
|
||||||
|
|
||||||
include ':capacitor-status-bar'
|
include ':capacitor-status-bar'
|
||||||
project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
|
project(':capacitor-status-bar').projectDir = new File('../node_modules/@capacitor/status-bar/android')
|
||||||
|
|
||||||
include ':capacitor-storage'
|
|
||||||
project(':capacitor-storage').projectDir = new File('../node_modules/@capacitor/storage/android')
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#Sun Apr 17 13:28:55 CDT 2022
|
#Sun Apr 17 13:28:55 CDT 2022
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
ext {
|
ext {
|
||||||
minSdkVersion = 24
|
minSdkVersion = 24
|
||||||
compileSdkVersion = 31
|
compileSdkVersion = 33
|
||||||
targetSdkVersion = 31
|
targetSdkVersion = 32
|
||||||
androidxActivityVersion = '1.2.0'
|
androidxActivityVersion = '1.4.0'
|
||||||
androidxAppCompatVersion = '1.4.1'
|
androidxAppCompatVersion = '1.4.2'
|
||||||
androidxCoordinatorLayoutVersion = '1.2.0'
|
androidxCoordinatorLayoutVersion = '1.2.0'
|
||||||
androidxCoreVersion = '1.6.0'
|
androidxCoreVersion = '1.8.0'
|
||||||
androidPlayCore = '1.9.0'
|
androidPlayCore = '1.9.0'
|
||||||
androidxFragmentVersion = '1.3.0'
|
androidxFragmentVersion = '1.4.1'
|
||||||
junitVersion = '4.13.1'
|
junitVersion = '4.13.2'
|
||||||
androidxJunitVersion = '1.1.2'
|
androidxJunitVersion = '1.1.3'
|
||||||
androidxEspressoCoreVersion = '3.3.0'
|
androidxEspressoCoreVersion = '3.4.0'
|
||||||
cordovaAndroidVersion = '10.1.1'
|
cordovaAndroidVersion = '10.1.1'
|
||||||
androidx_car_version = '1.0.0-alpha7'
|
androidx_car_version = '1.0.0-alpha7'
|
||||||
androidx_core_ktx_version = '1.7.0'
|
androidx_core_ktx_version = '1.7.0'
|
||||||
|
@ -27,11 +27,13 @@ ext {
|
||||||
gradle_version = '3.1.4'
|
gradle_version = '3.1.4'
|
||||||
gson_version = '2.8.5'
|
gson_version = '2.8.5'
|
||||||
junit_version = '4.13'
|
junit_version = '4.13'
|
||||||
kotlin_version = '1.5.30'
|
kotlin_version = '1.7.20'
|
||||||
kotlin_coroutines_version = '1.1.0'
|
kotlin_coroutines_version = '1.1.0'
|
||||||
multidex_version = '1.0.3'
|
multidex_version = '1.0.3'
|
||||||
play_services_auth_version = '18.1.0'
|
play_services_auth_version = '18.1.0'
|
||||||
recycler_view_version = '1.1.0'
|
recycler_view_version = '1.1.0'
|
||||||
robolectric_version = '4.2'
|
robolectric_version = '4.2'
|
||||||
test_runner_version = '1.1.0'
|
test_runner_version = '1.1.0'
|
||||||
|
coreSplashScreenVersion = '1.0.0-rc01'
|
||||||
|
androidxWebkitVersion = '1.4.0'
|
||||||
}
|
}
|
||||||
|
|
|
@ -651,7 +651,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = YES;
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
ONLY_ACTIVE_ARCH = YES;
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
|
@ -705,7 +705,7 @@
|
||||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = NO;
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
SDKROOT = iphoneos;
|
SDKROOT = iphoneos;
|
||||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||||
|
@ -723,7 +723,7 @@
|
||||||
CURRENT_PROJECT_VERSION = 17;
|
CURRENT_PROJECT_VERSION = 17;
|
||||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 0.9.60;
|
MARKETING_VERSION = 0.9.60;
|
||||||
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
OTHER_SWIFT_FLAGS = "$(inherited) \"-D\" \"COCOAPODS\" \"-DDEBUG\"";
|
||||||
|
@ -747,7 +747,7 @@
|
||||||
CURRENT_PROJECT_VERSION = 17;
|
CURRENT_PROJECT_VERSION = 17;
|
||||||
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
DEVELOPMENT_TEAM = 7UFJ7D8V6A;
|
||||||
INFOPLIST_FILE = App/Info.plist;
|
INFOPLIST_FILE = App/Info.plist;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||||
MARKETING_VERSION = 0.9.60;
|
MARKETING_VERSION = 0.9.60;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app;
|
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.app;
|
||||||
|
@ -768,7 +768,7 @@
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
|
@ -790,7 +790,7 @@
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
GENERATE_INFOPLIST_FILE = YES;
|
GENERATE_INFOPLIST_FILE = YES;
|
||||||
IPHONEOS_DEPLOYMENT_TARGET = 16.0;
|
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
MTL_FAST_MATH = YES;
|
MTL_FAST_MATH = YES;
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.AudiobookshelfUnitTests;
|
PRODUCT_BUNDLE_IDENTIFIER = com.audiobookshelf.AudiobookshelfUnitTests;
|
||||||
|
|
|
@ -94,15 +94,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||||
backgroundCompletionHandler = completionHandler
|
backgroundCompletionHandler = completionHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
|
||||||
super.touchesBegan(touches, with: event)
|
|
||||||
|
|
||||||
let statusBarRect = self.window?.windowScene?.statusBarManager?.statusBarFrame
|
|
||||||
guard let touchPoint = event?.allTouches?.first?.location(in: self.window) else { return }
|
|
||||||
|
|
||||||
if statusBarRect?.contains(touchPoint) ?? false {
|
|
||||||
NotificationCenter.default.post(name: .capacitorStatusBarTapped, object: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,11 +22,6 @@
|
||||||
<string>$(CURRENT_PROJECT_VERSION)</string>
|
<string>$(CURRENT_PROJECT_VERSION)</string>
|
||||||
<key>LSRequiresIPhoneOS</key>
|
<key>LSRequiresIPhoneOS</key>
|
||||||
<true/>
|
<true/>
|
||||||
<key>NSAppTransportSecurity</key>
|
|
||||||
<dict>
|
|
||||||
<key>NSAllowsArbitraryLoads</key>
|
|
||||||
<true/>
|
|
||||||
</dict>
|
|
||||||
<key>NSUserActivityTypes</key>
|
<key>NSUserActivityTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<string>INPlayMediaIntent</string>
|
<string>INPlayMediaIntent</string>
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
platform :ios, '12.0'
|
require_relative '../../node_modules/@capacitor/ios/scripts/pods_helpers'
|
||||||
|
|
||||||
|
platform :ios, '13.0'
|
||||||
use_frameworks!
|
use_frameworks!
|
||||||
|
|
||||||
# workaround to avoid Xcode caching of Pods that requires
|
# workaround to avoid Xcode caching of Pods that requires
|
||||||
|
@ -9,12 +11,12 @@ install! 'cocoapods', :disable_input_output_paths => true
|
||||||
def capacitor_pods
|
def capacitor_pods
|
||||||
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
pod 'Capacitor', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
pod 'CapacitorCordova', :path => '../../node_modules/@capacitor/ios'
|
||||||
pod 'CapacitorApp', :path => '..\..\node_modules\@capacitor\app'
|
pod 'CapacitorApp', :path => '../../node_modules/@capacitor/app'
|
||||||
pod 'CapacitorDialog', :path => '..\..\node_modules\@capacitor\dialog'
|
pod 'CapacitorDialog', :path => '../../node_modules/@capacitor/dialog'
|
||||||
pod 'CapacitorHaptics', :path => '..\..\node_modules\@capacitor\haptics'
|
pod 'CapacitorHaptics', :path => '../../node_modules/@capacitor/haptics'
|
||||||
pod 'CapacitorNetwork', :path => '..\..\node_modules\@capacitor\network'
|
pod 'CapacitorNetwork', :path => '../../node_modules/@capacitor/network'
|
||||||
pod 'CapacitorStatusBar', :path => '..\..\node_modules\@capacitor\status-bar'
|
pod 'CapacitorPreferences', :path => '../../node_modules/@capacitor/preferences'
|
||||||
pod 'CapacitorStorage', :path => '..\..\node_modules\@capacitor\storage'
|
pod 'CapacitorStatusBar', :path => '../../node_modules/@capacitor/status-bar'
|
||||||
pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'
|
pod 'CordovaPlugins', :path => '../capacitor-cordova-ios-plugins'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -25,3 +27,8 @@ target 'Audiobookshelf' do
|
||||||
pod 'RealmSwift', '~>10'
|
pod 'RealmSwift', '~>10'
|
||||||
pod 'Alamofire', '~> 5.5'
|
pod 'Alamofire', '~> 5.5'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
assertDeploymentTarget(installer)
|
||||||
|
end
|
||||||
|
|
719
package-lock.json
generated
719
package-lock.json
generated
File diff suppressed because it is too large
Load diff
20
package.json
20
package.json
|
@ -12,16 +12,15 @@
|
||||||
"ionic:serve": "npm run start"
|
"ionic:serve": "npm run start"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^3.4.3",
|
"@capacitor/android": "^4.0.0",
|
||||||
"@capacitor/app": "^1.1.1",
|
"@capacitor/app": "^4.0.0",
|
||||||
"@capacitor/cli": "^3.4.3",
|
"@capacitor/core": "^4.0.0",
|
||||||
"@capacitor/core": "^3.4.3",
|
"@capacitor/dialog": "^4.0.0",
|
||||||
"@capacitor/dialog": "^1.0.7",
|
"@capacitor/haptics": "^4.0.0",
|
||||||
"@capacitor/haptics": "^1.1.4",
|
"@capacitor/ios": "^4.0.0",
|
||||||
"@capacitor/ios": "^3.2.2",
|
"@capacitor/network": "^4.0.0",
|
||||||
"@capacitor/network": "^1.0.7",
|
"@capacitor/preferences": "^4.0.2",
|
||||||
"@capacitor/status-bar": "^1.0.8",
|
"@capacitor/status-bar": "^4.0.0",
|
||||||
"@capacitor/storage": "^1.2.5",
|
|
||||||
"@nuxtjs/axios": "^5.13.6",
|
"@nuxtjs/axios": "^5.13.6",
|
||||||
"cordova-plugin-screen-orientation": "^3.0.2",
|
"cordova-plugin-screen-orientation": "^3.0.2",
|
||||||
"core-js": "^3.15.1",
|
"core-js": "^3.15.1",
|
||||||
|
@ -38,6 +37,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "7.13.15",
|
"@babel/core": "7.13.15",
|
||||||
"@babel/preset-env": "7.13.15",
|
"@babel/preset-env": "7.13.15",
|
||||||
|
"@capacitor/cli": "^4.0.0",
|
||||||
"@nuxtjs/tailwindcss": "^4.2.0",
|
"@nuxtjs/tailwindcss": "^4.2.0",
|
||||||
"postcss": "^8.3.5"
|
"postcss": "^8.3.5"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { Storage } from '@capacitor/storage'
|
import { Preferences } from '@capacitor/preferences'
|
||||||
|
|
||||||
class LocalStorage {
|
class LocalStorage {
|
||||||
constructor(vuexStore) {
|
constructor(vuexStore) {
|
||||||
|
@ -7,7 +7,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async setUserSettings(settings) {
|
async setUserSettings(settings) {
|
||||||
try {
|
try {
|
||||||
await Storage.set({ key: 'userSettings', value: JSON.stringify(settings) })
|
await Preferences.set({ key: 'userSettings', value: JSON.stringify(settings) })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to update user settings', error)
|
console.error('[LocalStorage] Failed to update user settings', error)
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async getUserSettings() {
|
async getUserSettings() {
|
||||||
try {
|
try {
|
||||||
const settingsObj = await Storage.get({ key: 'userSettings' }) || {}
|
const settingsObj = await Preferences.get({ key: 'userSettings' }) || {}
|
||||||
return settingsObj.value ? JSON.parse(settingsObj.value) : null
|
return settingsObj.value ? JSON.parse(settingsObj.value) : null
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to get user settings', error)
|
console.error('[LocalStorage] Failed to get user settings', error)
|
||||||
|
@ -25,7 +25,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async setServerSettings(settings) {
|
async setServerSettings(settings) {
|
||||||
try {
|
try {
|
||||||
await Storage.set({ key: 'serverSettings', value: JSON.stringify(settings) })
|
await Preferences.set({ key: 'serverSettings', value: JSON.stringify(settings) })
|
||||||
console.log('Saved server settings', JSON.stringify(settings))
|
console.log('Saved server settings', JSON.stringify(settings))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to update server settings', error)
|
console.error('[LocalStorage] Failed to update server settings', error)
|
||||||
|
@ -34,7 +34,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async getServerSettings() {
|
async getServerSettings() {
|
||||||
try {
|
try {
|
||||||
var settingsObj = await Storage.get({ key: 'serverSettings' }) || {}
|
var settingsObj = await Preferences.get({ key: 'serverSettings' }) || {}
|
||||||
return settingsObj.value ? JSON.parse(settingsObj.value) : null
|
return settingsObj.value ? JSON.parse(settingsObj.value) : null
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to get server settings', error)
|
console.error('[LocalStorage] Failed to get server settings', error)
|
||||||
|
@ -44,7 +44,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async setUseChapterTrack(useChapterTrack) {
|
async setUseChapterTrack(useChapterTrack) {
|
||||||
try {
|
try {
|
||||||
await Storage.set({ key: 'useChapterTrack', value: useChapterTrack ? '1' : '0' })
|
await Preferences.set({ key: 'useChapterTrack', value: useChapterTrack ? '1' : '0' })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to set use chapter track', error)
|
console.error('[LocalStorage] Failed to set use chapter track', error)
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async getUseChapterTrack() {
|
async getUseChapterTrack() {
|
||||||
try {
|
try {
|
||||||
var obj = await Storage.get({ key: 'useChapterTrack' }) || {}
|
var obj = await Preferences.get({ key: 'useChapterTrack' }) || {}
|
||||||
return obj.value === '1'
|
return obj.value === '1'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to get use chapter track', error)
|
console.error('[LocalStorage] Failed to get use chapter track', error)
|
||||||
|
@ -62,7 +62,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async setUseTotalTrack(useTotalTrack) {
|
async setUseTotalTrack(useTotalTrack) {
|
||||||
try {
|
try {
|
||||||
await Storage.set({ key: 'useTotalTrack', value: useTotalTrack ? '1' : '0' })
|
await Preferences.set({ key: 'useTotalTrack', value: useTotalTrack ? '1' : '0' })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to set use total track', error)
|
console.error('[LocalStorage] Failed to set use total track', error)
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async getUseTotalTrack() {
|
async getUseTotalTrack() {
|
||||||
try {
|
try {
|
||||||
var obj = await Storage.get({ key: 'useTotalTrack' }) || {}
|
var obj = await Preferences.get({ key: 'useTotalTrack' }) || {}
|
||||||
return obj.value === '1'
|
return obj.value === '1'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to get use total track', error)
|
console.error('[LocalStorage] Failed to get use total track', error)
|
||||||
|
@ -80,7 +80,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async setPlayerLock(lock) {
|
async setPlayerLock(lock) {
|
||||||
try {
|
try {
|
||||||
await Storage.set({ key: 'playerLock', value: lock ? '1' : '0' })
|
await Preferences.set({ key: 'playerLock', value: lock ? '1' : '0' })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to set player lock', error)
|
console.error('[LocalStorage] Failed to set player lock', error)
|
||||||
}
|
}
|
||||||
|
@ -88,7 +88,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async getPlayerLock() {
|
async getPlayerLock() {
|
||||||
try {
|
try {
|
||||||
var obj = await Storage.get({ key: 'playerLock' }) || {}
|
var obj = await Preferences.get({ key: 'playerLock' }) || {}
|
||||||
return obj.value === '1'
|
return obj.value === '1'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to get player lock', error)
|
console.error('[LocalStorage] Failed to get player lock', error)
|
||||||
|
@ -98,7 +98,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async setBookshelfListView(useIt) {
|
async setBookshelfListView(useIt) {
|
||||||
try {
|
try {
|
||||||
await Storage.set({ key: 'bookshelfListView', value: useIt ? '1' : '0' })
|
await Preferences.set({ key: 'bookshelfListView', value: useIt ? '1' : '0' })
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to set bookshelf list view', error)
|
console.error('[LocalStorage] Failed to set bookshelf list view', error)
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async getBookshelfListView() {
|
async getBookshelfListView() {
|
||||||
try {
|
try {
|
||||||
var obj = await Storage.get({ key: 'bookshelfListView' }) || {}
|
var obj = await Preferences.get({ key: 'bookshelfListView' }) || {}
|
||||||
return obj.value === '1'
|
return obj.value === '1'
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to get bookshelf list view', error)
|
console.error('[LocalStorage] Failed to get bookshelf list view', error)
|
||||||
|
@ -116,7 +116,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async setLastLibraryId(libraryId) {
|
async setLastLibraryId(libraryId) {
|
||||||
try {
|
try {
|
||||||
await Storage.set({ key: 'lastLibraryId', value: libraryId })
|
await Preferences.set({ key: 'lastLibraryId', value: libraryId })
|
||||||
console.log('[LocalStorage] Set Last Library Id', libraryId)
|
console.log('[LocalStorage] Set Last Library Id', libraryId)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to set last library id', error)
|
console.error('[LocalStorage] Failed to set last library id', error)
|
||||||
|
@ -125,7 +125,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async removeLastLibraryId() {
|
async removeLastLibraryId() {
|
||||||
try {
|
try {
|
||||||
await Storage.remove({ key: 'lastLibraryId' })
|
await Preferences.remove({ key: 'lastLibraryId' })
|
||||||
console.log('[LocalStorage] Remove Last Library Id')
|
console.log('[LocalStorage] Remove Last Library Id')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to remove last library id', error)
|
console.error('[LocalStorage] Failed to remove last library id', error)
|
||||||
|
@ -134,7 +134,7 @@ class LocalStorage {
|
||||||
|
|
||||||
async getLastLibraryId() {
|
async getLastLibraryId() {
|
||||||
try {
|
try {
|
||||||
var obj = await Storage.get({ key: 'lastLibraryId' }) || {}
|
var obj = await Preferences.get({ key: 'lastLibraryId' }) || {}
|
||||||
return obj.value || null
|
return obj.value || null
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[LocalStorage] Failed to get last library id', error)
|
console.error('[LocalStorage] Failed to get last library id', error)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue