playback: re-add state persistence

Re-add state persistence with support for the new queue.

This should finally finish the new queue system.
This commit is contained in:
Alexander Capehart 2023-01-09 13:53:37 -07:00
parent 9bd78bc855
commit 692839e8fe
No known key found for this signature in database
GPG key ID: 37DBE3621FE9AD47
4 changed files with 210 additions and 170 deletions

View file

@ -77,6 +77,7 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), EditableListL
override fun onDestroyBinding(binding: FragmentQueueBinding) { override fun onDestroyBinding(binding: FragmentQueueBinding) {
super.onDestroyBinding(binding) super.onDestroyBinding(binding)
touchHelper = null
binding.queueRecycler.adapter = null binding.queueRecycler.adapter = null
} }

View file

@ -22,6 +22,7 @@ import android.content.Context
import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper import android.database.sqlite.SQLiteOpenHelper
import android.provider.BaseColumns import android.provider.BaseColumns
import androidx.core.database.getIntOrNull
import androidx.core.database.sqlite.transaction import androidx.core.database.sqlite.transaction
import org.oxycblt.auxio.music.* import org.oxycblt.auxio.music.*
import org.oxycblt.auxio.music.library.Library import org.oxycblt.auxio.music.library.Library
@ -40,17 +41,22 @@ class PlaybackStateDatabase private constructor(context: Context) :
// of the non-queue parts of the state, such as the playback position. // of the non-queue parts of the state, such as the playback position.
db.createTable(TABLE_STATE) { db.createTable(TABLE_STATE) {
append("${BaseColumns._ID} INTEGER PRIMARY KEY,") append("${BaseColumns._ID} INTEGER PRIMARY KEY,")
append("${StateColumns.INDEX} INTEGER NOT NULL,") append("${PlaybackStateColumns.INDEX} INTEGER NOT NULL,")
append("${StateColumns.POSITION} LONG NOT NULL,") append("${PlaybackStateColumns.POSITION} LONG NOT NULL,")
append("${StateColumns.REPEAT_MODE} INTEGER NOT NULL,") append("${PlaybackStateColumns.REPEAT_MODE} INTEGER NOT NULL,")
append("${StateColumns.IS_SHUFFLED} BOOLEAN NOT NULL,") append("${PlaybackStateColumns.SONG_UID} STRING,")
append("${StateColumns.SONG_UID} STRING,") append("${PlaybackStateColumns.PARENT_UID} STRING")
append("${StateColumns.PARENT_UID} STRING")
} }
db.createTable(TABLE_QUEUE) { db.createTable(TABLE_QUEUE_HEAP) {
append("${BaseColumns._ID} INTEGER PRIMARY KEY,") append("${BaseColumns._ID} INTEGER PRIMARY KEY,")
append("${QueueColumns.SONG_UID} STRING NOT NULL") append("${QueueHeapColumns.SONG_UID} STRING NOT NULL")
}
db.createTable(TABLE_QUEUE_MAPPINGS) {
append("${BaseColumns._ID} INTEGER PRIMARY KEY,")
append("${QueueMappingColumns.ORDERED_INDEX} INT NOT NULL,")
append("${QueueMappingColumns.SHUFFLED_INDEX} INT")
} }
} }
@ -61,7 +67,8 @@ class PlaybackStateDatabase private constructor(context: Context) :
logD("Nuking database") logD("Nuking database")
db.apply { db.apply {
execSQL("DROP TABLE IF EXISTS $TABLE_STATE") execSQL("DROP TABLE IF EXISTS $TABLE_STATE")
execSQL("DROP TABLE IF EXISTS $TABLE_QUEUE") execSQL("DROP TABLE IF EXISTS $TABLE_QUEUE_HEAP")
execSQL("DROP TABLE IF EXISTS $TABLE_QUEUE_MAPPINGS")
onCreate(this) onCreate(this)
} }
} }
@ -77,63 +84,78 @@ class PlaybackStateDatabase private constructor(context: Context) :
requireBackgroundThread() requireBackgroundThread()
// Read the saved state and queue. If the state is non-null, that must imply an // Read the saved state and queue. If the state is non-null, that must imply an
// existent, albeit possibly empty, queue. // existent, albeit possibly empty, queue.
val rawState = readRawState() ?: return null val rawState = readRawPlaybackState() ?: return null
val queue = readQueue(library) val rawQueueState = readRawQueueState(library)
// Correct the index to match up with a queue that has possibly been shortened due to
// song removals.
var actualIndex = rawState.index
while (queue.getOrNull(actualIndex)?.uid != rawState.songUid && actualIndex > -1) {
actualIndex--
}
// Restore parent item from the music library. If this fails, then the playback mode // Restore parent item from the music library. If this fails, then the playback mode
// reverts to "All Songs", which is considered okay. // reverts to "All Songs", which is considered okay.
val parent = rawState.parentUid?.let { library.find<MusicParent>(it) } val parent = rawState.parentUid?.let { library.find<MusicParent>(it) }
return SavedState( return SavedState(
index = actualIndex,
parent = parent, parent = parent,
queue = queue, queueState =
Queue.SavedState(
heap = rawQueueState.heap,
orderedMapping = rawQueueState.orderedMapping,
shuffledMapping = rawQueueState.shuffledMapping,
index = rawState.index,
songUid = rawState.songUid),
positionMs = rawState.positionMs, positionMs = rawState.positionMs,
repeatMode = rawState.repeatMode, repeatMode = rawState.repeatMode)
isShuffled = rawState.isShuffled)
} }
private fun readRawState() = private fun readRawPlaybackState() =
readableDatabase.queryAll(TABLE_STATE) { cursor -> readableDatabase.queryAll(TABLE_STATE) { cursor ->
if (!cursor.moveToFirst()) { if (!cursor.moveToFirst()) {
// Empty, nothing to do. // Empty, nothing to do.
return@queryAll null return@queryAll null
} }
val indexIndex = cursor.getColumnIndexOrThrow(StateColumns.INDEX) val indexIndex = cursor.getColumnIndexOrThrow(PlaybackStateColumns.INDEX)
val posIndex = cursor.getColumnIndexOrThrow(StateColumns.POSITION) val posIndex = cursor.getColumnIndexOrThrow(PlaybackStateColumns.POSITION)
val repeatModeIndex = cursor.getColumnIndexOrThrow(StateColumns.REPEAT_MODE) val repeatModeIndex = cursor.getColumnIndexOrThrow(PlaybackStateColumns.REPEAT_MODE)
val shuffleIndex = cursor.getColumnIndexOrThrow(StateColumns.IS_SHUFFLED) val songUidIndex = cursor.getColumnIndexOrThrow(PlaybackStateColumns.SONG_UID)
val songUidIndex = cursor.getColumnIndexOrThrow(StateColumns.SONG_UID) val parentUidIndex = cursor.getColumnIndexOrThrow(PlaybackStateColumns.PARENT_UID)
val parentUidIndex = cursor.getColumnIndexOrThrow(StateColumns.PARENT_UID) RawPlaybackState(
RawState(
index = cursor.getInt(indexIndex), index = cursor.getInt(indexIndex),
positionMs = cursor.getLong(posIndex), positionMs = cursor.getLong(posIndex),
repeatMode = RepeatMode.fromIntCode(cursor.getInt(repeatModeIndex)) repeatMode = RepeatMode.fromIntCode(cursor.getInt(repeatModeIndex))
?: RepeatMode.NONE, ?: RepeatMode.NONE,
isShuffled = cursor.getInt(shuffleIndex) == 1,
songUid = Music.UID.fromString(cursor.getString(songUidIndex)) songUid = Music.UID.fromString(cursor.getString(songUidIndex))
?: return@queryAll null, ?: return@queryAll null,
parentUid = cursor.getString(parentUidIndex)?.let(Music.UID::fromString)) parentUid = cursor.getString(parentUidIndex)?.let(Music.UID::fromString))
} }
private fun readQueue(library: Library): List<Song> { private fun readRawQueueState(library: Library): RawQueueState {
val queue = mutableListOf<Song>() val heap = mutableListOf<Song?>()
readableDatabase.queryAll(TABLE_QUEUE) { cursor -> readableDatabase.queryAll(TABLE_QUEUE_HEAP) { cursor ->
val songIndex = cursor.getColumnIndexOrThrow(QueueColumns.SONG_UID) if (cursor.count == 0) {
// Empty, nothing to do.
return@queryAll
}
val songIndex = cursor.getColumnIndexOrThrow(QueueHeapColumns.SONG_UID)
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
val uid = Music.UID.fromString(cursor.getString(songIndex)) ?: continue heap.add(Music.UID.fromString(cursor.getString(songIndex))?.let(library::find))
val song = library.find<Song>(uid) ?: continue }
queue.add(song) }
logD("Successfully read queue of ${heap.size} songs")
val orderedMapping = mutableListOf<Int?>()
val shuffledMapping = mutableListOf<Int?>()
readableDatabase.queryAll(TABLE_QUEUE_MAPPINGS) { cursor ->
if (cursor.count == 0) {
// Empty, nothing to do.
return@queryAll
}
val orderedIndex = cursor.getColumnIndexOrThrow(QueueMappingColumns.ORDERED_INDEX)
val shuffledIndex = cursor.getColumnIndexOrThrow(QueueMappingColumns.SHUFFLED_INDEX)
while (cursor.moveToNext()) {
orderedMapping.add(cursor.getInt(orderedIndex))
cursor.getIntOrNull(shuffledIndex)?.let(shuffledMapping::add)
} }
} }
logD("Successfully read queue of ${queue.size} songs") return RawQueueState(heap, orderedMapping.filterNotNull(), shuffledMapping.filterNotNull())
return queue
} }
/** /**
@ -144,40 +166,43 @@ class PlaybackStateDatabase private constructor(context: Context) :
requireBackgroundThread() requireBackgroundThread()
// Only bother saving a state if a song is actively playing from one. // Only bother saving a state if a song is actively playing from one.
// This is not the case with a null state or a state with an out-of-bounds index. // This is not the case with a null state or a state with an out-of-bounds index.
if (state != null && state.index in state.queue.indices) { if (state != null) {
// Transform saved state into raw state, which can then be written to the database. // Transform saved state into raw state, which can then be written to the database.
val rawState = val rawPlaybackState =
RawState( RawPlaybackState(
index = state.index, index = state.queueState.index,
positionMs = state.positionMs, positionMs = state.positionMs,
repeatMode = state.repeatMode, repeatMode = state.repeatMode,
isShuffled = state.isShuffled, songUid = state.queueState.songUid,
songUid = state.queue[state.index].uid,
parentUid = state.parent?.uid) parentUid = state.parent?.uid)
writeRawState(rawState) writeRawPlaybackState(rawPlaybackState)
writeQueue(state.queue) val rawQueueState =
RawQueueState(
heap = state.queueState.heap,
orderedMapping = state.queueState.orderedMapping,
shuffledMapping = state.queueState.shuffledMapping)
writeRawQueueState(rawQueueState)
logD("Wrote state") logD("Wrote state")
} else { } else {
writeRawState(null) writeRawPlaybackState(null)
writeQueue(null) writeRawQueueState(null)
logD("Cleared state") logD("Cleared state")
} }
} }
private fun writeRawState(rawState: RawState?) { private fun writeRawPlaybackState(rawPlaybackState: RawPlaybackState?) {
writableDatabase.transaction { writableDatabase.transaction {
delete(TABLE_STATE, null, null) delete(TABLE_STATE, null, null)
if (rawState != null) { if (rawPlaybackState != null) {
val stateData = val stateData =
ContentValues(7).apply { ContentValues(7).apply {
put(BaseColumns._ID, 0) put(BaseColumns._ID, 0)
put(StateColumns.SONG_UID, rawState.songUid.toString()) put(PlaybackStateColumns.SONG_UID, rawPlaybackState.songUid.toString())
put(StateColumns.POSITION, rawState.positionMs) put(PlaybackStateColumns.POSITION, rawPlaybackState.positionMs)
put(StateColumns.PARENT_UID, rawState.parentUid?.toString()) put(PlaybackStateColumns.PARENT_UID, rawPlaybackState.parentUid?.toString())
put(StateColumns.INDEX, rawState.index) put(PlaybackStateColumns.INDEX, rawPlaybackState.index)
put(StateColumns.IS_SHUFFLED, rawState.isShuffled) put(PlaybackStateColumns.REPEAT_MODE, rawPlaybackState.repeatMode.intCode)
put(StateColumns.REPEAT_MODE, rawState.repeatMode.intCode)
} }
insert(TABLE_STATE, null, stateData) insert(TABLE_STATE, null, stateData)
@ -185,47 +210,54 @@ class PlaybackStateDatabase private constructor(context: Context) :
} }
} }
private fun writeQueue(queue: List<Song>?) { private fun writeRawQueueState(rawQueueState: RawQueueState?) {
writableDatabase.writeList(queue ?: listOf(), TABLE_QUEUE) { i, song -> writableDatabase.writeList(rawQueueState?.heap ?: listOf(), TABLE_QUEUE_HEAP) { i, song ->
ContentValues(2).apply { ContentValues(2).apply {
put(BaseColumns._ID, i) put(BaseColumns._ID, i)
put(QueueColumns.SONG_UID, song.uid.toString()) put(QueueHeapColumns.SONG_UID, unlikelyToBeNull(song).uid.toString())
}
}
val combinedMapping =
rawQueueState?.run {
if (shuffledMapping.isNotEmpty()) {
orderedMapping.zip(shuffledMapping)
} else {
orderedMapping.map { Pair(it, null) }
}
}
writableDatabase.writeList(combinedMapping ?: listOf(), TABLE_QUEUE_MAPPINGS) { i, pair ->
ContentValues(3).apply {
put(BaseColumns._ID, i)
put(QueueMappingColumns.ORDERED_INDEX, pair.first)
put(QueueMappingColumns.SHUFFLED_INDEX, pair.second)
} }
} }
} }
/** /**
* A condensed representation of the playback state that can be persisted. * A condensed representation of the playback state that can be persisted.
* @param index The position of the currently playing item in the queue. Can be -1 if the * @param parent The [MusicParent] item currently being played from.
* persisted index no longer exists. * @param queueState The [Queue.SavedState]
* @param queue The [Song] queue.
* @param parent The [MusicParent] item currently being played from
* @param positionMs The current position in the currently played song, in ms * @param positionMs The current position in the currently played song, in ms
* @param repeatMode The current [RepeatMode]. * @param repeatMode The current [RepeatMode].
* @param isShuffled Whether the queue is shuffled or not.
*/ */
data class SavedState( data class SavedState(
val index: Int,
val queue: List<Song>,
val parent: MusicParent?, val parent: MusicParent?,
val queueState: Queue.SavedState,
val positionMs: Long, val positionMs: Long,
val repeatMode: RepeatMode, val repeatMode: RepeatMode,
val isShuffled: Boolean
) )
/** /** A lower-level form of [SavedState] that contains individual field-based information. */
* A lower-level form of [SavedState] that contains additional information to create a more private data class RawPlaybackState(
* reliable restoration process. /** @see Queue.SavedState.index */
*/
private data class RawState(
/** @see SavedState.index */
val index: Int, val index: Int,
/** @see SavedState.positionMs */ /** @see SavedState.positionMs */
val positionMs: Long, val positionMs: Long,
/** @see SavedState.repeatMode */ /** @see SavedState.repeatMode */
val repeatMode: RepeatMode, val repeatMode: RepeatMode,
/** @see SavedState.isShuffled */
val isShuffled: Boolean,
/** /**
* The [Music.UID] of the [Song] that was originally in the queue at [index]. This can be * The [Music.UID] of the [Song] that was originally in the queue at [index]. This can be
* used to restore the currently playing item in the queue if the index mapping changed. * used to restore the currently playing item in the queue if the index mapping changed.
@ -235,33 +267,50 @@ class PlaybackStateDatabase private constructor(context: Context) :
val parentUid: Music.UID? val parentUid: Music.UID?
) )
/** A lower-level form of [Queue.SavedState] that contains heap and mapping information. */
private data class RawQueueState(
/** @see Queue.SavedState.heap */
val heap: List<Song?>,
/** @see Queue.SavedState.orderedMapping */
val orderedMapping: List<Int>,
/** @see Queue.SavedState.shuffledMapping */
val shuffledMapping: List<Int>
)
/** Defines the columns used in the playback state table. */ /** Defines the columns used in the playback state table. */
private object StateColumns { private object PlaybackStateColumns {
/** @see RawState.index */ /** @see RawPlaybackState.index */
const val INDEX = "queue_index" const val INDEX = "queue_index"
/** @see RawState.positionMs */ /** @see RawPlaybackState.positionMs */
const val POSITION = "position" const val POSITION = "position"
/** @see RawState.isShuffled */ /** @see RawPlaybackState.repeatMode */
const val IS_SHUFFLED = "is_shuffling"
/** @see RawState.repeatMode */
const val REPEAT_MODE = "repeat_mode" const val REPEAT_MODE = "repeat_mode"
/** @see RawState.songUid */ /** @see RawPlaybackState.songUid */
const val SONG_UID = "song_uid" const val SONG_UID = "song_uid"
/** @see RawState.parentUid */ /** @see RawPlaybackState.parentUid */
const val PARENT_UID = "parent" const val PARENT_UID = "parent"
} }
/** Defines the columns used in the queue table. */ /** Defines the columns used in the queue heap table. */
private object QueueColumns { private object QueueHeapColumns {
/** @see Music.UID */ /** @see Music.UID */
const val SONG_UID = "song_uid" const val SONG_UID = "song_uid"
} }
/** Defines the columns used in the queue mapping table. */
private object QueueMappingColumns {
/** @see Queue.SavedState.orderedMapping */
const val ORDERED_INDEX = "ordered_index"
/** @see Queue.SavedState.shuffledMapping */
const val SHUFFLED_INDEX = "shuffled_index"
}
companion object { companion object {
private const val DB_NAME = "auxio_playback_state.db" private const val DB_NAME = "auxio_playback_state.db"
private const val DB_VERSION = 8 private const val DB_VERSION = 9
private const val TABLE_STATE = "playback_state" private const val TABLE_STATE = "playback_state"
private const val TABLE_QUEUE = "queue" private const val TABLE_QUEUE_HEAP = "queue_heap"
private const val TABLE_QUEUE_MAPPINGS = "queue_mapping"
@Volatile private var INSTANCE: PlaybackStateDatabase? = null @Volatile private var INSTANCE: PlaybackStateDatabase? = null

View file

@ -155,8 +155,7 @@ class PlaybackStateManager private constructor() {
/** /**
* Start new playback. * Start new playback.
* @param song A particular [Song] to play, or null to play the first [Song] in the new queue. * @param song A particular [Song] to play, or null to play the first [Song] in the new queue.
* @param parent The [MusicParent] to play from, or null if to play from the entire * @param parent The [MusicParent] to play from, or null if to play from the entire [Library].
* [MusicStore.Library].
* @param sort [Sort] to initially sort an ordered queue with. * @param sort [Sort] to initially sort an ordered queue with.
* @param shuffled Whether to shuffle or not. * @param shuffled Whether to shuffle or not.
*/ */
@ -390,7 +389,7 @@ class PlaybackStateManager private constructor() {
/** /**
* Restore the previously saved state (if any) and apply it to the playback state. * Restore the previously saved state (if any) and apply it to the playback state.
* @param database The [PlaybackStateDatabase] to load from. * @param database The [PlaybackStateDatabase] to load from.
* @param force Whether to force a restore regardless of the current state. * @param force Whether to do a restore regardless of any prior playback state.
* @return If the state was restored, false otherwise. * @return If the state was restored, false otherwise.
*/ */
suspend fun restoreState(database: PlaybackStateDatabase, force: Boolean): Boolean { suspend fun restoreState(database: PlaybackStateDatabase, force: Boolean): Boolean {
@ -399,49 +398,37 @@ class PlaybackStateManager private constructor() {
return false return false
} }
// TODO: Re-implement with new queue val library = musicStore.library ?: return false
return false val internalPlayer = internalPlayer ?: return false
val state =
try {
withContext(Dispatchers.IO) { database.read(library) }
} catch (e: Exception) {
logE("Unable to restore playback state.")
logE(e.stackTraceToString())
return false
}
// val library = musicStore.library ?: return false // Translate the state we have just read into a usable playback state for this
// val internalPlayer = internalPlayer ?: return false // instance.
// val state = return synchronized(this) {
// try { // State could have changed while we were loading, so check if we were initialized
// withContext(Dispatchers.IO) { database.read(library) } // now before applying the state.
// } catch (e: Exception) { if (state != null && (!isInitialized || force)) {
// logE("Unable to restore playback state.") parent = state.parent
// logE(e.stackTraceToString()) queue.applySavedState(state.queueState)
// return false repeatMode = state.repeatMode
// } notifyNewPlayback()
// notifyRepeatModeChanged()
// // Translate the state we have just read into a usable playback state for this // Continuing playback after drastic state updates is a bad idea, so pause.
// // instance. internalPlayer.loadSong(queue.currentSong, false)
// return synchronized(this) { internalPlayer.seekTo(state.positionMs)
// // State could have changed while we were loading, so check if we were isInitialized = true
// initialized true
// // now before applying the state. } else {
// if (state != null && (!isInitialized || force)) { false
// index = state.index }
// parent = state.parent }
// _queue = state.queue.toMutableList()
// repeatMode = state.repeatMode
// isShuffled = state.isShuffled
//
// notifyNewPlayback()
// notifyRepeatModeChanged()
// notifyShuffledChanged()
//
// // Continuing playback after drastic state updates is a bad idea, so
// pause.
// internalPlayer.loadSong(song, false)
// internalPlayer.seekTo(state.positionMs)
//
// isInitialized = true
//
// true
// } else {
// false
// }
// }
} }
/** /**
@ -451,26 +438,25 @@ class PlaybackStateManager private constructor() {
*/ */
suspend fun saveState(database: PlaybackStateDatabase): Boolean { suspend fun saveState(database: PlaybackStateDatabase): Boolean {
logD("Saving state to DB") logD("Saving state to DB")
return false // Create the saved state from the current playback state.
// // Create the saved state from the current playback state. val state =
// val state = synchronized(this) {
// synchronized(this) { queue.toSavedState()?.let {
// PlaybackStateDatabase.SavedState( PlaybackStateDatabase.SavedState(
// index = index, parent = parent,
// parent = parent, queueState = it,
// queue = _queue, positionMs = playerState.calculateElapsedPositionMs(),
// positionMs = playerState.calculateElapsedPositionMs(), repeatMode = repeatMode)
// isShuffled = isShuffled, }
// repeatMode = repeatMode) }
// } return try {
// return try { withContext(Dispatchers.IO) { database.write(state) }
// withContext(Dispatchers.IO) { database.write(state) } true
// true } catch (e: Exception) {
// } catch (e: Exception) { logE("Unable to save playback state.")
// logE("Unable to save playback state.") logE(e.stackTraceToString())
// logE(e.stackTraceToString()) false
// false }
// }
} }
/** /**
@ -519,8 +505,9 @@ class PlaybackStateManager private constructor() {
} }
// Sanitize the queue. // Sanitize the queue.
queue.applySavedState( queue.toSavedState()?.let { state ->
queue.toSavedState().remap { newLibrary.sanitize(unlikelyToBeNull(it)) }) queue.applySavedState(state.remap { newLibrary.sanitize(unlikelyToBeNull(it)) })
}
notifyNewPlayback() notifyNewPlayback()

View file

@ -59,7 +59,13 @@ class Queue {
* Resolve this queue into a more conventional list of [Song]s. * Resolve this queue into a more conventional list of [Song]s.
* @return A list of [Song] corresponding to the current queue mapping. * @return A list of [Song] corresponding to the current queue mapping.
*/ */
fun resolve() = shuffledMapping.map { heap[it] }.ifEmpty { orderedMapping.map { heap[it] } } fun resolve() =
if (currentSong != null) {
shuffledMapping.map { heap[it] }.ifEmpty { orderedMapping.map { heap[it] } }
} else {
// Queue doesn't exist, return saner data.
listOf()
}
/** /**
* Go to a particular index in the queue. * Go to a particular index in the queue.
@ -253,12 +259,10 @@ class Queue {
* @return A new [SavedState] reflecting the exact state of the queue when called. * @return A new [SavedState] reflecting the exact state of the queue when called.
*/ */
fun toSavedState() = fun toSavedState() =
SavedState( currentSong?.let { song ->
heap.toList(), SavedState(
orderedMapping.toList(), heap.toList(), orderedMapping.toList(), shuffledMapping.toList(), index, song.uid)
shuffledMapping.toList(), }
index,
currentSong?.uid)
/** /**
* Update this instance from the given [SavedState]. * Update this instance from the given [SavedState].
@ -287,8 +291,8 @@ class Queue {
} }
// Make sure we re-align the index to point to the previously playing song. // Make sure we re-align the index to point to the previously playing song.
index = savedState.currentIndex index = savedState.index
while (currentSong?.uid != savedState.currentSongUid && index > -1) { while (currentSong?.uid != savedState.songUid && index > -1) {
index-- index--
} }
check() check()
@ -348,15 +352,15 @@ class Queue {
* other values. * other values.
* @param orderedMapping The mapping of the [heap] to an ordered queue. * @param orderedMapping The mapping of the [heap] to an ordered queue.
* @param shuffledMapping The mapping of the [heap] to a shuffled queue. * @param shuffledMapping The mapping of the [heap] to a shuffled queue.
* @param currentIndex The index of the currently playing [Song] at the time of serialization. * @param index The index of the currently playing [Song] at the time of serialization.
* @param currentSongUid The [Music.UID] of the [Song] that was originally at [currentIndex]. * @param songUid The [Music.UID] of the [Song] that was originally at [index].
*/ */
class SavedState( class SavedState(
val heap: List<Song?>, val heap: List<Song?>,
val orderedMapping: List<Int>, val orderedMapping: List<Int>,
val shuffledMapping: List<Int>, val shuffledMapping: List<Int>,
val currentIndex: Int, val index: Int,
val currentSongUid: Music.UID?, val songUid: Music.UID,
) { ) {
/** /**
* Remaps the [heap] of this instance based on the given mapping function and copies it into * Remaps the [heap] of this instance based on the given mapping function and copies it into
@ -367,8 +371,7 @@ class Queue {
* @throws IllegalStateException If the invariant specified by [transform] is violated. * @throws IllegalStateException If the invariant specified by [transform] is violated.
*/ */
inline fun remap(transform: (Song?) -> Song?) = inline fun remap(transform: (Song?) -> Song?) =
SavedState( SavedState(heap.map(transform), orderedMapping, shuffledMapping, index, songUid)
heap.map(transform), orderedMapping, shuffledMapping, currentIndex, currentSongUid)
} }
/** /**