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) {
|
override fun onDestroyBinding(binding: FragmentQueueBinding) {
|
||||||
super.onDestroyBinding(binding)
|
super.onDestroyBinding(binding)
|
||||||
|
touchHelper = null
|
||||||
binding.queueRecycler.adapter = null
|
binding.queueRecycler.adapter = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
|
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue