Fix persistence bugs
Fix some edge cases with the persistent PlaybackState restoration.
This commit is contained in:
parent
6d809f4303
commit
212ffbf2c7
7 changed files with 117 additions and 62 deletions
|
@ -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,
|
||||
|
|
|
@ -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<PlaybackState>
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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],
|
||||
|
|
Loading…
Reference in a new issue