diff --git a/android/app/src/main/java/com/audiobookshelf/app/MainActivity.kt b/android/app/src/main/java/com/audiobookshelf/app/MainActivity.kt index f0bf5f18..38f13bc4 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/MainActivity.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/MainActivity.kt @@ -12,7 +12,7 @@ import android.util.Log import androidx.core.app.ActivityCompat import com.anggrayudi.storage.SimpleStorage import com.anggrayudi.storage.SimpleStorageHelper -import com.audiobookshelf.app.data.AbsDatabase +import com.audiobookshelf.app.plugins.AbsDatabase import com.audiobookshelf.app.managers.DbManager import com.audiobookshelf.app.player.PlayerNotificationService import com.audiobookshelf.app.plugins.AbsAudioPlayer diff --git a/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt b/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt index e559ea58..3722a0ec 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/data/PlaybackSession.kt @@ -15,13 +15,7 @@ import com.google.android.exoplayer2.MediaMetadata import com.google.android.gms.cast.MediaInfo import com.google.android.gms.cast.MediaQueueItem import com.google.android.gms.common.images.WebImage - - -// TODO: enum or something in kotlin? -val PLAYMETHOD_DIRECTPLAY = 0 -val PLAYMETHOD_DIRECTSTREAM = 1 -val PLAYMETHOD_TRANSCODE = 2 -val PLAYMETHOD_LOCAL = 3 +import com.audiobookshelf.app.player.* @JsonIgnoreProperties(ignoreUnknown = true) class PlaybackSession( diff --git a/android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt b/android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt index 58a5293c..94f3756a 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/device/DeviceManager.kt @@ -21,7 +21,6 @@ object DeviceManager { val serverUserId get() = serverConnectionConfig?.userId ?: "" val token get() = serverConnectionConfig?.token ?: "" val isConnectedToServer get() = serverConnectionConfig != null - val hasLastServerConnectionConfig get() = deviceData.getLastServerConnectionConfig() != null var widgetUpdater:WidgetEventEmitter? = null diff --git a/android/app/src/main/java/com/audiobookshelf/app/device/FolderScanner.kt b/android/app/src/main/java/com/audiobookshelf/app/device/FolderScanner.kt index 2cca1a9d..7cdb49a0 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/device/FolderScanner.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/device/FolderScanner.kt @@ -72,12 +72,12 @@ class FolderScanner(var ctx: Context) { Log.d(tag, "Iterating over Folder Found ${itemFolder.name} | ${itemFolder.getSimplePath(ctx)} | URI: ${itemFolder.uri}") val existingItem = existingLocalLibraryItems.find { emi -> emi.id == getLocalLibraryItemId(itemFolder.id) } - val result = scanLibraryItemFolder(itemFolder, localFolder, existingItem, forceAudioProbe) - - if (result == ItemScanResult.REMOVED) mediaItemsRemoved++ - else if (result == ItemScanResult.UPDATED) mediaItemsUpdated++ - else if (result == ItemScanResult.ADDED) mediaItemsAdded++ - else mediaItemsUpToDate++ + when (scanLibraryItemFolder(itemFolder, localFolder, existingItem, forceAudioProbe)) { + ItemScanResult.REMOVED -> mediaItemsRemoved++ + ItemScanResult.UPDATED -> mediaItemsUpdated++ + ItemScanResult.ADDED -> mediaItemsAdded++ + else -> mediaItemsUpToDate++ + } } Log.d(tag, "Folder $${localFolder.name} scan Results: $mediaItemsAdded Added | $mediaItemsUpdated Updated | $mediaItemsRemoved Removed | $mediaItemsUpToDate Up-to-date") @@ -130,7 +130,7 @@ class FolderScanner(var ctx: Context) { Log.d(tag, "File attributes Id:${localFileId}|ContentUrl:${localFile.contentUrl}|isDownloadsDocument:${file.isDownloadsDocument}") if (isAudio) { - var audioTrackToAdd:AudioTrack? = null + val audioTrackToAdd:AudioTrack? val existingAudioTrack = existingAudioTracks.find { eat -> eat.localFileId == localFileId } if (existingAudioTrack != null) { // Update existing audio track @@ -258,7 +258,7 @@ class FolderScanner(var ctx: Context) { Log.d(tag, "scanDownloadItem ${filesFound.size} files found in ${downloadItem.itemFolderPath}") var localEpisodeId:String? = null - var localLibraryItem:LocalLibraryItem? = null + var localLibraryItem:LocalLibraryItem? if (downloadItem.mediaType == "book") { localLibraryItem = LocalLibraryItem(localLibraryItemId, downloadItem.localFolder.id, itemFolderBasePath, itemFolderAbsolutePath, itemFolderUrl, false, downloadItem.mediaType, downloadItem.media.getLocalCopy(), mutableListOf(), null, null, true, downloadItem.serverConnectionConfigId, downloadItem.serverAddress, downloadItem.serverUserId, downloadItem.libraryItemId) } else { @@ -400,7 +400,7 @@ class FolderScanner(var ctx: Context) { localLibraryItem.localFiles.removeIf { it.id == localFileId } if (existingAudioTracks.find { it.localFileId == localFileId } != null) { - Log.d(tag, "scanLocalLibraryItem audio track file ${localFileId} was removed from ${localLibraryItem.absolutePath}") + Log.d(tag, "scanLocalLibraryItem audio track file $localFileId was removed from ${localLibraryItem.absolutePath}") localLibraryItem.media.removeAudioTrack(localFileId) } wasUpdated = true @@ -463,7 +463,7 @@ class FolderScanner(var ctx: Context) { return LocalLibraryItemScanResult(wasUpdated, localLibraryItem) } - fun probeAudioFile(absolutePath:String):AudioProbeResult? { + private fun probeAudioFile(absolutePath:String):AudioProbeResult? { val session = FFprobeKit.execute("-i \"${absolutePath}\" -print_format json -show_format -show_streams -select_streams a -show_chapters -loglevel quiet") var probeObject:JSObject? = null @@ -474,13 +474,13 @@ class FolderScanner(var ctx: Context) { } Log.d(tag, "FFprobe output $probeObject") - if (probeObject == null || !probeObject.has("streams")) { // Check if output is empty + return if (probeObject == null || !probeObject.has("streams")) { // Check if output is empty Log.d(tag, "probeAudioFile Probe audio file $absolutePath failed or invalid") - return null + null } else { val audioProbeResult = jacksonMapper.readValue(session.output) Log.d(tag, "Probe Result DATA ${audioProbeResult.duration} | ${audioProbeResult.size} | ${audioProbeResult.title} | ${audioProbeResult.artist}") - return audioProbeResult + audioProbeResult } } } diff --git a/android/app/src/main/java/com/audiobookshelf/app/managers/DbManager.kt b/android/app/src/main/java/com/audiobookshelf/app/managers/DbManager.kt index e0447961..5dc7c234 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/managers/DbManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/managers/DbManager.kt @@ -11,7 +11,7 @@ class DbManager { val tag = "DbManager" companion object { - var isDbInitialized = false + private var isDbInitialized = false fun initialize(ctx: Context) { if (isDbInitialized) return @@ -155,7 +155,7 @@ class DbManager { val localLibraryItems = getLocalLibraryItems() localLibraryItems.forEach { lli -> - var hasUpates = false + var hasUpdates = false // Check local files lli.localFiles = lli.localFiles.filter { localFile -> @@ -163,7 +163,7 @@ class DbManager { val file = File(localFile.absolutePath) if (!file.exists()) { Log.d(tag, "cleanLocalLibraryItems: Local file ${localFile.absolutePath} was removed from library item ${lli.media.metadata.title}") - hasUpates = true + hasUpdates = true } file.exists() } as MutableList @@ -174,7 +174,7 @@ class DbManager { podcast.episodes = podcast.episodes?.filter { ep -> if (lli.localFiles.find { lf -> lf.id == ep.audioTrack?.localFileId } == null) { Log.d(tag, "cleanLocalLibraryItems: Podcast episode ${ep.title} was removed from library item ${lli.media.metadata.title}") - hasUpates = true + hasUpdates = true } ep.audioTrack != null && lli.localFiles.find { lf -> lf.id == ep.audioTrack?.localFileId } != null } as MutableList @@ -183,7 +183,7 @@ class DbManager { book.tracks = book.tracks?.filter { track -> if (lli.localFiles.find { lf -> lf.id == track.localFileId } == null) { Log.d(tag, "cleanLocalLibraryItems: Audio track ${track.title} was removed from library item ${lli.media.metadata.title}") - hasUpates = true + hasUpdates = true } lli.localFiles.find { lf -> lf.id == track.localFileId } != null } as MutableList @@ -197,11 +197,11 @@ class DbManager { Log.d(tag, "cleanLocalLibraryItems: Cover $it was removed from library item ${lli.media.metadata.title}") lli.coverAbsolutePath = null lli.coverContentUrl = null - hasUpates = true + hasUpdates = true } } - if (hasUpates) { + if (hasUpdates) { Log.d(tag, "cleanLocalLibraryItems: Saving local library item ${lli.id}") Paper.book("localLibraryItems").write(lli.id, lli) } diff --git a/android/app/src/main/java/com/audiobookshelf/app/managers/DownloadItemManager.kt b/android/app/src/main/java/com/audiobookshelf/app/managers/DownloadItemManager.kt index 1272a1dd..3e89211e 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/managers/DownloadItemManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/managers/DownloadItemManager.kt @@ -23,7 +23,7 @@ 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) { +class DownloadItemManager(var downloadManager:DownloadManager, private var folderScanner: FolderScanner, var mainActivity: MainActivity, private var clientEventEmitter:DownloadEventEmitter) { val tag = "DownloadItemManager" private val maxSimultaneousDownloads = 1 private var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature()) @@ -125,24 +125,30 @@ class DownloadItemManager(var downloadManager:DownloadManager, var folderScanner val bytesDownloadedSoFar = if (bytesDownloadedColumnIndex >= 0) it.getLong(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 - downloadItemPart.progress = 1 - downloadItemPart.bytesDownloaded = bytesDownloadedSoFar - 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 - downloadItemPart.bytesDownloaded = bytesDownloadedSoFar - return DownloadCheckStatus.InProgress + return when (downloadStatus) { + DownloadManager.STATUS_SUCCESSFUL -> { + Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Successful") + downloadItemPart.completed = true + downloadItemPart.progress = 1 + downloadItemPart.bytesDownloaded = bytesDownloadedSoFar + + DownloadCheckStatus.Successful + } + DownloadManager.STATUS_FAILED -> { + Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Failed") + downloadItemPart.completed = true + downloadItemPart.failed = true + + DownloadCheckStatus.Failed + } + else -> { + val percentProgress = if (totalBytes > 0) ((bytesDownloadedSoFar * 100L) / totalBytes) else 0 + Log.d(tag, "checkDownloads Download ${downloadItemPart.filename} Progress = $percentProgress%") + downloadItemPart.progress = percentProgress + downloadItemPart.bytesDownloaded = bytesDownloadedSoFar + + DownloadCheckStatus.InProgress + } } } else { Log.d(tag, "Download ${downloadItemPart.filename} not found in dlmanager") diff --git a/android/app/src/main/java/com/audiobookshelf/app/managers/SleepTimerManager.kt b/android/app/src/main/java/com/audiobookshelf/app/managers/SleepTimerManager.kt index b4a97f97..fb5da1b4 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/managers/SleepTimerManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/managers/SleepTimerManager.kt @@ -12,7 +12,7 @@ import kotlin.math.roundToInt const val SLEEP_EXTENSION_TIME = 900000L // 15m -class SleepTimerManager constructor(val playerNotificationService: PlayerNotificationService) { +class SleepTimerManager constructor(private val playerNotificationService: PlayerNotificationService) { private val tag = "SleepTimerManager" private var sleepTimerTask:TimerTask? = null @@ -90,7 +90,7 @@ class SleepTimerManager constructor(val playerNotificationService: PlayerNotific sleepTimerRunning = true sleepTimerTask = Timer("SleepTimer", false).schedule(0L, 1000L) { - Handler(Looper.getMainLooper()).post() { + Handler(Looper.getMainLooper()).post { if (getIsPlaying()) { sleepTimerElapsed += 1000L diff --git a/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt b/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt index ac2a0db6..f89566ca 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/media/MediaManager.kt @@ -15,24 +15,24 @@ import org.json.JSONException import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine -class MediaManager(var apiHandler: ApiHandler, var ctx: Context) { +class MediaManager(private var apiHandler: ApiHandler, var ctx: Context) { val tag = "MediaManager" - var serverLibraryItems = mutableListOf() // Store all items here - var selectedLibraryItems = mutableListOf() - var selectedLibraryId = "" + private var serverLibraryItems = mutableListOf() // Store all items here + private var selectedLibraryItems = mutableListOf() + private var selectedLibraryId = "" - var selectedPodcast:Podcast? = null - var selectedLibraryItemId:String? = null - var podcastEpisodeLibraryItemMap = mutableMapOf() - var serverLibraryCategories = listOf() + private var selectedPodcast:Podcast? = null + private var selectedLibraryItemId:String? = null + private var podcastEpisodeLibraryItemMap = mutableMapOf() + private var serverLibraryCategories = listOf() + private var serverConfigIdUsed:String? = null + private var serverConfigLastPing:Long = 0L + var serverUserMediaProgress:MutableList = mutableListOf() var serverItemsInProgress = listOf() var serverLibraries = listOf() - var serverConfigIdUsed:String? = null - var serverConfigLastPing:Long = 0L - var serverUserMediaProgress:MutableList = mutableListOf() - var userSettingsPlaybackRate:Float? = null + private var userSettingsPlaybackRate:Float? = null fun getIsLibrary(id:String) : Boolean { return serverLibraries.find { it.id == id } != null @@ -76,7 +76,7 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) { } } - fun loadItemsInProgressForAllLibraries(cb: (List) -> Unit) { + private fun loadItemsInProgressForAllLibraries(cb: (List) -> Unit) { if (serverItemsInProgress.isNotEmpty()) { cb(serverItemsInProgress) } else { @@ -109,7 +109,7 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) { } } - fun loadLibraryItem(libraryItemId:String, cb: (LibraryItemWrapper?) -> Unit) { + private fun loadLibraryItem(libraryItemId:String, cb: (LibraryItemWrapper?) -> Unit) { if (libraryItemId.startsWith("local")) { cb(DeviceManager.dbManager.getLocalLibraryItem(libraryItemId)) } else { @@ -176,7 +176,7 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) { } } - fun loadLibraries(cb: (List) -> Unit) { + private fun loadLibraries(cb: (List) -> Unit) { if (serverLibraries.isNotEmpty()) { cb(serverLibraries) } else { @@ -187,9 +187,9 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) { } } - suspend fun checkServerConnection(config:ServerConnectionConfig) : Boolean { + private suspend fun checkServerConnection(config:ServerConnectionConfig) : Boolean { var successfulPing = false - suspendCoroutine { cont -> + suspendCoroutine { cont -> apiHandler.pingServer(config) { Log.d(tag, "checkServerConnection: Checked server conn for ${config.address} result = $it") successfulPing = it @@ -199,9 +199,9 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) { return successfulPing } - suspend fun authorize(config:ServerConnectionConfig) : MutableList { + private suspend fun authorize(config:ServerConnectionConfig) : MutableList { var mediaProgress:MutableList = mutableListOf() - suspendCoroutine> { cont -> + suspendCoroutine { cont -> apiHandler.authorize(config) { Log.d(tag, "authorize: Authorized server config ${config.address} result = $it") if (!it.isNullOrEmpty()) { @@ -213,7 +213,7 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) { return mediaProgress } - fun checkSetValidServerConnectionConfig(cb: (Boolean) -> Unit) = runBlocking { + private fun checkSetValidServerConnectionConfig(cb: (Boolean) -> Unit) = runBlocking { Log.d(tag, "checkSetValidServerConnectionConfig | $serverConfigIdUsed") coroutineScope { @@ -340,18 +340,18 @@ class MediaManager(var apiHandler: ApiHandler, var ctx: Context) { } fun getPodcastWithEpisodeByEpisodeId(id:String) : LibraryItemWithEpisode? { - if (id.startsWith("local")) { - return DeviceManager.dbManager.getLocalLibraryItemWithEpisode(id) + return if (id.startsWith("local")) { + DeviceManager.dbManager.getLocalLibraryItemWithEpisode(id) } else { - return podcastEpisodeLibraryItemMap[id] + podcastEpisodeLibraryItemMap[id] } } fun getById(id:String) : LibraryItemWrapper? { - if (id.startsWith("local")) { - return DeviceManager.dbManager.getLocalLibraryItem(id) + return if (id.startsWith("local")) { + DeviceManager.dbManager.getLocalLibraryItem(id) } else { - return serverLibraryItems.find { it.id == id } + serverLibraryItems.find { it.id == id } } } diff --git a/android/app/src/main/java/com/audiobookshelf/app/models/DownloadItem.kt b/android/app/src/main/java/com/audiobookshelf/app/models/DownloadItem.kt index fe9a7540..ca93b6e4 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/models/DownloadItem.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/models/DownloadItem.kt @@ -24,13 +24,6 @@ data class DownloadItem( @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 { val itemParts = mutableListOf() diff --git a/android/app/src/main/java/com/audiobookshelf/app/models/DownloadItemPart.kt b/android/app/src/main/java/com/audiobookshelf/app/models/DownloadItemPart.kt index 2d34a2d7..9e0dec2f 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/models/DownloadItemPart.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/models/DownloadItemPart.kt @@ -2,7 +2,6 @@ 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 diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/AbMediaDescriptionAdapter.kt b/android/app/src/main/java/com/audiobookshelf/app/player/AbMediaDescriptionAdapter.kt index e981cc51..3a2c2064 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/AbMediaDescriptionAdapter.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/AbMediaDescriptionAdapter.kt @@ -10,19 +10,17 @@ import android.support.v4.media.session.MediaControllerCompat import android.util.Log import com.audiobookshelf.app.R import com.bumptech.glide.Glide -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.request.RequestOptions import com.google.android.exoplayer2.Player import com.google.android.exoplayer2.ui.PlayerNotificationManager import kotlinx.coroutines.* const val NOTIFICATION_LARGE_ICON_SIZE = 144 // px -class AbMediaDescriptionAdapter constructor(private val controller: MediaControllerCompat, val playerNotificationService: PlayerNotificationService) : PlayerNotificationManager.MediaDescriptionAdapter { +class AbMediaDescriptionAdapter constructor(private val controller: MediaControllerCompat, private val playerNotificationService: PlayerNotificationService) : PlayerNotificationManager.MediaDescriptionAdapter { private val tag = "MediaDescriptionAdapter" - var currentIconUri: Uri? = null - var currentBitmap: Bitmap? = null + private var currentIconUri: Uri? = null + private var currentBitmap: Bitmap? = null private val serviceJob = SupervisorJob() private val serviceScope = CoroutineScope(Dispatchers.Main + serviceJob) @@ -48,6 +46,7 @@ class AbMediaDescriptionAdapter constructor(private val controller: MediaControl if (currentIconUri.toString().startsWith("content://")) { currentBitmap = if (Build.VERSION.SDK_INT < 28) { + @Suppress("DEPRECATION") MediaStore.Images.Media.getBitmap(playerNotificationService.contentResolver, currentIconUri) } else { val source: ImageDecoder.Source = ImageDecoder.createSource(playerNotificationService.contentResolver, currentIconUri!!) diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/BrowseTree.kt b/android/app/src/main/java/com/audiobookshelf/app/player/BrowseTree.kt index 099996c3..d752b5d5 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/BrowseTree.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/BrowseTree.kt @@ -17,11 +17,10 @@ class BrowseTree( /** * get uri to drawable or any other resource type if u wish - * @param context - context * @param drawableId - drawable res id * @return - uri */ - fun getUriToDrawable(@AnyRes drawableId: Int): Uri { + private fun getUriToDrawable(@AnyRes drawableId: Int): Uri { return Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + context.resources.getResourcePackageName(drawableId) + '/' + context.resources.getResourceTypeName(drawableId) @@ -49,7 +48,7 @@ class BrowseTree( putString(MediaMetadataCompat.METADATA_KEY_ALBUM_ART_URI, getUriToDrawable(R.drawable.icon_library_folder).toString()) }.build() - if (!itemsInProgress.isEmpty()) { + if (itemsInProgress.isNotEmpty()) { rootList += continueListeningMetadata } diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt index 36f5a4b1..7fa00026 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionCallback.kt @@ -2,10 +2,7 @@ package com.audiobookshelf.app.player import android.annotation.SuppressLint import android.content.Intent -import android.os.Bundle -import android.os.Handler -import android.os.Looper -import android.os.Message +import android.os.* import android.support.v4.media.session.MediaSessionCompat import android.util.Log import android.view.KeyEvent @@ -18,7 +15,7 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi var tag = "MediaSessionCallback" private var mediaButtonClickCount: Int = 0 - var mediaButtonClickTimeout: Long = 1000 //ms + private var mediaButtonClickTimeout: Long = 1000 //ms override fun onPrepare() { Log.d(tag, "ON PREPARE MEDIA SESSION COMPAT") @@ -28,7 +25,7 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi Log.e(tag, "Failed to play library item") } else { val playbackRate = playerNotificationService.mediaManager.getSavedPlaybackRate() - Handler(Looper.getMainLooper()).post() { + Handler(Looper.getMainLooper()).post { playerNotificationService.preparePlayer(it,true, playbackRate) } } @@ -54,7 +51,7 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi Log.e(tag, "Failed to play library item") } else { val playbackRate = playerNotificationService.mediaManager.getSavedPlaybackRate() - Handler(Looper.getMainLooper()).post() { + Handler(Looper.getMainLooper()).post { playerNotificationService.preparePlayer(it, true, playbackRate) } } @@ -93,7 +90,7 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi override fun onPlayFromMediaId(mediaId: String?, extras: Bundle?) { Log.d(tag, "ON PLAY FROM MEDIA ID $mediaId") - var libraryItemWrapper: LibraryItemWrapper? = null + val libraryItemWrapper: LibraryItemWrapper? var podcastEpisode: PodcastEpisode? = null if (mediaId.isNullOrEmpty()) { @@ -116,7 +113,7 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi Log.e(tag, "Failed to play library item") } else { val playbackRate = playerNotificationService.mediaManager.getSavedPlaybackRate() - Handler(Looper.getMainLooper()).post() { + Handler(Looper.getMainLooper()).post { playerNotificationService.preparePlayer(it, true, playbackRate) } } @@ -128,11 +125,17 @@ class MediaSessionCallback(var playerNotificationService:PlayerNotificationServi return handleCallMediaButton(mediaButtonEvent) } - fun handleCallMediaButton(intent: Intent): Boolean { + private fun handleCallMediaButton(intent: Intent): Boolean { Log.w(tag, "handleCallMediaButton $intent | ${intent.action}") if(Intent.ACTION_MEDIA_BUTTON == intent.action) { - val keyEvent = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT) + val keyEvent = if (Build.VERSION.SDK_INT >= 33) { + intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT, KeyEvent::class.java) + } else { + @Suppress("DEPRECATION") + intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT) + } + Log.d(tag, "handleCallMediaButton keyEvent = $keyEvent | action ${keyEvent?.action}") if (keyEvent?.action == KeyEvent.ACTION_DOWN) { diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionPlaybackPreparer.kt b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionPlaybackPreparer.kt index 536f600a..c53a0830 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionPlaybackPreparer.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/MediaSessionPlaybackPreparer.kt @@ -35,7 +35,7 @@ class MediaSessionPlaybackPreparer(var playerNotificationService:PlayerNotificat Log.e(tag, "Failed to play library item") } else { val playbackRate = playerNotificationService.mediaManager.getSavedPlaybackRate() - Handler(Looper.getMainLooper()).post() { + Handler(Looper.getMainLooper()).post { playerNotificationService.preparePlayer(it, playWhenReady, playbackRate) } } @@ -46,7 +46,7 @@ class MediaSessionPlaybackPreparer(var playerNotificationService:PlayerNotificat override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) { Log.d(tag, "ON PREPARE FROM MEDIA ID $mediaId $playWhenReady") - var libraryItemWrapper: LibraryItemWrapper? = null + val libraryItemWrapper: LibraryItemWrapper? var podcastEpisode: PodcastEpisode? = null if (mediaId.startsWith("ep_") || mediaId.startsWith("local_ep_")) { // Playing podcast episode @@ -63,7 +63,7 @@ class MediaSessionPlaybackPreparer(var playerNotificationService:PlayerNotificat Log.e(tag, "Failed to play library item") } else { val playbackRate = playerNotificationService.mediaManager.getSavedPlaybackRate() - Handler(Looper.getMainLooper()).post() { + Handler(Looper.getMainLooper()).post { playerNotificationService.preparePlayer(it, playWhenReady, playbackRate) } } @@ -79,7 +79,7 @@ class MediaSessionPlaybackPreparer(var playerNotificationService:PlayerNotificat Log.e(tag, "Failed to play library item") } else { val playbackRate = playerNotificationService.mediaManager.getSavedPlaybackRate() - Handler(Looper.getMainLooper()).post() { + Handler(Looper.getMainLooper()).post { playerNotificationService.preparePlayer(it, playWhenReady, playbackRate) } } diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerConstants.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerConstants.kt index cbf9f76e..0938b912 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerConstants.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerConstants.kt @@ -4,3 +4,17 @@ const val CUSTOM_ACTION_JUMP_FORWARD = "com.audiobookshelf.customAction.jump_for const val CUSTOM_ACTION_JUMP_BACKWARD = "com.audiobookshelf.customAction.jump_backward"; const val CUSTOM_ACTION_SKIP_FORWARD = "com.audiobookshelf.customAction.skip_forward"; const val CUSTOM_ACTION_SKIP_BACKWARD = "com.audiobookshelf.customAction.skip_backward"; + +const val PLAYMETHOD_DIRECTPLAY = 0 +const val PLAYMETHOD_DIRECTSTREAM = 1 +const val PLAYMETHOD_TRANSCODE = 2 +const val PLAYMETHOD_LOCAL = 3 + +// +// MEDIA BROWSER STUFF (ANDROID AUTO) +// +const val ANDROID_AUTO_PKG_NAME = "com.google.android.projection.gearhead" +const val ANDROID_AUTO_SIMULATOR_PKG_NAME = "com.google.android.autosimulator" +const val ANDROID_WEARABLE_PKG_NAME = "com.google.android.wearable.app" +const val ANDROID_GSEARCH_PKG_NAME = "com.google.android.googlequicksearchbox" +const val ANDROID_AUTOMOTIVE_PKG_NAME = "com.google.android.carassistant" diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt index 3191e69d..6504b060 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerListener.kt @@ -125,7 +125,7 @@ class PlayerListener(var playerNotificationService:PlayerNotificationService) : Log.d(tag, "STATE_READY : " + playerNotificationService.currentPlayer.duration) if (lastPauseTime == 0L) { - lastPauseTime = -1; + lastPauseTime = -1 } playerNotificationService.sendClientMetadata(PlayerState.READY) } diff --git a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt index 0d6f435f..9b3672af 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/player/PlayerNotificationService.kt @@ -141,6 +141,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { return START_STICKY } + @Deprecated("Deprecated in Java") override fun onStart(intent: Intent?, startId: Int) { Log.d(tag, "onStart $startId") } @@ -229,7 +230,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { .apply { setSessionActivity(sessionActivityPendingIntent) isActive = true - setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS) } val mediaController = MediaControllerCompat(ctx, mediaSession.sessionToken) @@ -758,10 +758,10 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { var timeToSeek = time Log.d(tag, "seekPlayer mediaCount = ${currentPlayer.mediaItemCount} | $timeToSeek") if (timeToSeek < 0) { - Log.w(tag, "seekPlayer invalid time ${timeToSeek} - setting to 0") + Log.w(tag, "seekPlayer invalid time $timeToSeek - setting to 0") timeToSeek = 0L } else if (timeToSeek > getDuration()) { - Log.w(tag, "seekPlayer invalid time ${timeToSeek} - setting to MAX - 2000") + Log.w(tag, "seekPlayer invalid time $timeToSeek - setting to MAX - 2000") timeToSeek = getDuration() - 2000L } @@ -869,11 +869,6 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { // // MEDIA BROWSER STUFF (ANDROID AUTO) // - private val ANDROID_AUTO_PKG_NAME = "com.google.android.projection.gearhead" - private val ANDROID_AUTO_SIMULATOR_PKG_NAME = "com.google.android.autosimulator" - private val ANDROID_WEARABLE_PKG_NAME = "com.google.android.wearable.app" - private val ANDROID_GSEARCH_PKG_NAME = "com.google.android.googlequicksearchbox" - private val ANDROID_AUTOMOTIVE_PKG_NAME = "com.google.android.carassistant" private val VALID_MEDIA_BROWSERS = mutableListOf("com.audiobookshelf.app", ANDROID_AUTO_PKG_NAME, ANDROID_AUTO_SIMULATOR_PKG_NAME, ANDROID_WEARABLE_PKG_NAME, ANDROID_GSEARCH_PKG_NAME, ANDROID_AUTOMOTIVE_PKG_NAME) private val AUTO_MEDIA_ROOT = "/" @@ -1064,7 +1059,7 @@ class PlayerNotificationService : MediaBrowserServiceCompat() { // Unregister shake sensor after wake up expiration shakeSensorUnregisterTask?.cancel() shakeSensorUnregisterTask = Timer("ShakeUnregisterTimer", false).schedule(SLEEP_TIMER_WAKE_UP_EXPIRATION) { - Handler(Looper.getMainLooper()).post() { + Handler(Looper.getMainLooper()).post { Log.d(tag, "wake time expired: Unregistering shake sensor") mSensorManager!!.unregisterListener(mShakeDetector) isShakeSensorRegistered = false diff --git a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt index 3ae9d630..6b55ed58 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsAudioPlayer.kt @@ -8,7 +8,6 @@ import com.audiobookshelf.app.data.* import com.audiobookshelf.app.device.DeviceManager import com.audiobookshelf.app.media.MediaEventManager import com.audiobookshelf.app.player.CastManager -import com.audiobookshelf.app.player.MediaProgressSyncer import com.audiobookshelf.app.player.PlayerListener import com.audiobookshelf.app.player.PlayerNotificationService import com.audiobookshelf.app.server.ApiHandler @@ -24,7 +23,7 @@ import org.json.JSONObject @CapacitorPlugin(name = "AbsAudioPlayer") class AbsAudioPlayer : Plugin() { private val tag = "AbsAudioPlayer" - var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature()) + private var jacksonMapper = jacksonObjectMapper().enable(JsonReadFeature.ALLOW_UNESCAPED_CONTROL_CHARS.mappedFeature()) private lateinit var mainActivity: MainActivity private lateinit var apiHandler:ApiHandler diff --git a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt index f133d33f..42747f53 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDatabase.kt @@ -1,7 +1,8 @@ -package com.audiobookshelf.app.data +package com.audiobookshelf.app.plugins import android.util.Log import com.audiobookshelf.app.MainActivity +import com.audiobookshelf.app.data.* import com.audiobookshelf.app.device.DeviceManager import com.audiobookshelf.app.media.MediaEventManager import com.audiobookshelf.app.server.ApiHandler @@ -307,8 +308,8 @@ class AbsDatabase : Plugin() { return call.resolve(JSObject("{\"error\":\"Invalid library item not a podcast\"}")) } - var duration = 0.0 - var podcastEpisode:PodcastEpisode? = null + val duration: Double + var podcastEpisode: PodcastEpisode? = null if (!localEpisodeId.isNullOrEmpty()) { val podcast = localLibraryItem.media as Podcast podcastEpisode = podcast.episodes?.find { episode -> diff --git a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDownloader.kt b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDownloader.kt index 4110cedd..598a21f9 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDownloader.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsDownloader.kt @@ -148,7 +148,7 @@ class AbsDownloader : Plugin() { val fileSize = audioTrack.metadata?.size ?: 0 val serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioTrack.relPath)}" val destinationFilename = getFilenameFromRelPath(audioTrack.relPath) - Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioTrack.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}") + Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioTrack.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName $destinationFilename") val finalDestinationFile = File("$itemFolderPath/$destinationFilename") val destinationFile = File("$tempFolderPath/$destinationFilename") @@ -208,7 +208,7 @@ class AbsDownloader : Plugin() { var serverPath = "/s/item/${libraryItem.id}/${cleanRelPath(audioTrack?.relPath ?: "")}" var destinationFilename = getFilenameFromRelPath(audioTrack?.relPath ?: "") - Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioTrack?.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName ${destinationFilename}") + Log.d(tag, "Audio File Server Path $serverPath | AF RelPath ${audioTrack?.relPath} | LocalFolder Path ${localFolder.absolutePath} | DestName $destinationFilename") var destinationFile = File("$tempFolderPath/$destinationFilename") var finalDestinationFile = File("$itemFolderPath/$destinationFilename") diff --git a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsFileSystem.kt b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsFileSystem.kt index c8219738..9b22caae 100644 --- a/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsFileSystem.kt +++ b/android/app/src/main/java/com/audiobookshelf/app/plugins/AbsFileSystem.kt @@ -28,7 +28,7 @@ import kotlinx.coroutines.launch class AbsFileSystem : Plugin() { private val TAG = "AbsFileSystem" private val tag = "AbsFileSystem" - 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 @@ -138,7 +138,7 @@ class AbsFileSystem : Plugin() { @PluginMethod fun checkStoragePermission(call: PluginCall) { - var res: Boolean + val res: Boolean if (Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.P) { res = SimpleStorage.hasStoragePermission(context) Log.d(TAG, "checkStoragePermission: Check Storage Access $res") @@ -147,33 +147,33 @@ class AbsFileSystem : Plugin() { res = true } - var jsobj = JSObject() + val jsobj = JSObject() jsobj.put("value", res) call.resolve(jsobj) } @PluginMethod fun checkFolderPermissions(call: PluginCall) { - var folderUrl = call.data.getString("folderUrl", "").toString() - Log.d(TAG, "Check Folder Permissions for $folderUrl") + val folderUrl = call.data.getString("folderUrl", "").toString() + Log.d(TAG, "Check Folder Permissions for $folderUrl") - var hasAccess = SimpleStorage.hasStorageAccess(context,folderUrl,true) + val hasAccess = SimpleStorage.hasStorageAccess(context,folderUrl,true) - var jsobj = JSObject() + val jsobj = JSObject() jsobj.put("value", hasAccess) call.resolve(jsobj) } @PluginMethod fun scanFolder(call: PluginCall) { - var folderId = call.data.getString("folderId", "").toString() - var forceAudioProbe = call.data.getBoolean("forceAudioProbe") + val folderId = call.data.getString("folderId", "").toString() + val forceAudioProbe = call.data.getBoolean("forceAudioProbe") Log.d(TAG, "Scan Folder $folderId | Force Audio Probe $forceAudioProbe") - var folder: LocalFolder? = DeviceManager.dbManager.getLocalFolder(folderId) + val folder: LocalFolder? = DeviceManager.dbManager.getLocalFolder(folderId) folder?.let { - var folderScanner = FolderScanner(context) - var folderScanResult = folderScanner.scanForMediaItems(it, forceAudioProbe) + val folderScanner = FolderScanner(context) + val folderScanResult = folderScanner.scanForMediaItems(it, forceAudioProbe) if (folderScanResult == null) { Log.d(TAG, "NO Scan DATA") return call.resolve(JSObject()) @@ -186,28 +186,28 @@ class AbsFileSystem : Plugin() { @PluginMethod fun removeFolder(call: PluginCall) { - var folderId = call.data.getString("folderId", "").toString() + val folderId = call.data.getString("folderId", "").toString() DeviceManager.dbManager.removeLocalFolder(folderId) call.resolve() } @PluginMethod fun removeLocalLibraryItem(call: PluginCall) { - var localLibraryItemId = call.data.getString("localLibraryItemId", "").toString() + val localLibraryItemId = call.data.getString("localLibraryItemId", "").toString() DeviceManager.dbManager.removeLocalLibraryItem(localLibraryItemId) call.resolve() } @PluginMethod fun scanLocalLibraryItem(call: PluginCall) { - var localLibraryItemId = call.data.getString("localLibraryItemId", "").toString() - var forceAudioProbe = call.data.getBoolean("forceAudioProbe") + val localLibraryItemId = call.data.getString("localLibraryItemId", "").toString() + val forceAudioProbe = call.data.getBoolean("forceAudioProbe") Log.d(TAG, "Scan Local library item $localLibraryItemId | Force Audio Probe $forceAudioProbe") GlobalScope.launch(Dispatchers.IO) { - var localLibraryItem: LocalLibraryItem? = DeviceManager.dbManager.getLocalLibraryItem(localLibraryItemId) + val localLibraryItem: LocalLibraryItem? = DeviceManager.dbManager.getLocalLibraryItem(localLibraryItemId) localLibraryItem?.let { - var folderScanner = FolderScanner(context) - var scanResult = folderScanner.scanLocalLibraryItem(it, forceAudioProbe) + val folderScanner = FolderScanner(context) + val scanResult = folderScanner.scanLocalLibraryItem(it, forceAudioProbe) if (scanResult == null) { Log.d(TAG, "NO Scan DATA") call.resolve(JSObject()) @@ -221,13 +221,13 @@ class AbsFileSystem : Plugin() { @PluginMethod 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() + val localLibraryItemId = call.data.getString("id", "").toString() + val absolutePath = call.data.getString("absolutePath", "").toString() + val contentUrl = call.data.getString("contentUrl", "").toString() Log.d(tag, "deleteItem $absolutePath | $contentUrl") - var docfile = DocumentFileCompat.fromUri(mainActivity, Uri.parse(contentUrl)) - var success = docfile?.delete() == true + val docfile = DocumentFileCompat.fromUri(mainActivity, Uri.parse(contentUrl)) + val success = docfile?.delete() == true if (success) { DeviceManager.dbManager.removeLocalLibraryItem(localLibraryItemId) } @@ -236,19 +236,19 @@ class AbsFileSystem : Plugin() { @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() + val localLibraryItemId = call.data.getString("id", "").toString() + val trackLocalFileId = call.data.getString("trackLocalFileId", "").toString() + val contentUrl = call.data.getString("trackContentUrl", "").toString() Log.d(tag, "deleteTrackFromItem $contentUrl") - var localLibraryItem = DeviceManager.dbManager.getLocalLibraryItem(localLibraryItemId) + val 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 + val docfile = DocumentFileCompat.fromUri(mainActivity, Uri.parse(contentUrl)) + val success = docfile?.delete() == true if (success) { localLibraryItem.media.removeAudioTrack(trackLocalFileId) localLibraryItem.removeLocalFile(trackLocalFileId)