playback: save state during sanitize

Save, but do not read the playback state when sanitizing.

Turns out there is an edge-case where we want to save the state. Still
keep the runtime sanitization, as that greatly reduces the time it
takes to rescan the library.
This commit is contained in:
OxygenCobalt 2022-07-07 15:07:23 -06:00
parent 41ff92a587
commit a217bde713
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 69 additions and 57 deletions

View file

@ -28,6 +28,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.oxycblt.auxio.IntegerTable import org.oxycblt.auxio.IntegerTable
import org.oxycblt.auxio.R import org.oxycblt.auxio.R
import org.oxycblt.auxio.playback.state.PlaybackStateDatabase
import org.oxycblt.auxio.playback.state.PlaybackStateManager import org.oxycblt.auxio.playback.state.PlaybackStateManager
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
@ -117,10 +118,9 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback {
// Wipe possibly-invalidated album covers // Wipe possibly-invalidated album covers
imageLoader.memoryCache?.clear() imageLoader.memoryCache?.clear()
// Clear invalid models from PlaybackStateManager. Shared objects // Clear invalid models from PlaybackStateManager.
// shouldn't be plugged into the callback system of other shared playbackManager.sanitize(
// objects, so we must update it here. PlaybackStateDatabase.getInstance(this@IndexerService), newLibrary)
playbackManager.sanitize(newLibrary)
} }
musicStore.updateLibrary(newLibrary) musicStore.updateLibrary(newLibrary)

View file

@ -83,10 +83,15 @@ class MusicStore private constructor() {
songs.find { it.path.name == displayName } songs.find { it.path.name == displayName }
} }
/** Sanitize an old item to find the corresponding item in a new library. */
fun sanitize(song: Song) = songs.find { it.id == song.id } fun sanitize(song: Song) = songs.find { it.id == song.id }
/** Sanitize an old item to find the corresponding item in a new library. */
fun sanitize(songs: List<Song>) = songs.mapNotNull { sanitize(it) } fun sanitize(songs: List<Song>) = songs.mapNotNull { sanitize(it) }
/** Sanitize an old item to find the corresponding item in a new library. */
fun sanitize(album: Album) = albums.find { it.id == album.id } fun sanitize(album: Album) = albums.find { it.id == album.id }
/** Sanitize an old item to find the corresponding item in a new library. */
fun sanitize(artist: Artist) = artists.find { it.id == artist.id } fun sanitize(artist: Artist) = artists.find { it.id == artist.id }
/** Sanitize an old item to find the corresponding item in a new library. */
fun sanitize(genre: Genre) = genres.find { it.id == genre.id } fun sanitize(genre: Genre) = genres.find { it.id == genre.id }
} }

View file

@ -107,6 +107,7 @@ class PlaybackStateDatabase private constructor(context: Context) :
val rawState = readRawState() ?: return null val rawState = readRawState() ?: return null
val queue = readQueue(library) val queue = readQueue(library)
// Correct the index to match up with a possibly shortened queue (file removals/changes)
var actualIndex = rawState.index var actualIndex = rawState.index
while (queue.getOrNull(actualIndex)?.id != rawState.songId && actualIndex > -1) { while (queue.getOrNull(actualIndex)?.id != rawState.songId && actualIndex > -1) {
actualIndex-- actualIndex--

View file

@ -30,7 +30,6 @@ import org.oxycblt.auxio.music.Song
import org.oxycblt.auxio.settings.Settings import org.oxycblt.auxio.settings.Settings
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.logW import org.oxycblt.auxio.util.logW
import org.oxycblt.auxio.util.unlikelyToBeNull
/** /**
* Master class (and possible god object) for the playback state. * Master class (and possible god object) for the playback state.
@ -364,10 +363,27 @@ class PlaybackStateManager private constructor() {
val state = withContext(Dispatchers.IO) { database.read(library) } val state = withContext(Dispatchers.IO) { database.read(library) }
synchronized(this) { synchronized(this) {
val exists = state != null val exists =
if (exists) { if (state != null) {
applyStateImpl(unlikelyToBeNull(state)) // Continuing playback while also possibly doing drastic state updates is
} // a bad idea, so pause.
isPlaying = false
index = state.index
parent = state.parent
_queue = state.queue.toMutableList()
repeatMode = state.repeatMode
isShuffled = state.isShuffled
notifyNewPlayback()
seekTo(state.positionMs)
notifyRepeatModeChanged()
notifyShuffledChanged()
true
} else {
false
}
isInitialized = true isInitialized = true
@ -384,40 +400,47 @@ class PlaybackStateManager private constructor() {
withContext(Dispatchers.IO) { database.write(state) } withContext(Dispatchers.IO) { database.write(state) }
} }
@Synchronized /** Sanitize the state with [newLibrary]. Writes the state to [database] */
fun sanitize(newLibrary: MusicStore.Library) { suspend fun sanitize(database: PlaybackStateDatabase, newLibrary: MusicStore.Library) {
if (!isInitialized) { val state =
logD("Not initialized, no need to sanitize") synchronized(this) {
return if (!isInitialized) {
} logD("Not initialized, no need to sanitize")
return
logD("Sanitizing state")
val oldSongId = song?.id
val oldPosition = positionMs
parent =
parent?.let {
when (it) {
is Album -> newLibrary.sanitize(it)
is Artist -> newLibrary.sanitize(it)
is Genre -> newLibrary.sanitize(it)
} }
logD("Sanitizing state")
val oldSongId = song?.id
val oldPosition = positionMs
parent =
parent?.let {
when (it) {
is Album -> newLibrary.sanitize(it)
is Artist -> newLibrary.sanitize(it)
is Genre -> newLibrary.sanitize(it)
}
}
_queue = newLibrary.sanitize(_queue).toMutableList()
while (song?.id != oldSongId && index > -1) {
index--
}
// Continuing playback while also possibly doing drastic state updates is
// a bad idea, so pause.
isPlaying = false
notifyNewPlayback()
// Controller may have reloaded the media item, re-seek to the previous position
seekTo(oldPosition)
makeStateImpl()
} }
_queue = newLibrary.sanitize(_queue).toMutableList() withContext(Dispatchers.IO) { database.write(state) }
while (song?.id != oldSongId && index > -1) {
index--
}
// Continuing playback while also possibly doing drastic state updates is
// a bad idea, so pause.
isPlaying = false
notifyNewPlayback()
// Controller may have reloaded the media item, re-seek to the previous position
seekTo(oldPosition)
} }
private fun makeStateImpl() = private fun makeStateImpl() =
@ -429,23 +452,6 @@ class PlaybackStateManager private constructor() {
isShuffled = isShuffled, isShuffled = isShuffled,
repeatMode = repeatMode) repeatMode = repeatMode)
private fun applyStateImpl(state: PlaybackStateDatabase.SavedState) {
// Continuing playback while also possibly doing drastic state updates is
// a bad idea, so pause.
isPlaying = false
index = state.index
parent = state.parent
_queue = state.queue.toMutableList()
repeatMode = state.repeatMode
isShuffled = state.isShuffled
notifyNewPlayback()
seekTo(state.positionMs)
notifyRepeatModeChanged()
notifyShuffledChanged()
}
// --- CALLBACKS --- // --- CALLBACKS ---
private fun notifyIndexMoved() { private fun notifyIndexMoved() {