settings: add option to force-restore state
Add an option to restore the previous playback state. This allows me to avoid having to use force stop to restore a previous state.
This commit is contained in:
parent
a15bc79cc9
commit
1730a73eac
15 changed files with 84 additions and 23 deletions
|
@ -38,13 +38,14 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
|||
/**
|
||||
* The single [AppCompatActivity] for Auxio.
|
||||
*
|
||||
* TODO: Add error screens. This likely has to be an external activity, so it is blocked by
|
||||
* eliminating exitProcess from the app.
|
||||
* TODO: Add error screens.
|
||||
*
|
||||
* TODO: Custom language support
|
||||
*
|
||||
* TODO: Add multi-select
|
||||
*
|
||||
* TODO: Bug test runtime rescanning
|
||||
*
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
|
|
@ -315,7 +315,6 @@ class HomeFragment : ViewBindingFragment<FragmentHomeBinding>(), Toolbar.OnMenuI
|
|||
}
|
||||
|
||||
private fun handleIndexingState(binding: FragmentHomeBinding, indexing: Indexer.Indexing) {
|
||||
updateFab()
|
||||
binding.homeIndexingContainer.visibility = View.VISIBLE
|
||||
binding.homeIndexingProgress.visibility = View.VISIBLE
|
||||
binding.homeIndexingAction.visibility = View.INVISIBLE
|
||||
|
|
|
@ -83,6 +83,7 @@ class BitmapProvider(private val context: Context) {
|
|||
* Release this instance, canceling all image load jobs. This should be ran when the object is
|
||||
* no longer used.
|
||||
*/
|
||||
@Synchronized
|
||||
fun release() {
|
||||
currentRequest?.run { disposable.dispose() }
|
||||
currentRequest = null
|
||||
|
|
|
@ -119,10 +119,15 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback {
|
|||
imageLoader.memoryCache?.clear()
|
||||
|
||||
// PlaybackStateManager needs to be updated. We would do this in the
|
||||
// playback module, but this service could is the only component
|
||||
// capable of doing long-running background work as it stands.
|
||||
playbackManager.sanitize(
|
||||
PlaybackStateDatabase.getInstance(this@IndexerService), newLibrary)
|
||||
// playback module, but this service could be the only component capable
|
||||
// of doing this at a particular point. Note that while it's certain
|
||||
// that PlaybackStateManager is initialized by now, it's best to be safe
|
||||
// and check first.
|
||||
if (playbackManager.isInitialized) {
|
||||
playbackManager.sanitize(
|
||||
PlaybackStateDatabase.getInstance(this@IndexerService),
|
||||
newLibrary)
|
||||
}
|
||||
}
|
||||
|
||||
musicStore.updateLibrary(newLibrary)
|
||||
|
|
|
@ -30,6 +30,8 @@ import org.oxycblt.auxio.util.unlikelyToBeNull
|
|||
|
||||
/** [Item] variant that represents a music item. */
|
||||
sealed class Music : Item() {
|
||||
// TODO: Split ID into an ID derived from all fields and a persistent ID derived from stable fields
|
||||
|
||||
/** The raw name of this item. Null if unknown. */
|
||||
abstract val rawName: String?
|
||||
|
||||
|
|
|
@ -18,7 +18,6 @@
|
|||
package org.oxycblt.auxio.playback
|
||||
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
|
@ -276,13 +275,22 @@ class PlaybackViewModel(application: Application) :
|
|||
// --- SAVE/RESTORE FUNCTIONS ---
|
||||
|
||||
/** Force save the current [PlaybackStateManager] state to the database. */
|
||||
fun savePlaybackState(context: Context, onDone: () -> Unit) {
|
||||
fun savePlaybackState(onDone: () -> Unit) {
|
||||
viewModelScope.launch {
|
||||
playbackManager.saveState(PlaybackStateDatabase.getInstance(context))
|
||||
playbackManager.saveState(PlaybackStateDatabase.getInstance(application))
|
||||
onDone()
|
||||
}
|
||||
}
|
||||
|
||||
/** Force restore the last [PlaybackStateManager] saved state */
|
||||
fun restorePlaybackState(onDone: (Boolean) -> Unit) {
|
||||
viewModelScope.launch {
|
||||
val restored =
|
||||
playbackManager.restoreState(PlaybackStateDatabase.getInstance(application))
|
||||
onDone(restored)
|
||||
}
|
||||
}
|
||||
|
||||
/** An action delayed until the complete load of the music library. */
|
||||
sealed class DelayedAction {
|
||||
object RestoreState : DelayedAction()
|
||||
|
|
|
@ -38,7 +38,7 @@ import org.oxycblt.auxio.util.requireBackgroundThread
|
|||
* But that would needlessly bloat my app and has crippling bugs.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class PlaybackStateDatabase(context: Context) :
|
||||
class PlaybackStateDatabase private constructor(context: Context) :
|
||||
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
||||
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
|
|
|
@ -30,6 +30,7 @@ import org.oxycblt.auxio.music.Song
|
|||
import org.oxycblt.auxio.settings.Settings
|
||||
import org.oxycblt.auxio.util.logD
|
||||
import org.oxycblt.auxio.util.logW
|
||||
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||
|
||||
/**
|
||||
* Master class (and possible god object) for the playback state.
|
||||
|
@ -357,17 +358,20 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
// --- PERSISTENCE FUNCTIONS ---
|
||||
|
||||
/** Restore the state from the [database] */
|
||||
suspend fun restoreState(database: PlaybackStateDatabase) {
|
||||
val library = musicStore.library ?: return
|
||||
/** Restore the state from the [database]. Returns if a state was restored. */
|
||||
suspend fun restoreState(database: PlaybackStateDatabase): Boolean {
|
||||
val library = musicStore.library ?: return false
|
||||
val state = withContext(Dispatchers.IO) { database.read(library) }
|
||||
|
||||
synchronized(this) {
|
||||
if (state != null) {
|
||||
applyStateImpl(state)
|
||||
val exists = state != null
|
||||
if (exists) {
|
||||
applyStateImpl(unlikelyToBeNull(state))
|
||||
}
|
||||
|
||||
isInitialized = true
|
||||
|
||||
return exists
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -383,6 +387,8 @@ class PlaybackStateManager private constructor() {
|
|||
suspend fun sanitize(database: PlaybackStateDatabase, newLibrary: MusicStore.Library) {
|
||||
// Since we need to sanitize the state and re-save it for consistency, take the
|
||||
// easy way out and just write a new state and restore from it. Don't really care.
|
||||
// FIXME: This hack actually creates bugs if a user were to save the state at just
|
||||
// the right time, replace it with something that operates at runtime
|
||||
logD("Sanitizing state")
|
||||
val state = synchronized(this) { makeStateImpl() }
|
||||
|
||||
|
@ -394,7 +400,7 @@ class PlaybackStateManager private constructor() {
|
|||
|
||||
synchronized(this) {
|
||||
if (sanitizedState != null) {
|
||||
applyStateImpl(state)
|
||||
applyStateImpl(sanitizedState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,9 +50,9 @@ import org.oxycblt.auxio.util.systemBarInsetsCompat
|
|||
* The actual fragment containing the settings menu. Inherits [PreferenceFragmentCompat].
|
||||
* @author OxygenCobalt
|
||||
*
|
||||
* TODO: Add option to restore the previous state
|
||||
*
|
||||
* TODO: Add option to not restore state
|
||||
*
|
||||
* TODO: Disable playback state options when music is loading
|
||||
*/
|
||||
@Suppress("UNUSED")
|
||||
class SettingsListFragment : PreferenceFragmentCompat() {
|
||||
|
@ -120,10 +120,16 @@ class SettingsListFragment : PreferenceFragmentCompat() {
|
|||
override fun onPreferenceTreeClick(preference: Preference): Boolean {
|
||||
when (preference.key) {
|
||||
getString(R.string.set_key_save_state) -> {
|
||||
playbackModel.savePlaybackState(requireContext()) {
|
||||
context?.showToast(R.string.lbl_state_saved)
|
||||
}
|
||||
playbackModel.savePlaybackState { context?.showToast(R.string.lbl_state_saved) }
|
||||
}
|
||||
getString(R.string.set_key_restore_state) ->
|
||||
playbackModel.restorePlaybackState { restored ->
|
||||
if (restored) {
|
||||
context?.showToast(R.string.lbl_state_restored)
|
||||
} else {
|
||||
context?.showToast(R.string.err_did_not_restore)
|
||||
}
|
||||
}
|
||||
getString(R.string.set_key_reindex) -> {
|
||||
indexerModel.reindex()
|
||||
}
|
||||
|
|
|
@ -25,7 +25,10 @@ import com.google.android.material.appbar.MaterialToolbar
|
|||
import org.oxycblt.auxio.R
|
||||
import org.oxycblt.auxio.util.getDimenSizeSafe
|
||||
|
||||
/** [MaterialToolbar] that automatically fixes padding in order to align with the M3 specs. */
|
||||
/**
|
||||
* [MaterialToolbar] that automatically fixes padding in order to align with the M3 specs.
|
||||
* @author OxygenCobalt
|
||||
*/
|
||||
class M3Toolbar : MaterialToolbar {
|
||||
constructor(context: Context) : super(context)
|
||||
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
android:id="@+id/home_indexing_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="invisible"
|
||||
android:layout_margin="@dimen/spacing_medium">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
<string name="set_key_repeat_pause" translatable="false">KEY_LOOP_PAUSE</string>
|
||||
|
||||
<string name="set_key_save_state" translatable="false">auxio_save_state</string>
|
||||
<string name="set_key_restore_state" translatable="false">auxio_restore_state</string>
|
||||
<string name="set_key_reindex" translatable="false">auxio_reindex</string>
|
||||
<string name="set_key_music_dirs" translatable="false">auxio_music_dirs</string>
|
||||
<string name="set_key_music_dirs_include" translatable="false">auxio_include_dirs</string>
|
||||
|
|
|
@ -53,7 +53,10 @@
|
|||
<string name="lbl_bitrate">Bit rate</string>
|
||||
<string name="lbl_sample_rate">Sample rate</string>
|
||||
|
||||
<!-- Referring to playback state -->
|
||||
<string name="lbl_state_saved">State saved</string>
|
||||
<!-- Referring to playback state -->
|
||||
<string name="lbl_state_restored">State restored</string>
|
||||
|
||||
<!-- Limit to 10 characters -->
|
||||
<string name="lbl_shuffle_shortcut_short">Shuffle</string>
|
||||
|
@ -131,6 +134,8 @@
|
|||
<string name="set_content">Content</string>
|
||||
<string name="set_save">Save playback state</string>
|
||||
<string name="set_save_desc">Save the current playback state now</string>
|
||||
<string name="set_restore">Restore playback state</string>
|
||||
<string name="set_restore_desc">Restore the previously saved playback state (if any)</string>
|
||||
<string name="set_reindex">Reload music</string>
|
||||
<string name="set_reindex_desc">May wipe playback state</string>
|
||||
<string name="set_dirs">Music folders</string>
|
||||
|
@ -151,6 +156,8 @@
|
|||
<string name="err_no_dirs">No Folders</string>
|
||||
<string name="err_bad_dir">This folder is not supported</string>
|
||||
<string name="err_too_small">Auxio does not support this window size</string>
|
||||
<!-- Referring to playback state -->
|
||||
<string name="err_did_not_restore">No state could be restored</string>
|
||||
|
||||
<!-- Hint Namespace | EditText Hints -->
|
||||
<string name="hint_search_library">Search your library…</string>
|
||||
|
@ -190,11 +197,17 @@
|
|||
<string name="def_widget_artist">Artist Name</string>
|
||||
|
||||
<!-- Codec Namespace | Format names -->
|
||||
<!-- "Audio" should be translated -->
|
||||
<string name="cdc_mp3">MPEG-1 Audio</string>
|
||||
<!-- "Audio" should be translated -->
|
||||
<string name="cdc_mp4">MPEG-4 Audio</string>
|
||||
<!-- "Audio" should be translated -->
|
||||
<string name="cdc_ogg">Ogg Audio</string>
|
||||
<!-- "Audio" should be translated -->
|
||||
<string name="cdc_mka">Matroska Audio</string>
|
||||
<!-- "Advanced Audio Coding" can optionally be translated -->
|
||||
<string name="cdc_aac">Advanced Audio Coding (AAC)</string>
|
||||
<!-- "Free Lossless Audio Codec" can optionally be translated -->
|
||||
<string name="cdc_flac">Free Lossless Audio Codec (FLAC)</string>
|
||||
|
||||
<!-- Color Label namespace | Accent names -->
|
||||
|
@ -219,9 +232,13 @@
|
|||
<!-- Format Namespace | Value formatting/plurals -->
|
||||
<string name="fmt_disc_no">Disc %d</string>
|
||||
|
||||
<!-- Use your native country's abbreviation for decibel units. -->
|
||||
<string name="fmt_db_pos">+%.1f dB</string>
|
||||
<!-- Use your native country's abbreviation for decibel units. -->
|
||||
<string name="fmt_db_neg">-%.1f dB</string>
|
||||
<!-- Use your native country's abbreviation for bitrate units. -->
|
||||
<string name="fmt_bitrate">%d kbps</string>
|
||||
<!-- Use your native country's abbreviation for hertz units. -->
|
||||
<string name="fmt_sample_rate">%d Hz</string>
|
||||
|
||||
<string name="fmt_indexing">Loading your music library… (%1$d/%2$d)</string>
|
||||
|
|
|
@ -161,6 +161,12 @@
|
|||
app:summary="@string/set_save_desc"
|
||||
app:title="@string/set_save" />
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="@string/set_key_restore_state"
|
||||
app:summary="@string/set_restore_desc"
|
||||
app:title="@string/set_restore" />
|
||||
|
||||
<Preference
|
||||
app:iconSpaceReserved="false"
|
||||
app:key="@string/set_key_reindex"
|
||||
|
|
|
@ -43,6 +43,11 @@ This is expected since reading from the audio database takes awhile, especially
|
|||
This is a current limitation with the music loader. To remedy this, go to Settings -> Reload music whenever new songs are added.
|
||||
I hope to make the app rescan music on the fly eventually.
|
||||
|
||||
#### Why does playback pause whenever music is reloaded?
|
||||
Whenever the music library signifigantly changes, updating the player's data while it is still playing may result in
|
||||
unwanted bugs or unexpected music playing. To safeguard against this, Auxio will pause whenever it reloads a new
|
||||
music library.
|
||||
|
||||
#### There should be one artist, but instead I get a bunch of "Artist & Collaborator" artists!
|
||||
This likely means your tags are wrong. By default, Auxio will use the "album artist" tag for
|
||||
grouping if present, falling back to the "artist" tag otherwise. If your music does not have
|
||||
|
|
Loading…
Reference in a new issue