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:
parent
9bd78bc855
commit
692839e8fe
4 changed files with 210 additions and 170 deletions
|
@ -77,6 +77,7 @@ class QueueFragment : ViewBindingFragment<FragmentQueueBinding>(), EditableListL
|
|||
|
||||
override fun onDestroyBinding(binding: FragmentQueueBinding) {
|
||||
super.onDestroyBinding(binding)
|
||||
touchHelper = null
|
||||
binding.queueRecycler.adapter = null
|
||||
}
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@ import android.content.Context
|
|||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.provider.BaseColumns
|
||||
import androidx.core.database.getIntOrNull
|
||||
import androidx.core.database.sqlite.transaction
|
||||
import org.oxycblt.auxio.music.*
|
||||
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.
|
||||
db.createTable(TABLE_STATE) {
|
||||
append("${BaseColumns._ID} INTEGER PRIMARY KEY,")
|
||||
append("${StateColumns.INDEX} INTEGER NOT NULL,")
|
||||
append("${StateColumns.POSITION} LONG NOT NULL,")
|
||||
append("${StateColumns.REPEAT_MODE} INTEGER NOT NULL,")
|
||||
append("${StateColumns.IS_SHUFFLED} BOOLEAN NOT NULL,")
|
||||
append("${StateColumns.SONG_UID} STRING,")
|
||||
append("${StateColumns.PARENT_UID} STRING")
|
||||
append("${PlaybackStateColumns.INDEX} INTEGER NOT NULL,")
|
||||
append("${PlaybackStateColumns.POSITION} LONG NOT NULL,")
|
||||
append("${PlaybackStateColumns.REPEAT_MODE} INTEGER NOT NULL,")
|
||||
append("${PlaybackStateColumns.SONG_UID} STRING,")
|
||||
append("${PlaybackStateColumns.PARENT_UID} STRING")
|
||||
}
|
||||
|
||||
db.createTable(TABLE_QUEUE) {
|
||||
db.createTable(TABLE_QUEUE_HEAP) {
|
||||
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")
|
||||
db.apply {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -77,63 +84,78 @@ class PlaybackStateDatabase private constructor(context: Context) :
|
|||
requireBackgroundThread()
|
||||
// Read the saved state and queue. If the state is non-null, that must imply an
|
||||
// existent, albeit possibly empty, queue.
|
||||
val rawState = readRawState() ?: return null
|
||||
val queue = readQueue(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--
|
||||
}
|
||||
val rawState = readRawPlaybackState() ?: return null
|
||||
val rawQueueState = readRawQueueState(library)
|
||||
// Restore parent item from the music library. If this fails, then the playback mode
|
||||
// reverts to "All Songs", which is considered okay.
|
||||
val parent = rawState.parentUid?.let { library.find<MusicParent>(it) }
|
||||
return SavedState(
|
||||
index = actualIndex,
|
||||
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,
|
||||
repeatMode = rawState.repeatMode,
|
||||
isShuffled = rawState.isShuffled)
|
||||
repeatMode = rawState.repeatMode)
|
||||
}
|
||||
|
||||
private fun readRawState() =
|
||||
private fun readRawPlaybackState() =
|
||||
readableDatabase.queryAll(TABLE_STATE) { cursor ->
|
||||
if (!cursor.moveToFirst()) {
|
||||
// Empty, nothing to do.
|
||||
return@queryAll null
|
||||
}
|
||||
|
||||
val indexIndex = cursor.getColumnIndexOrThrow(StateColumns.INDEX)
|
||||
val posIndex = cursor.getColumnIndexOrThrow(StateColumns.POSITION)
|
||||
val repeatModeIndex = cursor.getColumnIndexOrThrow(StateColumns.REPEAT_MODE)
|
||||
val shuffleIndex = cursor.getColumnIndexOrThrow(StateColumns.IS_SHUFFLED)
|
||||
val songUidIndex = cursor.getColumnIndexOrThrow(StateColumns.SONG_UID)
|
||||
val parentUidIndex = cursor.getColumnIndexOrThrow(StateColumns.PARENT_UID)
|
||||
RawState(
|
||||
val indexIndex = cursor.getColumnIndexOrThrow(PlaybackStateColumns.INDEX)
|
||||
val posIndex = cursor.getColumnIndexOrThrow(PlaybackStateColumns.POSITION)
|
||||
val repeatModeIndex = cursor.getColumnIndexOrThrow(PlaybackStateColumns.REPEAT_MODE)
|
||||
val songUidIndex = cursor.getColumnIndexOrThrow(PlaybackStateColumns.SONG_UID)
|
||||
val parentUidIndex = cursor.getColumnIndexOrThrow(PlaybackStateColumns.PARENT_UID)
|
||||
RawPlaybackState(
|
||||
index = cursor.getInt(indexIndex),
|
||||
positionMs = cursor.getLong(posIndex),
|
||||
repeatMode = RepeatMode.fromIntCode(cursor.getInt(repeatModeIndex))
|
||||
?: RepeatMode.NONE,
|
||||
isShuffled = cursor.getInt(shuffleIndex) == 1,
|
||||
songUid = Music.UID.fromString(cursor.getString(songUidIndex))
|
||||
?: return@queryAll null,
|
||||
parentUid = cursor.getString(parentUidIndex)?.let(Music.UID::fromString))
|
||||
}
|
||||
|
||||
private fun readQueue(library: Library): List<Song> {
|
||||
val queue = mutableListOf<Song>()
|
||||
readableDatabase.queryAll(TABLE_QUEUE) { cursor ->
|
||||
val songIndex = cursor.getColumnIndexOrThrow(QueueColumns.SONG_UID)
|
||||
private fun readRawQueueState(library: Library): RawQueueState {
|
||||
val heap = mutableListOf<Song?>()
|
||||
readableDatabase.queryAll(TABLE_QUEUE_HEAP) { cursor ->
|
||||
if (cursor.count == 0) {
|
||||
// Empty, nothing to do.
|
||||
return@queryAll
|
||||
}
|
||||
|
||||
val songIndex = cursor.getColumnIndexOrThrow(QueueHeapColumns.SONG_UID)
|
||||
while (cursor.moveToNext()) {
|
||||
val uid = Music.UID.fromString(cursor.getString(songIndex)) ?: continue
|
||||
val song = library.find<Song>(uid) ?: continue
|
||||
queue.add(song)
|
||||
heap.add(Music.UID.fromString(cursor.getString(songIndex))?.let(library::find))
|
||||
}
|
||||
}
|
||||
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 queue
|
||||
return RawQueueState(heap, orderedMapping.filterNotNull(), shuffledMapping.filterNotNull())
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,40 +166,43 @@ class PlaybackStateDatabase private constructor(context: Context) :
|
|||
requireBackgroundThread()
|
||||
// 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.
|
||||
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.
|
||||
val rawState =
|
||||
RawState(
|
||||
index = state.index,
|
||||
val rawPlaybackState =
|
||||
RawPlaybackState(
|
||||
index = state.queueState.index,
|
||||
positionMs = state.positionMs,
|
||||
repeatMode = state.repeatMode,
|
||||
isShuffled = state.isShuffled,
|
||||
songUid = state.queue[state.index].uid,
|
||||
songUid = state.queueState.songUid,
|
||||
parentUid = state.parent?.uid)
|
||||
writeRawState(rawState)
|
||||
writeQueue(state.queue)
|
||||
writeRawPlaybackState(rawPlaybackState)
|
||||
val rawQueueState =
|
||||
RawQueueState(
|
||||
heap = state.queueState.heap,
|
||||
orderedMapping = state.queueState.orderedMapping,
|
||||
shuffledMapping = state.queueState.shuffledMapping)
|
||||
writeRawQueueState(rawQueueState)
|
||||
logD("Wrote state")
|
||||
} else {
|
||||
writeRawState(null)
|
||||
writeQueue(null)
|
||||
writeRawPlaybackState(null)
|
||||
writeRawQueueState(null)
|
||||
logD("Cleared state")
|
||||
}
|
||||
}
|
||||
|
||||
private fun writeRawState(rawState: RawState?) {
|
||||
private fun writeRawPlaybackState(rawPlaybackState: RawPlaybackState?) {
|
||||
writableDatabase.transaction {
|
||||
delete(TABLE_STATE, null, null)
|
||||
|
||||
if (rawState != null) {
|
||||
if (rawPlaybackState != null) {
|
||||
val stateData =
|
||||
ContentValues(7).apply {
|
||||
put(BaseColumns._ID, 0)
|
||||
put(StateColumns.SONG_UID, rawState.songUid.toString())
|
||||
put(StateColumns.POSITION, rawState.positionMs)
|
||||
put(StateColumns.PARENT_UID, rawState.parentUid?.toString())
|
||||
put(StateColumns.INDEX, rawState.index)
|
||||
put(StateColumns.IS_SHUFFLED, rawState.isShuffled)
|
||||
put(StateColumns.REPEAT_MODE, rawState.repeatMode.intCode)
|
||||
put(PlaybackStateColumns.SONG_UID, rawPlaybackState.songUid.toString())
|
||||
put(PlaybackStateColumns.POSITION, rawPlaybackState.positionMs)
|
||||
put(PlaybackStateColumns.PARENT_UID, rawPlaybackState.parentUid?.toString())
|
||||
put(PlaybackStateColumns.INDEX, rawPlaybackState.index)
|
||||
put(PlaybackStateColumns.REPEAT_MODE, rawPlaybackState.repeatMode.intCode)
|
||||
}
|
||||
|
||||
insert(TABLE_STATE, null, stateData)
|
||||
|
@ -185,47 +210,54 @@ class PlaybackStateDatabase private constructor(context: Context) :
|
|||
}
|
||||
}
|
||||
|
||||
private fun writeQueue(queue: List<Song>?) {
|
||||
writableDatabase.writeList(queue ?: listOf(), TABLE_QUEUE) { i, song ->
|
||||
private fun writeRawQueueState(rawQueueState: RawQueueState?) {
|
||||
writableDatabase.writeList(rawQueueState?.heap ?: listOf(), TABLE_QUEUE_HEAP) { i, song ->
|
||||
ContentValues(2).apply {
|
||||
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.
|
||||
* @param index The position of the currently playing item in the queue. Can be -1 if the
|
||||
* persisted index no longer exists.
|
||||
* @param queue The [Song] queue.
|
||||
* @param parent The [MusicParent] item currently being played from
|
||||
* @param parent The [MusicParent] item currently being played from.
|
||||
* @param queueState The [Queue.SavedState]
|
||||
* @param positionMs The current position in the currently played song, in ms
|
||||
* @param repeatMode The current [RepeatMode].
|
||||
* @param isShuffled Whether the queue is shuffled or not.
|
||||
*/
|
||||
data class SavedState(
|
||||
val index: Int,
|
||||
val queue: List<Song>,
|
||||
val parent: MusicParent?,
|
||||
val queueState: Queue.SavedState,
|
||||
val positionMs: Long,
|
||||
val repeatMode: RepeatMode,
|
||||
val isShuffled: Boolean
|
||||
)
|
||||
|
||||
/**
|
||||
* A lower-level form of [SavedState] that contains additional information to create a more
|
||||
* reliable restoration process.
|
||||
*/
|
||||
private data class RawState(
|
||||
/** @see SavedState.index */
|
||||
/** A lower-level form of [SavedState] that contains individual field-based information. */
|
||||
private data class RawPlaybackState(
|
||||
/** @see Queue.SavedState.index */
|
||||
val index: Int,
|
||||
/** @see SavedState.positionMs */
|
||||
val positionMs: Long,
|
||||
/** @see SavedState.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
|
||||
* 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?
|
||||
)
|
||||
|
||||
/** 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. */
|
||||
private object StateColumns {
|
||||
/** @see RawState.index */
|
||||
private object PlaybackStateColumns {
|
||||
/** @see RawPlaybackState.index */
|
||||
const val INDEX = "queue_index"
|
||||
/** @see RawState.positionMs */
|
||||
/** @see RawPlaybackState.positionMs */
|
||||
const val POSITION = "position"
|
||||
/** @see RawState.isShuffled */
|
||||
const val IS_SHUFFLED = "is_shuffling"
|
||||
/** @see RawState.repeatMode */
|
||||
/** @see RawPlaybackState.repeatMode */
|
||||
const val REPEAT_MODE = "repeat_mode"
|
||||
/** @see RawState.songUid */
|
||||
/** @see RawPlaybackState.songUid */
|
||||
const val SONG_UID = "song_uid"
|
||||
/** @see RawState.parentUid */
|
||||
/** @see RawPlaybackState.parentUid */
|
||||
const val PARENT_UID = "parent"
|
||||
}
|
||||
|
||||
/** Defines the columns used in the queue table. */
|
||||
private object QueueColumns {
|
||||
/** Defines the columns used in the queue heap table. */
|
||||
private object QueueHeapColumns {
|
||||
/** @see Music.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 {
|
||||
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_QUEUE = "queue"
|
||||
private const val TABLE_QUEUE_HEAP = "queue_heap"
|
||||
private const val TABLE_QUEUE_MAPPINGS = "queue_mapping"
|
||||
|
||||
@Volatile private var INSTANCE: PlaybackStateDatabase? = null
|
||||
|
||||
|
|
|
@ -155,8 +155,7 @@ class PlaybackStateManager private constructor() {
|
|||
/**
|
||||
* Start new playback.
|
||||
* @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
|
||||
* [MusicStore.Library].
|
||||
* @param parent The [MusicParent] to play from, or null if to play from the entire [Library].
|
||||
* @param sort [Sort] to initially sort an ordered queue with.
|
||||
* @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.
|
||||
* @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.
|
||||
*/
|
||||
suspend fun restoreState(database: PlaybackStateDatabase, force: Boolean): Boolean {
|
||||
|
@ -399,49 +398,37 @@ class PlaybackStateManager private constructor() {
|
|||
return false
|
||||
}
|
||||
|
||||
// TODO: Re-implement with new queue
|
||||
return false
|
||||
val library = musicStore.library ?: 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
|
||||
// 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
|
||||
// }
|
||||
//
|
||||
// // Translate the state we have just read into a usable playback state for this
|
||||
// // instance.
|
||||
// return synchronized(this) {
|
||||
// // State could have changed while we were loading, so check if we were
|
||||
// initialized
|
||||
// // now before applying the state.
|
||||
// if (state != null && (!isInitialized || force)) {
|
||||
// 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
|
||||
// }
|
||||
// }
|
||||
// Translate the state we have just read into a usable playback state for this
|
||||
// instance.
|
||||
return synchronized(this) {
|
||||
// State could have changed while we were loading, so check if we were initialized
|
||||
// now before applying the state.
|
||||
if (state != null && (!isInitialized || force)) {
|
||||
parent = state.parent
|
||||
queue.applySavedState(state.queueState)
|
||||
repeatMode = state.repeatMode
|
||||
notifyNewPlayback()
|
||||
notifyRepeatModeChanged()
|
||||
// Continuing playback after drastic state updates is a bad idea, so pause.
|
||||
internalPlayer.loadSong(queue.currentSong, false)
|
||||
internalPlayer.seekTo(state.positionMs)
|
||||
isInitialized = true
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -451,26 +438,25 @@ class PlaybackStateManager private constructor() {
|
|||
*/
|
||||
suspend fun saveState(database: PlaybackStateDatabase): Boolean {
|
||||
logD("Saving state to DB")
|
||||
return false
|
||||
// // Create the saved state from the current playback state.
|
||||
// val state =
|
||||
// synchronized(this) {
|
||||
// PlaybackStateDatabase.SavedState(
|
||||
// index = index,
|
||||
// parent = parent,
|
||||
// queue = _queue,
|
||||
// positionMs = playerState.calculateElapsedPositionMs(),
|
||||
// isShuffled = isShuffled,
|
||||
// repeatMode = repeatMode)
|
||||
// }
|
||||
// return try {
|
||||
// withContext(Dispatchers.IO) { database.write(state) }
|
||||
// true
|
||||
// } catch (e: Exception) {
|
||||
// logE("Unable to save playback state.")
|
||||
// logE(e.stackTraceToString())
|
||||
// false
|
||||
// }
|
||||
// Create the saved state from the current playback state.
|
||||
val state =
|
||||
synchronized(this) {
|
||||
queue.toSavedState()?.let {
|
||||
PlaybackStateDatabase.SavedState(
|
||||
parent = parent,
|
||||
queueState = it,
|
||||
positionMs = playerState.calculateElapsedPositionMs(),
|
||||
repeatMode = repeatMode)
|
||||
}
|
||||
}
|
||||
return try {
|
||||
withContext(Dispatchers.IO) { database.write(state) }
|
||||
true
|
||||
} catch (e: Exception) {
|
||||
logE("Unable to save playback state.")
|
||||
logE(e.stackTraceToString())
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -519,8 +505,9 @@ class PlaybackStateManager private constructor() {
|
|||
}
|
||||
|
||||
// Sanitize the queue.
|
||||
queue.applySavedState(
|
||||
queue.toSavedState().remap { newLibrary.sanitize(unlikelyToBeNull(it)) })
|
||||
queue.toSavedState()?.let { state ->
|
||||
queue.applySavedState(state.remap { newLibrary.sanitize(unlikelyToBeNull(it)) })
|
||||
}
|
||||
|
||||
notifyNewPlayback()
|
||||
|
||||
|
|
|
@ -59,7 +59,13 @@ class Queue {
|
|||
* Resolve this queue into a more conventional list of [Song]s.
|
||||
* @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.
|
||||
|
@ -253,12 +259,10 @@ class Queue {
|
|||
* @return A new [SavedState] reflecting the exact state of the queue when called.
|
||||
*/
|
||||
fun toSavedState() =
|
||||
SavedState(
|
||||
heap.toList(),
|
||||
orderedMapping.toList(),
|
||||
shuffledMapping.toList(),
|
||||
index,
|
||||
currentSong?.uid)
|
||||
currentSong?.let { song ->
|
||||
SavedState(
|
||||
heap.toList(), orderedMapping.toList(), shuffledMapping.toList(), index, song.uid)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
index = savedState.currentIndex
|
||||
while (currentSong?.uid != savedState.currentSongUid && index > -1) {
|
||||
index = savedState.index
|
||||
while (currentSong?.uid != savedState.songUid && index > -1) {
|
||||
index--
|
||||
}
|
||||
check()
|
||||
|
@ -348,15 +352,15 @@ class Queue {
|
|||
* other values.
|
||||
* @param orderedMapping The mapping of the [heap] to an ordered 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 currentSongUid The [Music.UID] of the [Song] that was originally at [currentIndex].
|
||||
* @param index The index of the currently playing [Song] at the time of serialization.
|
||||
* @param songUid The [Music.UID] of the [Song] that was originally at [index].
|
||||
*/
|
||||
class SavedState(
|
||||
val heap: List<Song?>,
|
||||
val orderedMapping: List<Int>,
|
||||
val shuffledMapping: List<Int>,
|
||||
val currentIndex: Int,
|
||||
val currentSongUid: Music.UID?,
|
||||
val index: Int,
|
||||
val songUid: Music.UID,
|
||||
) {
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
inline fun remap(transform: (Song?) -> Song?) =
|
||||
SavedState(
|
||||
heap.map(transform), orderedMapping, shuffledMapping, currentIndex, currentSongUid)
|
||||
SavedState(heap.map(transform), orderedMapping, shuffledMapping, index, songUid)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue