playback: refactor queue persistence
Refactor the queue saved state system to make it easier to imlement in the playback state database.
This commit is contained in:
parent
82a9c08666
commit
0199d2f343
4 changed files with 90 additions and 50 deletions
|
@ -273,7 +273,5 @@ class Date private constructor(private val tokens: List<Int>) : Comparable<Date>
|
||||||
dst.add(src.getOrNull(4)?.inRangeOrNull(0..59) ?: return)
|
dst.add(src.getOrNull(4)?.inRangeOrNull(0..59) ?: return)
|
||||||
dst.add(src.getOrNull(5)?.inRangeOrNull(0..59) ?: return)
|
dst.add(src.getOrNull(5)?.inRangeOrNull(0..59) ?: return)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun transformYearToken(src: List<Int>, dst: MutableList<Int>) {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager.Listener
|
||||||
import org.oxycblt.auxio.util.logD
|
import org.oxycblt.auxio.util.logD
|
||||||
import org.oxycblt.auxio.util.logE
|
import org.oxycblt.auxio.util.logE
|
||||||
import org.oxycblt.auxio.util.logW
|
import org.oxycblt.auxio.util.logW
|
||||||
|
import org.oxycblt.auxio.util.unlikelyToBeNull
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core playback state controller class.
|
* Core playback state controller class.
|
||||||
|
@ -518,7 +519,8 @@ class PlaybackStateManager private constructor() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sanitize the queue.
|
// Sanitize the queue.
|
||||||
queue.remap { it.map(newLibrary::sanitize) }
|
queue.applySavedState(
|
||||||
|
queue.toSavedState().remap { newLibrary.sanitize(unlikelyToBeNull(it)) })
|
||||||
|
|
||||||
notifyNewPlayback()
|
notifyNewPlayback()
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ package org.oxycblt.auxio.playback.state
|
||||||
|
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
import kotlin.random.nextInt
|
import kotlin.random.nextInt
|
||||||
|
import org.oxycblt.auxio.music.Music
|
||||||
import org.oxycblt.auxio.music.Song
|
import org.oxycblt.auxio.music.Song
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -30,8 +31,8 @@ import org.oxycblt.auxio.music.Song
|
||||||
* interpreted into different queues depending on the current playback configuration.
|
* interpreted into different queues depending on the current playback configuration.
|
||||||
*
|
*
|
||||||
* In general, the implementation details don't need to be known for this data structure to be used,
|
* In general, the implementation details don't need to be known for this data structure to be used,
|
||||||
* except in special circumstances like [remap]. The functions exposed should be familiar for any
|
* except in special circumstances like [SavedState]. The functions exposed should be familiar for
|
||||||
* typical play queue.
|
* any typical play queue.
|
||||||
*
|
*
|
||||||
* @author OxygenCobalt
|
* @author OxygenCobalt
|
||||||
*/
|
*/
|
||||||
|
@ -124,46 +125,6 @@ class Queue {
|
||||||
check()
|
check()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Replace the given heap with a new
|
|
||||||
* @param map Code to remap the existing [Song] heap into a new [Song] heap. This **MUST** be
|
|
||||||
* the same size as the original heap. [Song] instances that could not be converted should be
|
|
||||||
* replaced with null in the new heap.
|
|
||||||
* @throws IllegalStateException If the given invariants regarding [map] were violated.
|
|
||||||
*/
|
|
||||||
fun remap(map: (List<Song>) -> List<Song?>) {
|
|
||||||
val newHeap = map(heap)
|
|
||||||
val oldSong = currentSong
|
|
||||||
check(newHeap.size == heap.size) { "New heap must be the same size as original heap" }
|
|
||||||
|
|
||||||
val adjustments = mutableListOf<Int?>()
|
|
||||||
var currentShift = 0
|
|
||||||
for (song in newHeap) {
|
|
||||||
if (song != null) {
|
|
||||||
adjustments.add(currentShift)
|
|
||||||
} else {
|
|
||||||
adjustments.add(null)
|
|
||||||
currentShift -= 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
heap = newHeap.filterNotNull().toMutableList()
|
|
||||||
orderedMapping =
|
|
||||||
orderedMapping.mapNotNullTo(mutableListOf()) { heapIndex ->
|
|
||||||
adjustments[heapIndex]?.let { heapIndex + it }
|
|
||||||
}
|
|
||||||
shuffledMapping =
|
|
||||||
shuffledMapping.mapNotNullTo(mutableListOf()) { heapIndex ->
|
|
||||||
adjustments[heapIndex]?.let { heapIndex + it }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make sure we re-align the index to point to the previously playing song.
|
|
||||||
while (currentSong != oldSong && index > -1) {
|
|
||||||
index--
|
|
||||||
}
|
|
||||||
check()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add [Song]s to the top of the queue. Will start playback if nothing is playing.
|
* Add [Song]s to the top of the queue. Will start playback if nothing is playing.
|
||||||
* @param songs The [Song]s to add.
|
* @param songs The [Song]s to add.
|
||||||
|
@ -287,6 +248,52 @@ class Queue {
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the current state of this instance into a [SavedState].
|
||||||
|
* @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)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update this instance from the given [SavedState].
|
||||||
|
* @param savedState A [SavedState] with a valid queue representation.
|
||||||
|
*/
|
||||||
|
fun applySavedState(savedState: SavedState) {
|
||||||
|
val adjustments = mutableListOf<Int?>()
|
||||||
|
var currentShift = 0
|
||||||
|
for (song in savedState.heap) {
|
||||||
|
if (song != null) {
|
||||||
|
adjustments.add(currentShift)
|
||||||
|
} else {
|
||||||
|
adjustments.add(null)
|
||||||
|
currentShift -= 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
heap = savedState.heap.filterNotNull().toMutableList()
|
||||||
|
orderedMapping =
|
||||||
|
savedState.orderedMapping.mapNotNullTo(mutableListOf()) { heapIndex ->
|
||||||
|
adjustments[heapIndex]?.let { heapIndex + it }
|
||||||
|
}
|
||||||
|
shuffledMapping =
|
||||||
|
savedState.shuffledMapping.mapNotNullTo(mutableListOf()) { heapIndex ->
|
||||||
|
adjustments[heapIndex]?.let { heapIndex + it }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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--
|
||||||
|
}
|
||||||
|
check()
|
||||||
|
}
|
||||||
|
|
||||||
private fun addSongToHeap(song: Song): Int {
|
private fun addSongToHeap(song: Song): Int {
|
||||||
// We want to first try to see if there are any "orphaned" songs in the queue
|
// We want to first try to see if there are any "orphaned" songs in the queue
|
||||||
// that we can re-use. This way, we can reduce the memory used up by songs that
|
// that we can re-use. This way, we can reduce the memory used up by songs that
|
||||||
|
@ -334,6 +341,36 @@ class Queue {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An immutable representation of the queue state.
|
||||||
|
* @param heap The heap of [Song]s that are/were used in the queue. This can be modified with
|
||||||
|
* null values to represent [Song]s that were "lost" from the heap without having to change
|
||||||
|
* 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].
|
||||||
|
*/
|
||||||
|
class SavedState(
|
||||||
|
val heap: List<Song?>,
|
||||||
|
val orderedMapping: List<Int>,
|
||||||
|
val shuffledMapping: List<Int>,
|
||||||
|
val currentIndex: Int,
|
||||||
|
val currentSongUid: Music.UID?,
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Remaps the [heap] of this instance based on the given mapping function and copies it into
|
||||||
|
* a new [SavedState].
|
||||||
|
* @param transform Code to remap the existing [Song] heap into a new [Song] heap. This
|
||||||
|
* **MUST** be the same size as the original heap. [Song] instances that could not be
|
||||||
|
* converted should be replaced with null in the new heap.
|
||||||
|
* @throws IllegalStateException If the invariant specified by [transform] is violated.
|
||||||
|
*/
|
||||||
|
inline fun remap(transform: (Song?) -> Song?) =
|
||||||
|
SavedState(
|
||||||
|
heap.map(transform), orderedMapping, shuffledMapping, currentIndex, currentSongUid)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the possible changes that can occur during certain queue mutation events. The
|
* Represents the possible changes that can occur during certain queue mutation events. The
|
||||||
* precise meanings of these differ somewhat depending on the type of mutation done.
|
* precise meanings of these differ somewhat depending on the type of mutation done.
|
||||||
|
|
|
@ -17,11 +17,14 @@
|
||||||
|
|
||||||
package org.oxycblt.auxio.music.library
|
package org.oxycblt.auxio.music.library
|
||||||
|
|
||||||
import org.oxycblt.auxio.music.Song
|
|
||||||
|
|
||||||
class LibraryTest {
|
class LibraryTest {
|
||||||
|
fun library_common() {}
|
||||||
|
|
||||||
companion object {
|
fun library_sparse() {}
|
||||||
val LIBRARY = listOf(Song.Raw())
|
|
||||||
}
|
fun library_multiArtist() {}
|
||||||
|
|
||||||
|
fun library_multiGenre() {}
|
||||||
|
|
||||||
|
fun library_musicBrainz() {}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue