diff --git a/CHANGELOG.md b/CHANGELOG.md index 32039e432..e51e2e36d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## dev #### What's New +- 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/music/Indexer.kt b/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt index 01d1e484e..2535ee0c9 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/Indexer.kt @@ -27,6 +27,8 @@ import org.oxycblt.auxio.BuildConfig 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 import org.oxycblt.auxio.util.logE @@ -80,7 +82,7 @@ class Indexer { return } - synchronized(this) { this.controller = controller } + this.controller = controller } /** Unregister a [Controller] with this instance. */ @@ -119,9 +121,8 @@ class Indexer { this.callback = null } - @Synchronized fun index(context: Context) { - val generation = ++currentGeneration + val generation = synchronized(this) { ++currentGeneration } val notGranted = ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == @@ -227,7 +228,15 @@ class Indexer { else -> Api21MediaStoreBackend() } - val songs = buildSongs(context, mediaStoreBackend, generation) + val settings = Settings(context) + val backend = + if (settings.useQualityTags) { + ExoPlayerBackend(mediaStoreBackend) + } else { + mediaStoreBackend + } + + val songs = buildSongs(context, backend, generation) if (songs.isEmpty()) { return null } diff --git a/app/src/main/java/org/oxycblt/auxio/music/IndexerService.kt b/app/src/main/java/org/oxycblt/auxio/music/IndexerService.kt index 610eaf58d..d6fe5fc8b 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/IndexerService.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/IndexerService.kt @@ -27,10 +27,11 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.cancel import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import org.oxycblt.auxio.IntegerTable +import org.oxycblt.auxio.R import org.oxycblt.auxio.playback.state.PlaybackStateDatabase import org.oxycblt.auxio.playback.state.PlaybackStateManager +import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.util.logD /** @@ -43,8 +44,14 @@ import org.oxycblt.auxio.util.logD * boilerplate you skip is not worth the insanity of androidx. * * @author OxygenCobalt + * + * TODO: Add file observing + * + * TODO: Audit usages of synchronized + * + * TODO: Rework UI flow once again */ -class IndexerService : Service(), Indexer.Controller { +class IndexerService : Service(), Indexer.Controller, Settings.Callback { private val indexer = Indexer.getInstance() private val musicStore = MusicStore.getInstance() @@ -53,6 +60,7 @@ class IndexerService : Service(), Indexer.Controller { private val updateScope = CoroutineScope(serviceJob + Dispatchers.Main) private val playbackManager = PlaybackStateManager.getInstance() + private lateinit var settings: Settings private var isForeground = false private lateinit var notification: IndexerNotification @@ -61,6 +69,7 @@ class IndexerService : Service(), Indexer.Controller { super.onCreate() notification = IndexerNotification(this) + settings = Settings(this, this) indexer.registerController(this) if (musicStore.library == null && indexer.isIndeterminate) { @@ -83,12 +92,14 @@ class IndexerService : Service(), Indexer.Controller { indexer.cancelLast() indexer.unregisterController(this) serviceJob.cancel() + settings.release() } + // --- CONTROLLER CALLBACKS --- + override fun onStartIndexing() { if (indexer.isIndexing) { indexer.cancelLast() - indexScope.cancel() } indexScope.launch { indexer.index(this@IndexerService) } @@ -103,9 +114,8 @@ class IndexerService : Service(), Indexer.Controller { val newLibrary = state.response.library - // Load was completed successfully, so apply the new library if we - // have not already. Only when we are done updating the library will - // the service stop it's foreground state. + // Load was completed successfully. However, we still need to do some + // extra work to update the app's state. updateScope.launch { imageLoader.memoryCache?.clear() @@ -116,7 +126,7 @@ class IndexerService : Service(), Indexer.Controller { PlaybackStateDatabase.getInstance(this@IndexerService), newLibrary) } - withContext(Dispatchers.Main) { musicStore.updateLibrary(newLibrary) } + musicStore.updateLibrary(newLibrary) stopForegroundSession() } @@ -152,6 +162,16 @@ class IndexerService : Service(), Indexer.Controller { } } + // --- 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() + } + } + private fun stopForegroundSession() { if (isForeground) { ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE) diff --git a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt index 644b78830..abf28922d 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/MusicStore.kt @@ -26,7 +26,6 @@ import org.oxycblt.auxio.util.contentResolverSafe * The main storage for music items. The items themselves are located in a [Library], however this * might not be available at all times. * - * TODO: Add automatic rescanning [major change] * @author OxygenCobalt */ class MusicStore private constructor() { diff --git a/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt b/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt index 866391e1b..57f35ec2d 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/backend/ExoPlayerBackend.kt @@ -184,7 +184,7 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) { } } is VorbisComment -> { - val id = tag.value.sanitize() + val id = tag.key.sanitize() val value = tag.value.sanitize() if (value.isNotEmpty()) { vorbisTags[id] = value @@ -244,6 +244,7 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) { } private fun populateVorbis(tags: Map) { + logD(tags) // Title tags["TITLE"]?.let { audio.title = it } @@ -266,7 +267,7 @@ class Task(context: Context, private val audio: MediaStoreBackend.Audio) { tags["ALBUM"]?.let { audio.album = it } // Artist - tags["ARTIST"]?.let { audio.title } + tags["ARTIST"]?.let { audio.artist = it } // Album artist. This actually comes into two flavors: // 1. ALBUMARTIST, which is the most common 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 f80a3b8a7..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 @@ -25,16 +25,12 @@ import android.view.LayoutInflater import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible -import androidx.fragment.app.activityViewModels import org.oxycblt.auxio.BuildConfig import org.oxycblt.auxio.R import org.oxycblt.auxio.databinding.DialogMusicDirsBinding import org.oxycblt.auxio.music.Directory -import org.oxycblt.auxio.music.IndexerViewModel -import org.oxycblt.auxio.playback.PlaybackViewModel import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.ui.fragment.ViewBindingDialogFragment -import org.oxycblt.auxio.util.androidActivityViewModels import org.oxycblt.auxio.util.context import org.oxycblt.auxio.util.getSystemServiceSafe import org.oxycblt.auxio.util.logD @@ -46,12 +42,11 @@ import org.oxycblt.auxio.util.showToast */ class MusicDirsDialog : ViewBindingDialogFragment(), MusicDirAdapter.Listener { - private val playbackModel: PlaybackViewModel by androidActivityViewModels() - private val indexerModel: IndexerViewModel by activityViewModels() - private val dirAdapter = MusicDirAdapter(this) private val settings: Settings by lifecycleObject { binding -> Settings(binding.context) } - private var storageManager: StorageManager? = null + private val storageManager: StorageManager by lifecycleObject { binding -> + binding.context.getSystemServiceSafe(StorageManager::class) + } override fun onCreateBinding(inflater: LayoutInflater) = DialogMusicDirsBinding.inflate(inflater) @@ -61,7 +56,17 @@ class MusicDirsDialog : builder .setTitle(R.string.set_dirs) .setNeutralButton(R.string.lbl_add, null) - .setPositiveButton(R.string.lbl_save, null) + .setPositiveButton(R.string.lbl_save) { _, _ -> + val dirs = settings.getMusicDirs(storageManager) + val newDirs = + MusicDirs( + dirs = dirAdapter.data.currentList, + shouldInclude = isInclude(requireBinding())) + if (dirs != newDirs) { + logD("Committing changes") + settings.setMusicDirs(newDirs) + } + } .setNegativeButton(R.string.lbl_cancel, null) } @@ -80,19 +85,6 @@ class MusicDirsDialog : logD("Opening launcher") launcher.launch(null) } - - dialog.getButton(AlertDialog.BUTTON_POSITIVE)?.setOnClickListener { - val dirs = settings.getMusicDirs(requireStorageManager()) - - if (dirs.dirs != dirAdapter.data.currentList || - dirs.shouldInclude != isInclude(requireBinding())) { - logD("Committing changes") - saveAndDismiss() - } else { - logD("Dropping changes") - dismiss() - } - } } binding.dirsRecycler.apply { @@ -100,7 +92,6 @@ class MusicDirsDialog : itemAnimator = null } - val storageManager = requireStorageManager() var dirs = settings.getMusicDirs(storageManager) if (savedInstanceState != null) { @@ -173,7 +164,7 @@ class MusicDirsDialog : val treeUri = DocumentsContract.getTreeDocumentId(docUri) // Parsing handles the rest - return Directory.fromDocumentUri(requireStorageManager(), treeUri) + return Directory.fromDocumentUri(storageManager, treeUri) } private fun updateMode() { @@ -188,22 +179,6 @@ class MusicDirsDialog : private fun isInclude(binding: DialogMusicDirsBinding) = binding.folderModeGroup.checkedButtonId == R.id.dirs_mode_include - private fun saveAndDismiss() { - settings.setMusicDirs(MusicDirs(dirAdapter.data.currentList, isInclude(requireBinding()))) - indexerModel.reindex() - dismiss() - } - - private fun requireStorageManager(): StorageManager { - val mgr = storageManager - if (mgr != null) { - return mgr - } - val newMgr = requireContext().getSystemServiceSafe(StorageManager::class) - storageManager = newMgr - return newMgr - } - companion object { const val TAG = BuildConfig.APPLICATION_ID + ".tag.EXCLUDED" const val KEY_PENDING_DIRS = BuildConfig.APPLICATION_ID + ".key.PENDING_DIRS" 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 dc666a142..196374779 100644 --- a/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt +++ b/app/src/main/java/org/oxycblt/auxio/settings/Settings.kt @@ -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 parse metadata directly with ExoPlayer. */ + val useQualityTags: Boolean + get() = inner.getBoolean(context.getString(R.string.set_key_quality_tags), false) + /** Get the list of directories that music should be hidden/loaded from. */ fun getMusicDirs(storageManager: StorageManager): MusicDirs { val key = context.getString(R.string.set_key_music_dirs) @@ -214,9 +218,7 @@ class Settings(private val context: Context, private val callback: Callback? = n musicDirs.dirs.map(Directory::toDocumentUri).toSet()) putBoolean( context.getString(R.string.set_key_music_dirs_include), musicDirs.shouldInclude) - - // TODO: This is a stopgap measure before automatic rescanning, remove - commit() + apply() } } diff --git a/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt b/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt index 7444bb67b..747e9bfc0 100644 --- a/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt +++ b/app/src/main/java/org/oxycblt/auxio/ui/BottomSheetLayout.kt @@ -83,6 +83,8 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat * extendable. You have been warned. * * @author OxygenCobalt (With help from Umano and Hai Zhang) + * + * FIXME: Inconsistent padding across recreates */ class BottomSheetLayout @JvmOverloads diff --git a/app/src/main/res/values/settings.xml b/app/src/main/res/values/settings.xml index 232851842..bcc1af291 100644 --- a/app/src/main/res/values/settings.xml +++ b/app/src/main/res/values/settings.xml @@ -27,6 +27,7 @@ auxio_reindex auxio_music_dirs auxio_include_dirs + 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 cdd84ef19..cbe70408b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -132,7 +132,7 @@ Save playback state Save the current playback state now Reload music - Will restart app + May wipe playback state Music folders Manage where music should be loaded from Mode @@ -140,6 +140,8 @@ Music will not be loaded from the folders you add. Include Music will only be loaded from the folders you add. + Ignore MediaStore tags + Increases tag quality, but requires longer loading times No music found diff --git a/app/src/main/res/xml/prefs_main.xml b/app/src/main/res/xml/prefs_main.xml index dc1c9c0f2..091f13d99 100644 --- a/app/src/main/res/xml/prefs_main.xml +++ b/app/src/main/res/xml/prefs_main.xml @@ -173,5 +173,13 @@ app:summary="@string/set_dirs_desc" app:title="@string/set_dirs" /> + + \ No newline at end of file