diff --git a/app/src/main/java/org/oxycblt/auxio/music/tags/Date.kt b/app/src/main/java/org/oxycblt/auxio/music/tags/Date.kt index 1c68fa05e..d3658ce6f 100644 --- a/app/src/main/java/org/oxycblt/auxio/music/tags/Date.kt +++ b/app/src/main/java/org/oxycblt/auxio/music/tags/Date.kt @@ -273,7 +273,5 @@ class Date private constructor(private val tokens: List) : Comparable dst.add(src.getOrNull(4)?.inRangeOrNull(0..59) ?: return) dst.add(src.getOrNull(5)?.inRangeOrNull(0..59) ?: return) } - - private fun transformYearToken(src: List, dst: MutableList) {} } } diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index 67b596edf..efa375415 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -28,6 +28,7 @@ import org.oxycblt.auxio.playback.state.PlaybackStateManager.Listener import org.oxycblt.auxio.util.logD import org.oxycblt.auxio.util.logE import org.oxycblt.auxio.util.logW +import org.oxycblt.auxio.util.unlikelyToBeNull /** * Core playback state controller class. @@ -518,7 +519,8 @@ class PlaybackStateManager private constructor() { } // Sanitize the queue. - queue.remap { it.map(newLibrary::sanitize) } + queue.applySavedState( + queue.toSavedState().remap { newLibrary.sanitize(unlikelyToBeNull(it)) }) notifyNewPlayback() diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/Queue.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/Queue.kt index de48736a2..36655d543 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/Queue.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/Queue.kt @@ -19,6 +19,7 @@ package org.oxycblt.auxio.playback.state import kotlin.random.Random import kotlin.random.nextInt +import org.oxycblt.auxio.music.Music 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. * * 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 - * typical play queue. + * except in special circumstances like [SavedState]. The functions exposed should be familiar for + * any typical play queue. * * @author OxygenCobalt */ @@ -124,46 +125,6 @@ class Queue { 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) -> List) { - 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() - 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. * @param songs The [Song]s to add. @@ -287,6 +248,52 @@ class Queue { 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() + 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 { // 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 @@ -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, + val orderedMapping: List, + val shuffledMapping: List, + 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 * precise meanings of these differ somewhat depending on the type of mutation done. diff --git a/app/src/test/java/org/oxycblt/auxio/music/library/LibraryTest.kt b/app/src/test/java/org/oxycblt/auxio/music/library/LibraryTest.kt index 6368a32e1..42263b2bb 100644 --- a/app/src/test/java/org/oxycblt/auxio/music/library/LibraryTest.kt +++ b/app/src/test/java/org/oxycblt/auxio/music/library/LibraryTest.kt @@ -17,11 +17,14 @@ package org.oxycblt.auxio.music.library -import org.oxycblt.auxio.music.Song - class LibraryTest { + fun library_common() {} - companion object { - val LIBRARY = listOf(Song.Raw()) - } + fun library_sparse() {} + + fun library_multiArtist() {} + + fun library_multiGenre() {} + + fun library_musicBrainz() {} }