playback: make sanitization runtime

Do not save the playback state when sanitizing.

After some thought, there is no situation where re-saving the playback
state is desirable. A previously saved state will be consistent with
a sanitized state, and there is no need to save when the service is
active. Thus, save on speed and reduce insane race conditions by just
sanitizing the current runtime state and not saving at all.
This commit is contained in:
OxygenCobalt 2022-07-07 11:58:25 -06:00
parent 83d6c529e2
commit e0a05ef486
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
5 changed files with 41 additions and 30 deletions

View file

@ -32,7 +32,7 @@ If you have the knowledge, you can also implement the feature yourself and creat
Its also recommended that you read about [Auxio's Architecture](../info/ARCHITECTURE.md) as well to make changes better and more efficient. Its also recommended that you read about [Auxio's Architecture](../info/ARCHITECTURE.md) as well to make changes better and more efficient.
## Translations ## Translations
I don't really see the use in weblate, so currently you should see the [Translations Megathread]](https://github.com/OxygenCobalt/Auxio/issues/3) to see how to propose translations. Go to Auxio's weblate project [here](https://hosted.weblate.org/engage/auxio/).
## Code Contributions ## Code Contributions
If you have knowledge of Android/Kotlin, feel free to to contribute to the project. If you have knowledge of Android/Kotlin, feel free to to contribute to the project.

View file

@ -29,6 +29,7 @@ import org.oxycblt.auxio.util.getSystemServiceSafe
import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logD
import org.oxycblt.auxio.util.newMainPendingIntent import org.oxycblt.auxio.util.newMainPendingIntent
/** The notification responsible for showing the indexer state. */
class IndexerNotification(private val context: Context) : class IndexerNotification(private val context: Context) :
NotificationCompat.Builder(context, CHANNEL_ID) { NotificationCompat.Builder(context, CHANNEL_ID) {
private val notificationManager = context.getSystemServiceSafe(NotificationManager::class) private val notificationManager = context.getSystemServiceSafe(NotificationManager::class)

View file

@ -28,7 +28,6 @@ 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
@ -118,16 +117,10 @@ class IndexerService : Service(), Indexer.Controller, Settings.Callback {
// Wipe possibly-invalidated album covers // Wipe possibly-invalidated album covers
imageLoader.memoryCache?.clear() imageLoader.memoryCache?.clear()
// PlaybackStateManager needs to be updated. We would do this in the // Clear invalid models from PlaybackStateManager. Shared objects
// playback module, but this service could be the only component capable // shouldn't be plugged into the callback system of other shared
// of doing this at a particular point. Note that while it's certain // objects, so we must update it here.
// that PlaybackStateManager is initialized by now, it's best to be safe playbackManager.sanitize(newLibrary)
// and check first.
if (playbackManager.isInitialized) {
playbackManager.sanitize(
PlaybackStateDatabase.getInstance(this@IndexerService),
newLibrary)
}
} }
musicStore.updateLibrary(newLibrary) musicStore.updateLibrary(newLibrary)

View file

@ -84,6 +84,7 @@ class MusicStore private constructor() {
} }
fun sanitize(song: Song) = songs.find { it.id == song.id } fun sanitize(song: Song) = songs.find { it.id == song.id }
fun sanitize(songs: List<Song>) = songs.mapNotNull { sanitize(it) }
fun sanitize(album: Album) = albums.find { it.id == album.id } fun sanitize(album: Album) = albums.find { it.id == album.id }
fun sanitize(artist: Artist) = artists.find { it.id == artist.id } fun sanitize(artist: Artist) = artists.find { it.id == artist.id }
fun sanitize(genre: Genre) = genres.find { it.id == genre.id } fun sanitize(genre: Genre) = genres.find { it.id == genre.id }

View file

@ -384,24 +384,40 @@ class PlaybackStateManager private constructor() {
withContext(Dispatchers.IO) { database.write(state) } withContext(Dispatchers.IO) { database.write(state) }
} }
suspend fun sanitize(database: PlaybackStateDatabase, newLibrary: MusicStore.Library) { @Synchronized
// Since we need to sanitize the state and re-save it for consistency, take the fun sanitize(newLibrary: MusicStore.Library) {
// easy way out and just write a new state and restore from it. Don't really care. if (!isInitialized) {
// TODO: Do we even need to save here? Doesn't seem like it's required for logD("Not initialized, no need to sanitize")
logD("Sanitizing state") return
val state = synchronized(this) { makeStateImpl() }
val sanitizedState =
withContext(Dispatchers.IO) {
database.write(state)
database.read(newLibrary)
}
synchronized(this) {
if (sanitizedState != null) {
applyStateImpl(sanitizedState)
}
} }
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)
} }
private fun makeStateImpl() = private fun makeStateImpl() =