diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fb665495..c748ad85e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,8 +3,10 @@ ## dev #### What's New +- Massively overhauled how music is loaded [#72]: + - Auxio can now reload music without requiring a restart + - Added a new option to reload music when device files change - Added direct metadata parsing, allowing more correct metadata at the cost of longer loading times -- Auxio can now reload music without requiring a restart - Added a shuffle shortcut - Widgets now have a more sleek and consistent button layout - "Rounded album covers" is now "Round mode" diff --git a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt index abfaf5b64..ec034fb70 100644 --- a/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/detail/DetailViewModel.kt @@ -33,9 +33,9 @@ import org.oxycblt.auxio.detail.recycler.SortHeader import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.MimeType import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.music.system.MimeType import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.ui.recycler.Header diff --git a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt index e2d57c35e..6d877e668 100644 --- a/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/home/HomeFragment.kt @@ -46,10 +46,10 @@ import org.oxycblt.auxio.home.list.SongListFragment import org.oxycblt.auxio.music.Album import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre +import org.oxycblt.auxio.music.IndexerViewModel import org.oxycblt.auxio.music.Music import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.system.Indexer -import org.oxycblt.auxio.music.system.IndexerViewModel import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.ui.DisplayMode import org.oxycblt.auxio.ui.MainNavigationAction @@ -313,7 +313,7 @@ class HomeFragment : ViewBindingFragment(), Toolbar.OnMenuI when (indexing) { is Indexer.Indexing.Indeterminate -> { - binding.homeIndexingStatus.textSafe = getString(R.string.lbl_indexing) + binding.homeIndexingStatus.textSafe = getString(R.string.lbl_indexing_desc) binding.homeIndexingProgress.isIndeterminate = true } is Indexer.Indexing.Songs -> { diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerViewModel.kt b/app/src/main/java/org/oxycblt/auxio/music/IndexerViewModel.kt similarity index 91% rename from app/src/main/java/org/oxycblt/auxio/music/system/IndexerViewModel.kt rename to app/src/main/java/org/oxycblt/auxio/music/IndexerViewModel.kt index bd42c765d..809006c79 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/IndexerViewModel.kt @@ -15,15 +15,18 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.system +package org.oxycblt.auxio.music import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import org.oxycblt.auxio.music.system.Indexer /** * A ViewModel representing the current music indexing state. * @author OxygenCobalt + * + * TODO: Indeterminate state for Home + Settings */ class IndexerViewModel : ViewModel(), Indexer.Callback { private val indexer = Indexer.getInstance() diff --git a/app/src/main/java/org/oxycblt/auxio/music/Music.kt b/app/src/main/java/org/oxycblt/auxio/music/Music.kt index 8e83442ee..2c4d47b46 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Music.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Music.kt @@ -23,8 +23,6 @@ import android.content.Context import android.net.Uri import android.provider.MediaStore import org.oxycblt.auxio.R -import org.oxycblt.auxio.music.system.MimeType -import org.oxycblt.auxio.music.system.Path import org.oxycblt.auxio.ui.recycler.Item import org.oxycblt.auxio.util.unlikelyToBeNull diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/StorageFramework.kt b/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt similarity index 98% rename from app/src/main/java/org/oxycblt/auxio/music/system/StorageFramework.kt rename to app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt index f36691143..0540eb4fb 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/StorageFramework.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/StorageFramework.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.system +package org.oxycblt.auxio.music import android.annotation.SuppressLint import android.content.Context @@ -109,7 +109,7 @@ val StorageVolume.directoryCompat: String? if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { directory?.absolutePath } else { - // Replicate getDirectory: getPath if mounted, null if not + // Replicate API: getPath if mounted, null if not when (stateCompat) { Environment.MEDIA_MOUNTED, Environment.MEDIA_MOUNTED_READ_ONLY -> diff --git a/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirAdapter.kt b/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirAdapter.kt index f29daad7c..968a59dd3 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirAdapter.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirAdapter.kt @@ -19,7 +19,7 @@ package org.oxycblt.auxio.music.dirs import android.content.Context import org.oxycblt.auxio.databinding.ItemMusicDirBinding -import org.oxycblt.auxio.music.system.Directory +import org.oxycblt.auxio.music.Directory import org.oxycblt.auxio.ui.recycler.BackingData import org.oxycblt.auxio.ui.recycler.BindingViewHolder import org.oxycblt.auxio.ui.recycler.MonoAdapter diff --git a/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirs.kt b/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirs.kt index 82a65fb7a..748bd43c1 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirs.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirs.kt @@ -17,7 +17,7 @@ package org.oxycblt.auxio.music.dirs -import org.oxycblt.auxio.music.system.Directory +import org.oxycblt.auxio.music.Directory /** Represents a the configuration for the "Folder Management" setting */ data class MusicDirs(val dirs: List, val shouldInclude: Boolean) diff --git a/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirsDialog.kt b/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirsDialog.kt index 0d5c78576..ac7aae92f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirsDialog.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/dirs/MusicDirsDialog.kt @@ -28,7 +28,7 @@ import androidx.core.view.isVisible import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogMusicDirsBinding -import org.oxycblt.auxio.music.system.Directory +import org.oxycblt.auxio.music.Directory import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment import org.oxycblt.auxio.util.context diff --git a/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt b/app/src/main/java/org/oxycblt/auxio/music/system/ExoPlayerBackend.kt similarity index 99% rename from app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt rename to app/src/main/java/org/oxycblt/auxio/music/system/ExoPlayerBackend.kt index e8e247be5..e4a953184 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/ExoPlayerBackend.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.backend +package org.oxycblt.auxio.music.system import android.content.Context import android.database.Cursor @@ -29,7 +29,6 @@ import org.oxycblt.auxio.music.audioUri import org.oxycblt.auxio.music.id3GenreName import org.oxycblt.auxio.music.iso8601year import org.oxycblt.auxio.music.plainTrackNo -import org.oxycblt.auxio.music.system.Indexer import org.oxycblt.auxio.music.trackDiscNo import org.oxycblt.auxio.music.year import org.oxycblt.auxio.util.logD diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt index 23d27a761..128e3444b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/Indexer.kt @@ -32,10 +32,6 @@ import org.oxycblt.auxio.music.Artist import org.oxycblt.auxio.music.Genre import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.music.Song -import org.oxycblt.auxio.music.backend.Api21MediaStoreBackend -import org.oxycblt.auxio.music.backend.Api29MediaStoreBackend -import org.oxycblt.auxio.music.backend.Api30MediaStoreBackend -import org.oxycblt.auxio.music.backend.ExoPlayerBackend import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.Sort import org.oxycblt.auxio.util.logD @@ -130,6 +126,10 @@ class Indexer { this.callback = null } + /** + * Start the indexing process. This should be done by [Controller] in a background thread. When + * complete, a new completion state will be pushed to each callback. + */ suspend fun index(context: Context) { requireBackgroundThread() @@ -190,7 +190,7 @@ class Indexer { @Synchronized private fun emitIndexing(indexing: Indexing?, generation: Long) { - checkGenerationImpl(generation) + checkGeneration(generation) if (indexing == indexingState) { // Ignore redundant states used when the backends just want to check for @@ -211,7 +211,7 @@ class Indexer { private suspend fun emitCompletion(response: Response, generation: Long) { synchronized(this) { - checkGenerationImpl(generation) + checkGeneration(generation) // Do not check for redundancy here, as we actually need to notify a switch // from Indexing -> Complete and not Indexing -> Indexing or Complete -> Complete. @@ -230,7 +230,7 @@ class Indexer { } } - private fun checkGenerationImpl(generation: Long) { + private fun checkGeneration(generation: Long) { if (currentGeneration != generation) { // Not the running task anymore, cancel this co-routine. This allows a yield-like // behavior to be implemented in a far cheaper manner for each backend. diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotification.kt b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt similarity index 69% rename from app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotification.kt rename to app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt index 0cfd1f1a4..ee9525394 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotification.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerNotifications.kt @@ -30,7 +30,7 @@ import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.newMainPendingIntent /** The notification responsible for showing the indexer state. */ -class IndexerNotification(private val context: Context) : +class IndexingNotification(private val context: Context) : NotificationCompat.Builder(context, CHANNEL_ID) { private val notificationManager = context.getSystemServiceSafe(NotificationManager::class) @@ -52,7 +52,7 @@ class IndexerNotification(private val context: Context) : setContentIntent(context.newMainPendingIntent()) setVisibility(NotificationCompat.VISIBILITY_PUBLIC) setContentTitle(context.getString(R.string.info_indexer_channel_name)) - setContentText(context.getString(R.string.lbl_indexing)) + setContentText(context.getString(R.string.lbl_indexing_desc)) setProgress(0, 0, true) } @@ -64,7 +64,7 @@ class IndexerNotification(private val context: Context) : when (indexing) { is Indexer.Indexing.Indeterminate -> { logD("Updating state to $indexing") - setContentText(context.getString(R.string.lbl_indexing)) + setContentText(context.getString(R.string.lbl_indexing_desc)) setProgress(0, 0, true) return true } @@ -87,3 +87,37 @@ class IndexerNotification(private val context: Context) : const val CHANNEL_ID = BuildConfig.APPLICATION_ID + ".channel.INDEXER" } } + +/** The notification responsible for showing the indexer state. */ +class ObservingNotification(context: Context) : NotificationCompat.Builder(context, CHANNEL_ID) { + private val notificationManager = context.getSystemServiceSafe(NotificationManager::class) + + init { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channel = + NotificationChannel( + CHANNEL_ID, + context.getString(R.string.info_indexer_channel_name), + NotificationManager.IMPORTANCE_LOW) + + notificationManager.createNotificationChannel(channel) + } + + setSmallIcon(R.drawable.ic_indexer_24) + setCategory(NotificationCompat.CATEGORY_SERVICE) + setShowWhen(false) + setSilent(true) + setContentIntent(context.newMainPendingIntent()) + setVisibility(NotificationCompat.VISIBILITY_PUBLIC) + setContentTitle(context.getString(R.string.lbl_observing)) + setContentText(context.getString(R.string.lbl_observing_desc)) + } + + fun renotify() { + notificationManager.notify(IntegerTable.INDEXER_NOTIFICATION_CODE, build()) + } + + companion object { + const val CHANNEL_ID = BuildConfig.APPLICATION_ID + ".channel.INDEXER" + } +} diff --git a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt index 824a44a36..0461c2e5c 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/IndexerService.kt @@ -17,9 +17,14 @@ package org.oxycblt.auxio.music.system +import android.app.Notification import android.app.Service import android.content.Intent +import android.database.ContentObserver +import android.os.Handler import android.os.IBinder +import android.os.Looper +import android.provider.MediaStore import androidx.core.app.ServiceCompat import coil.imageLoader import kotlinx.coroutines.CoroutineScope @@ -31,6 +36,7 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.music.MusicStore import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.settings.Settings +import org.oxycblt.auxio.util.contentResolverSafe import org.oxycblt.auxio.util.logD /** @@ -43,8 +49,6 @@ import org.oxycblt.auxio.util.logD * boilerplate you skip is not worth the insanity of androidx. * * @author OxygenCobalt - * - * TODO: Add file observing */ class IndexerService : Service(), Indexer.Controller, Settings.Callback { private val indexer = Indexer.getInstance() @@ -52,18 +56,23 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback { private val serviceJob = Job() private val indexScope = CoroutineScope(serviceJob + Dispatchers.IO) + private lateinit var indexerContentObserver: SystemContentObserver private val playbackManager = PlaybackStateManager.getInstance() private lateinit var settings: Settings private var isForeground = false - private lateinit var notification: IndexerNotification + private lateinit var indexingNotification: IndexingNotification + private lateinit var observingNotification: ObservingNotification override fun onCreate() { super.onCreate() - notification = IndexerNotification(this) settings = Settings(this, this) + indexerContentObserver = SystemContentObserver() + + indexingNotification = IndexingNotification(this) + observingNotification = ObservingNotification(this) indexer.registerController(this) if (musicStore.library == null && indexer.isIndeterminate) { @@ -81,12 +90,14 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback { override fun onDestroy() { super.onDestroy() - // cancelLast actually stops foreground for us as it updates the loading state to - // null or completed. - indexer.cancelLast() - indexer.unregisterController(this) - serviceJob.cancel() + // De-initialize the components first to prevent stray reloading events settings.release() + indexerContentObserver.release() + indexer.unregisterController(this) + + // Then cancel the other components. + indexer.cancelLast() + serviceJob.cancel() } // --- CONTROLLER CALLBACKS --- @@ -109,7 +120,7 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback { val newLibrary = state.response.library if (musicStore.library != null) { - // This is a new library to replace a pre-existing one. + // This is a new library to replace an existing one. // Wipe possibly-invalidated album covers imageLoader.memoryCache?.clear() @@ -127,46 +138,115 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback { // error, in practice that comes into conflict with the upcoming Android 13 // notification permission, and there is no point implementing permission // on-boarding for such when it will only be used for this. - stopForegroundSession() + updateIdleSession() } is Indexer.State.Indexing -> { - // When loading, we want to enter the foreground state so that android does - // not shut off the loading process. Note that while we will always post the - // notification when initially starting, we will not update the notification - // unless it indicates that we have changed it. - val changed = notification.updateIndexingState(state.indexing) - if (!isForeground) { - logD("Starting foreground session") - startForeground(IntegerTable.INDEXER_NOTIFICATION_CODE, notification.build()) - isForeground = true - } else if (changed) { - logD("Notification changed, re-posting notification") - notification.renotify() - } + updateActiveSession(state.indexing) } null -> { // Null is the indeterminate state that occurs on app startup or after // the cancellation of a load, so in that case we want to stop foreground // since (technically) nothing is loading. - stopForegroundSession() + updateIdleSession() } } } + // --- INTERNAL --- + + private fun updateActiveSession(state: Indexer.Indexing) { + // When loading, we want to enter the foreground state so that android does + // not shut off the loading process. Note that while we will always post the + // notification when initially starting, we will not update the notification + // unless it indicates that we have changed it. + val changed = indexingNotification.updateIndexingState(state) + if (!tryStartForeground(indexingNotification.build()) && changed) { + logD("Notification changed, re-posting notification") + indexingNotification.renotify() + } + } + + private fun updateIdleSession() { + if (settings.shouldBeObserving) { + // There are a few reasons why we stay in the foreground with automatic rescanning: + // 1. Newer versions of Android have become more and more restrictive regarding + // how a foreground service starts. Thus, it's best to go foreground now so that + // we can go foreground later. + // 2. If a non-foreground service is killed, the app will probably still be alive, + // and thus the music library will not be updated at all. + if (!tryStartForeground(observingNotification.build())) { + observingNotification.renotify() + } + } else { + tryStopForeground() + } + } + + private fun tryStartForeground(notification: Notification): Boolean { + if (isForeground) { + return false + } + + startForeground(IntegerTable.INDEXER_NOTIFICATION_CODE, notification) + isForeground = true + + return true + } + + private fun tryStopForeground() { + if (!isForeground) { + return + } + + ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE) + isForeground = false + } + // --- SETTING CALLBACKS --- override fun onSettingChanged(key: String) { - if (key == getString(R.string.set_key_music_dirs) || - key == getString(R.string.set_key_music_dirs_include) || - key == getString(R.string.set_key_quality_tags)) { - onStartIndexing() + when (key) { + getString(R.string.set_key_music_dirs), + getString(R.string.set_key_music_dirs_include), + getString(R.string.set_key_quality_tags) -> onStartIndexing() + getString(R.string.set_key_observing) -> { + if (!indexer.isIndexing) { + updateIdleSession() + } + } } } - private fun stopForegroundSession() { - if (isForeground) { - ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE) - isForeground = false + /** Internal content observer intended to work with the automatic reloading framework. */ + private inner class SystemContentObserver( + private val handler: Handler = Handler(Looper.getMainLooper()) + ) : ContentObserver(handler), Runnable { + init { + contentResolverSafe.registerContentObserver( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, true, this) + } + + fun release() { + contentResolverSafe.unregisterContentObserver(this) + } + + override fun onChange(selfChange: Boolean) { + // Batch rapid-fire updates to the library into a single call to run after an + // arbitrary amount of time. + handler.removeCallbacks(this) + handler.postDelayed(this, REINDEX_DELAY) + } + + override fun run() { + // Check here if we should even start a reindex. This is much less bug-prone than + // registering and de-registering this component as this setting changes. + if (settings.shouldBeObserving) { + onStartIndexing() + } } } + + companion object { + const val REINDEX_DELAY = 500L + } } diff --git a/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt b/app/src/main/java/org/oxycblt/auxio/music/system/MediaStoreBackend.kt similarity index 97% rename from app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt rename to app/src/main/java/org/oxycblt/auxio/music/system/MediaStoreBackend.kt index 639296268..a769ea585 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/backend/MediaStoreBackend.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/system/MediaStoreBackend.kt @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -package org.oxycblt.auxio.music.backend +package org.oxycblt.auxio.music.system import android.content.Context import android.database.Cursor @@ -27,20 +27,19 @@ import androidx.annotation.RequiresApi import androidx.core.database.getIntOrNull import androidx.core.database.getStringOrNull import java.io.File +import org.oxycblt.auxio.music.Directory +import org.oxycblt.auxio.music.MimeType +import org.oxycblt.auxio.music.Path import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.albumCoverUri import org.oxycblt.auxio.music.audioUri +import org.oxycblt.auxio.music.directoryCompat import org.oxycblt.auxio.music.id3GenreName +import org.oxycblt.auxio.music.mediaStoreVolumeNameCompat import org.oxycblt.auxio.music.packedDiscNo import org.oxycblt.auxio.music.packedTrackNo import org.oxycblt.auxio.music.queryCursor -import org.oxycblt.auxio.music.system.Directory -import org.oxycblt.auxio.music.system.Indexer -import org.oxycblt.auxio.music.system.MimeType -import org.oxycblt.auxio.music.system.Path -import org.oxycblt.auxio.music.system.directoryCompat -import org.oxycblt.auxio.music.system.mediaStoreVolumeNameCompat -import org.oxycblt.auxio.music.system.storageVolumesCompat +import org.oxycblt.auxio.music.storageVolumesCompat import org.oxycblt.auxio.music.trackDiscNo import org.oxycblt.auxio.music.useQuery import org.oxycblt.auxio.settings.Settings @@ -182,6 +181,8 @@ abstract class MediaStoreBackend : Indexer.Backend { audios.add(buildAudio(context, cursor)) if (cursor.position % 50 == 0) { // Only check for a cancellation every 50 songs or so (~20ms). + // While this seems redundant, each call to emitIndexing checks for a + // cancellation of the co-routine this loading task is running on. emitIndexing(Indexer.Indexing.Indeterminate) } } @@ -462,8 +463,8 @@ class Api21MediaStoreBackend : MediaStoreBackend() { } /** - * A [MediaStoreBackend] that selects directories and builds paths using the modern volume - * primitives available from API 29 onwards. + * A [MediaStoreBackend] that selects directories and builds paths using the modern volume fields + * available from API 29 onwards. * @author OxygenCobalt */ @RequiresApi(Build.VERSION_CODES.Q) @@ -503,7 +504,7 @@ open class VolumeAwareMediaStoreBackend : MediaStoreBackend() { val relativePath = cursor.getString(relativePathIndex) // Find the StorageVolume whose MediaStore name corresponds to this song. - // This is what we use for the Directory. + // This is what we use for the Directory's volume. val volume = volumes.find { it.mediaStoreVolumeNameCompat == volumeName } if (volume != null) { audio.dir = Directory(volume, relativePath.removeSuffix(File.separator)) @@ -532,7 +533,7 @@ open class Api29MediaStoreBackend : VolumeAwareMediaStoreBackend() { trackIndex = cursor.getColumnIndexOrThrow(MediaStore.Audio.AudioColumns.TRACK) } - // This backend is volume-aware, but does not support the modern track primitives. + // This backend is volume-aware, but does not support the modern track fields. // Use the packed utilities instead. val rawTrack = cursor.getIntOrNull(trackIndex) if (rawTrack != null) { diff --git a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt index 605f22b46..e723c1d80 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt @@ -25,8 +25,8 @@ import androidx.core.content.edit import androidx.preference.PreferenceManager import org.oxycblt.auxio.R import org.oxycblt.auxio.home.tabs.Tab +import org.oxycblt.auxio.music.Directory import org.oxycblt.auxio.music.dirs.MusicDirs -import org.oxycblt.auxio.music.system.Directory import org.oxycblt.auxio.playback.replaygain.ReplayGainMode import org.oxycblt.auxio.playback.replaygain.ReplayGainPreAmp import org.oxycblt.auxio.playback.state.PlaybackMode @@ -185,6 +185,10 @@ class Settings(private val context: Context, private val callback: Callback? = n val pauseOnRepeat: Boolean get() = inner.getBoolean(context.getString(R.string.set_key_repeat_pause), false) + /** Whether to be actively watching for changes in the music library. */ + val shouldBeObserving: Boolean + get() = inner.getBoolean(context.getString(R.string.set_key_observing), false) + /** Whether to parse metadata directly with ExoPlayer. */ val useQualityTags: Boolean get() = inner.getBoolean(context.getString(R.string.set_key_quality_tags), false) diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt index 694848be2..0747919c8 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsCompat.kt @@ -27,10 +27,10 @@ import android.util.Log import androidx.core.content.edit import java.io.File import org.oxycblt.auxio.R -import org.oxycblt.auxio.music.system.Directory -import org.oxycblt.auxio.music.system.directoryCompat -import org.oxycblt.auxio.music.system.isInternalCompat -import org.oxycblt.auxio.music.system.storageVolumesCompat +import org.oxycblt.auxio.music.Directory +import org.oxycblt.auxio.music.directoryCompat +import org.oxycblt.auxio.music.isInternalCompat +import org.oxycblt.auxio.music.storageVolumesCompat import org.oxycblt.auxio.ui.accent.Accent import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.queryAll diff --git a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt index 12320f83b..0153ea1fe 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/SettingsListFragment.kt @@ -31,8 +31,8 @@ import androidx.recyclerview.widget.RecyclerView import coil.Coil import org.oxycblt.auxio.R import org.oxycblt.auxio.home.tabs.TabCustomizeDialog +import org.oxycblt.auxio.music.IndexerViewModel import org.oxycblt.auxio.music.dirs.MusicDirsDialog -import org.oxycblt.auxio.music.system.IndexerViewModel import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.playback.replaygain.PreAmpCustomizeDialog import org.oxycblt.auxio.settings.ui.IntListPreference diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index db1e1ba21..5d49380cf 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -6,7 +6,7 @@ Načítání hudby Zobrazení a ovládání přehrávání hudby - Načítání vaší hudební knihovny… + Načítání vaší hudební knihovny… Zkusit znovu Udělit Žánry diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 311ab26e9..4030813cf 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -192,7 +192,7 @@ Mischen Alle mischen Musik wird geladen - Lade deine Musikbibliothek… + Lade deine Musikbibliothek… Abtastrate Zeige Eigenschaften an Lied-Eigenschaften diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 4c2a61af6..5b03f7864 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -101,7 +101,7 @@ Lecture de musique Chargement de musique Afficher et contrôler la lecture de la musique - Chargement de votre bibliothèque musicale… + Chargement de votre bibliothèque musicale… Nom Artiste Album diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index c874eac0e..e4d207268 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -73,7 +73,7 @@ Ukuran Tingkat sampel Tab Pustaka - Memuat perpustakaan musik Anda… + Memuat perpustakaan musik Anda… Nama Artis Album diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 905782f4d..1d393889f 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -185,7 +185,7 @@ Nessuna frequenza di campionamento -%.1f dB Caricamento musica - Caricamento libreria musicale… + Caricamento libreria musicale… Durata Conteggio canzoni Disco diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index beb4842c7..520b5405a 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -102,7 +102,7 @@ Modo O Auxio precisa de permissão para ler sua biblioteca de músicas Um leitor de música simples e racional para android. - Carregando sua biblioteca de músicas… + Carregando sua biblioteca de músicas… Ano Duração Contagem de Músicas diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 16fe1c7ae..e5cd49a34 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -76,7 +76,7 @@ Müzik çalmayı görüntüle ve kontrol et Android için basit, rasyonel bir müzik çalar. Müzik Yükleniyor - Müzik kitaplığınız yükleniyor… + Müzik kitaplığınız yükleniyor… İsim Sanatçı Albüm diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index ecd926c95..984eceab1 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -185,7 +185,7 @@ 已加载专辑数量:%d 没有采样率信息 音乐加载中 - 正在加载您的音乐库…… + 正在加载您的音乐库…… 碟片 时长 歌曲计数 diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/settings.xml index 2bd59f2eb..a9af21329 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/settings.xml @@ -28,7 +28,8 @@ auxio_reindex auxio_music_dirs auxio_include_dirs - auxio_quality_tags + auxio_observing + auxio_quality_tags KEY_SEARCH_FILTER diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 62013c2d2..31dc2bc91 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -2,14 +2,16 @@ A simple, rational music player for android. - Music Playback - Music Loading + Music playback + Music loading View and control music playback - Loading your music library… Retry Grant + Loading your music library… + Automatic reloading + Monitoring your music library for changes… (You can disable this in settings) Genres Artists @@ -146,7 +148,9 @@ Include Music will only be loaded from the folders you add. Ignore MediaStore tags - Increases tag quality, but requires longer loading times + Increases tag quality, but requires longer loading times (Experimental) + Automatic reloading + Reload music whenever your audio files change (Experimental) No music found @@ -227,6 +231,7 @@ Orange Brown Grey + Dynamic @@ -247,6 +252,7 @@ Albums loaded: %d Artists loaded: %d Genres loaded: %d + Total duration: %s diff --git a/app/src/main/res/xml/prefs_main.xml b/app/src/main/res/xml/prefs_main.xml index c664c75ac..e2c36d027 100644 --- a/app/src/main/res/xml/prefs_main.xml +++ b/app/src/main/res/xml/prefs_main.xml @@ -187,5 +187,13 @@ app:summary="@string/set_quality_tags_desc" app:title="@string/set_quality_tags" /> + + \ No newline at end of file