From 212ffbf2c7db7b4d34a963bea54b10c591fbbeb9 Mon Sep 17 00:00:00 2001 From: OxygenCobalt Date: Sun, 15 Nov 2020 20:07:40 -0700 Subject: [PATCH] Fix persistence bugs Fix some edge cases with the persistent PlaybackState restoration. --- .../oxycblt/auxio/database/PlaybackState.kt | 4 +- .../auxio/database/PlaybackStateDAO.kt | 4 - .../oxycblt/auxio/playback/PlaybackService.kt | 22 ++++ .../auxio/playback/PlaybackViewModel.kt | 15 ++- .../oxycblt/auxio/playback/state/LoopMode.kt | 16 +-- .../auxio/playback/state/PlaybackMode.kt | 4 +- .../playback/state/PlaybackStateManager.kt | 114 +++++++++++------- 7 files changed, 117 insertions(+), 62 deletions(-) diff --git a/app/src/main/java/org/oxycblt/auxio/database/PlaybackState.kt b/app/src/main/java/org/oxycblt/auxio/database/PlaybackState.kt index 5b5c4689a..dbeea08f3 100644 --- a/app/src/main/java/org/oxycblt/auxio/database/PlaybackState.kt +++ b/app/src/main/java/org/oxycblt/auxio/database/PlaybackState.kt @@ -7,10 +7,10 @@ import androidx.room.PrimaryKey @Entity(tableName = "playback_state_table") data class PlaybackState( @PrimaryKey(autoGenerate = true) - var id: Long = 0L, + var id: Long = Long.MIN_VALUE, @ColumnInfo(name = "song_id") - val songId: Long = -1L, + val songId: Long = Long.MIN_VALUE, @ColumnInfo(name = "position") val position: Long, diff --git a/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDAO.kt b/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDAO.kt index f128653cd..caa285ba5 100644 --- a/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDAO.kt +++ b/app/src/main/java/org/oxycblt/auxio/database/PlaybackStateDAO.kt @@ -3,16 +3,12 @@ package org.oxycblt.auxio.database import androidx.room.Dao import androidx.room.Insert import androidx.room.Query -import androidx.room.Update @Dao interface PlaybackStateDAO { @Insert fun insert(playbackState: PlaybackState) - @Update - fun update(playbackState: PlaybackState) - @Query("SELECT * FROM playback_state_table") fun getAll(): List diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt index dd199ba38..6ff3bfb6e 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt @@ -285,6 +285,12 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca serviceScope.launch { playbackManager.getStateFromDatabase(this@PlaybackService) } + + Log.d(this::class.simpleName, "notification restore") + + // FIXME: Current position just will not show on notification when state is restored + restorePlayer() + restoreNotification() } // --- OTHER FUNCTIONS --- @@ -302,6 +308,22 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateManager.Ca } } + private fun restoreNotification() { + playbackManager.song?.let { + uploadMetadataToSession(it) + notification.updateLoop(this) + notification.updateMode(this) + notification.updatePlaying(this) + notification.setMetadata(it, this) { + startForegroundOrNotify() + } + + return + } + + stopForegroundAndNotification() + } + private fun uploadMetadataToSession(song: Song) { val builder = MediaMetadataCompat.Builder() .putString(MediaMetadataCompat.METADATA_KEY_TITLE, song.name) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt index 8b69dfaa4..2596e5084 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackViewModel.kt @@ -79,12 +79,15 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { init { playbackManager.addCallback(this) - // If the PlaybackViewModel was cleared [signified by the PlaybackStateManager having a - // song and the fact that were are in the init function], then try to restore the playback - // state. - if (playbackManager.song != null) { + // If the PlaybackViewModel was cleared [Signified by PlaybackStateManager still being + // around & the fact that we are in the init function], then attempt to restore the + // viewmodel state. + if (playbackManager.isRestored) { restorePlaybackState() } else { + // If [PlaybackStateManger] was also cleared [Due to Auxio's process dying], then + // attempt to restore it [Albeit PlaybackService will need to do the job as it + // has a context while PlaybackViewModel doesn't]. playbackManager.needContextToRestoreState() } } @@ -166,7 +169,7 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { mutableListOf().apply { forEach { this.add(it) - } + } } ) ) @@ -323,7 +326,9 @@ class PlaybackViewModel : ViewModel(), PlaybackStateManager.Callback { mSong.value = playbackManager.song mPosition.value = playbackManager.position / 1000 + mParent.value = playbackManager.parent mQueue.value = playbackManager.queue + mMode.value = playbackManager.mode mUserQueue.value = playbackManager.userQueue mIndex.value = playbackManager.index mIsPlaying.value = playbackManager.isPlaying diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/LoopMode.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/LoopMode.kt index 31f9888c7..03204af70 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/LoopMode.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/LoopMode.kt @@ -3,14 +3,6 @@ package org.oxycblt.auxio.playback.state enum class LoopMode { NONE, ONCE, INFINITE; - fun toConstant(): Int { - return when (this) { - NONE -> CONSTANT_NONE - ONCE -> CONSTANT_ONCE - INFINITE -> CONSTANT_INFINITE - } - } - fun increment(): LoopMode { return when (this) { NONE -> ONCE @@ -19,6 +11,14 @@ enum class LoopMode { } } + fun toConstant(): Int { + return when (this) { + NONE -> CONSTANT_NONE + ONCE -> CONSTANT_ONCE + INFINITE -> CONSTANT_INFINITE + } + } + companion object { const val CONSTANT_NONE = 0xA050 const val CONSTANT_ONCE = 0xA051 diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackMode.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackMode.kt index c71155c65..4ad5a8145 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackMode.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackMode.kt @@ -19,8 +19,8 @@ enum class PlaybackMode { companion object { const val CONSTANT_IN_ARTIST = 0xA040 const val CONSTANT_IN_GENRE = 0xA041 - const val CONSTANT_IN_ALBUM = 0x4042 - const val CONSTANT_ALL_SONGS = 0x4043 + const val CONSTANT_IN_ALBUM = 0xA042 + const val CONSTANT_ALL_SONGS = 0xA043 fun fromConstant(constant: Int): PlaybackMode? { return when (constant) { diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index 9c54e3ff9..728ed9202 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -24,6 +24,7 @@ import kotlin.random.Random * * All instantiation should be done with [PlaybackStateManager.from()]. * @author OxygenCobalt + * FIXME: Add started variable instead of relying on mSong being null */ class PlaybackStateManager private constructor() { // Playback @@ -84,6 +85,7 @@ class PlaybackStateManager private constructor() { callbacks.forEach { it.onLoopUpdate(value) } } private var mIsInUserQueue = false + private var mIsRestored = false val song: Song? get() = mSong val parent: BaseModel? get() = mParent @@ -95,6 +97,7 @@ class PlaybackStateManager private constructor() { val isPlaying: Boolean get() = mIsPlaying val isShuffling: Boolean get() = mIsShuffling val loopMode: LoopMode get() = mLoopMode + val isRestored: Boolean get() = mIsRestored // --- CALLBACKS --- @@ -450,7 +453,7 @@ class PlaybackStateManager private constructor() { // --- PERSISTENCE FUNCTIONS --- // TODO: Persist queue edits? - // FIXME: Calling genShuffle without knowing the original queue edit from keepSong will cause issues. + // FIXME: Shuffling w/o knowing the original queue edit from keepSong will cause issues fun needContextToRestoreState() { callbacks.forEach { it.onNeedContextToRestoreState() } @@ -471,54 +474,25 @@ class PlaybackStateManager private constructor() { suspend fun getStateFromDatabase(context: Context) { Log.d(this::class.simpleName, "Getting state from DB.") - withContext(Dispatchers.IO) { + val states = withContext(Dispatchers.IO) { val database = PlaybackStateDatabase.getInstance(context) val states = database.playbackStateDAO.getAll() - if (states.isEmpty()) { - Log.d(this::class.simpleName, "Nothing here. Not restoring.") - return@withContext - } - - val state = states[0] - - Log.d(this::class.simpleName, "Old state found, $state") - database.playbackStateDAO.clear() - withContext(Dispatchers.Main) { - val musicStore = MusicStore.getInstance() - - mSong = musicStore.songs.find { it.id == state.songId } - mPosition = state.position - mParent = musicStore.parents.find { it.id == state.parentId } - mUserQueue = QueueConverter.fromString(state.userQueueIds) - mMode = PlaybackMode.fromConstant(state.mode) ?: PlaybackMode.ALL_SONGS - mLoopMode = LoopMode.fromConstant(state.loopMode) ?: LoopMode.NONE - mIsShuffling = state.isShuffling - mShuffleSeed = state.shuffleSeed - mIsInUserQueue = state.inUserQueue - - mQueue = when (mMode) { - PlaybackMode.IN_ARTIST -> orderSongsInArtist(mParent as Artist) - PlaybackMode.IN_ALBUM -> orderSongsInAlbum(mParent as Album) - PlaybackMode.IN_GENRE -> orderSongsInGenre(mParent as Genre) - PlaybackMode.ALL_SONGS -> musicStore.songs.toMutableList() - } - - if (mIsShuffling) { - Log.d(this::class.simpleName, "You stupid fucking retard. JUST FUNCTION.") - mQueue.shuffle(Random(mShuffleSeed)) - } - - mIndex = state.index - } + return@withContext states } - // Update PlaybackService outside of the main thread since its special for some reason - callbacks.forEach { - it.onSeekConfirm(mPosition) + mIsRestored = true + + if (states.isEmpty()) { + Log.d(this::class.simpleName, "Nothing here. Not restoring.") + return } + + Log.d(this::class.simpleName, "Old state found, ${states[0]}") + + unpackFromPlaybackState(states[0]) } private fun packToPlaybackState(): PlaybackState { @@ -548,6 +522,64 @@ class PlaybackStateManager private constructor() { ) } + private fun unpackFromPlaybackState(playbackState: PlaybackState) { + val musicStore = MusicStore.getInstance() + + // Turn the simplified information from PlaybackState into values that can be used + mSong = musicStore.songs.find { it.id == playbackState.songId } + mPosition = playbackState.position + mParent = musicStore.parents.find { it.id == playbackState.parentId } + mUserQueue = QueueConverter.fromString(playbackState.userQueueIds) + mMode = PlaybackMode.fromConstant(playbackState.mode) ?: PlaybackMode.ALL_SONGS + mLoopMode = LoopMode.fromConstant(playbackState.loopMode) ?: LoopMode.NONE + mIsShuffling = playbackState.isShuffling + mShuffleSeed = playbackState.shuffleSeed + mIsInUserQueue = playbackState.inUserQueue + + // If the parent was somehow dropped during saving, attempt to restore it. + mSong?.let { + if (mParent == null && mMode != PlaybackMode.ALL_SONGS) { + Log.d(this::class.simpleName, "Parent was corrupted while in mode $mMode. Attempting to restore.") + mParent = when (mMode) { + PlaybackMode.IN_ARTIST -> it.album.artist + PlaybackMode.IN_ALBUM -> it.album + else -> { + // If that fails, then just put the mode into all songs. + mMode = PlaybackMode.ALL_SONGS + null + } + } + } + } + + if (mIsShuffling) { + mQueue = when (mMode) { + PlaybackMode.IN_ARTIST -> (mParent as Artist).songs + PlaybackMode.IN_ALBUM -> (mParent as Album).songs + PlaybackMode.IN_GENRE -> (mParent as Genre).songs + PlaybackMode.ALL_SONGS -> musicStore.songs.toMutableList() + } + + mQueue.shuffle(Random(mShuffleSeed)) + + forceQueueUpdate() + } else { + mQueue = when (mMode) { + PlaybackMode.IN_ARTIST -> orderSongsInArtist(mParent as Artist) + PlaybackMode.IN_ALBUM -> orderSongsInAlbum(mParent as Album) + PlaybackMode.IN_GENRE -> orderSongsInGenre(mParent as Genre) + PlaybackMode.ALL_SONGS -> musicStore.songs.toMutableList() + } + } + + mIndex = playbackState.index + + callbacks.forEach { + it.onSeekConfirm(mPosition) + it.onModeUpdate(mMode) + } + } + /** * The interface for receiving updates from [PlaybackStateManager]. * Add the callback to [PlaybackStateManager] using [addCallback],