mirror of
https://github.com/austinried/subtracks.git
synced 2026-11-11 15:29:15 +01:00
keep lastPosition in sync when a track move or delete shifts the current
move/removeAt/undo (and the window shift) changed the current track's position without updating lastPosition, so the next transition compared against a stale position and consumed whatever now sat there — moving a queued track in front of the playing one deleted the moved track on the following skip. Added an eager-dispatcher regression test.
This commit is contained in:
@@ -337,6 +337,7 @@ class PlaybackController(
|
|||||||
if (wasUpNext) queueRepository.combinedIndexOf(updated, it) else queueRepository.combinedContextIndexOf(updated, it)
|
if (wasUpNext) queueRepository.combinedIndexOf(updated, it) else queueRepository.combinedContextIndexOf(updated, it)
|
||||||
} ?: (if (position < current) current - 1 else current)
|
} ?: (if (position < current) current - 1 else current)
|
||||||
).coerceIn(0, updated.size - 1)
|
).coerceIn(0, updated.size - 1)
|
||||||
|
lastPosition = target
|
||||||
lastEdit =
|
lastEdit =
|
||||||
if (removed == null) {
|
if (removed == null) {
|
||||||
ReloadUndo(entries, cursor, snapshot.shuffleOrder, upNext, snapshot.upNextAnchor)
|
ReloadUndo(entries, cursor, snapshot.shuffleOrder, upNext, snapshot.upNextAnchor)
|
||||||
@@ -390,6 +391,7 @@ class PlaybackController(
|
|||||||
if (!queueRepository.move(snapshot, from, to)) return@withLock
|
if (!queueRepository.move(snapshot, from, to)) return@withLock
|
||||||
this.snapshot = readSnapshot()
|
this.snapshot = readSnapshot()
|
||||||
val target = movedCursor(current, from, to)
|
val target = movedCursor(current, from, to)
|
||||||
|
lastPosition = target
|
||||||
windowJob?.cancel()
|
windowJob?.cancel()
|
||||||
val fromInWindow = from in windowStart..windowEnd
|
val fromInWindow = from in windowStart..windowEnd
|
||||||
val toInWindow = to in windowStart..windowEnd
|
val toInWindow = to in windowStart..windowEnd
|
||||||
@@ -447,6 +449,7 @@ class PlaybackController(
|
|||||||
return@withLock
|
return@withLock
|
||||||
}
|
}
|
||||||
val target = undo.cursor.coerceIn(0, restored.size - 1)
|
val target = undo.cursor.coerceIn(0, restored.size - 1)
|
||||||
|
lastPosition = target
|
||||||
val restoredId = queueRepository.itemAt(restored, target)?.song?.id
|
val restoredId = queueRepository.itemAt(restored, target)?.song?.id
|
||||||
windowJob?.cancel()
|
windowJob?.cancel()
|
||||||
val canMirror =
|
val canMirror =
|
||||||
@@ -951,6 +954,7 @@ class PlaybackController(
|
|||||||
} finally {
|
} finally {
|
||||||
updating = false
|
updating = false
|
||||||
}
|
}
|
||||||
|
lastPosition = target
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun rebuildWindow(
|
private fun rebuildWindow(
|
||||||
|
|||||||
@@ -733,6 +733,96 @@ class PlaybackControllerTest {
|
|||||||
eager.close()
|
eager.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun movingAQueuedTrackFromAnotherAlbumBeforeTheCurrentDoesNotConsumeIt() {
|
||||||
|
seedAlbum(3, sourceId = 1)
|
||||||
|
seedSong("b1", "al2")
|
||||||
|
seedSong("b2", "al2")
|
||||||
|
seedSong("c1", "al3")
|
||||||
|
val eagerHandle = FakePlayerHandle()
|
||||||
|
val inline =
|
||||||
|
object : kotlinx.coroutines.CoroutineDispatcher() {
|
||||||
|
override fun dispatch(
|
||||||
|
context: kotlin.coroutines.CoroutineContext,
|
||||||
|
block: Runnable,
|
||||||
|
) = block.run()
|
||||||
|
}
|
||||||
|
val eager = PlaybackController(sources, queues, FakePlayerConnection(eagerHandle), dispatcher = inline)
|
||||||
|
|
||||||
|
eager.playAlbum(1, "al1", 0)
|
||||||
|
await {
|
||||||
|
eager.state.value.item
|
||||||
|
?.id == "s1"
|
||||||
|
}
|
||||||
|
eager.addToQueue(1, QueueKind.Album, "al2")
|
||||||
|
eager.addToQueue(1, QueueKind.Song, "c1")
|
||||||
|
await { runBlocking { eager.upcomingItem()?.id } == "b1" }
|
||||||
|
|
||||||
|
eager.playAt(2)
|
||||||
|
await {
|
||||||
|
eager.state.value.item
|
||||||
|
?.id == "b2"
|
||||||
|
}
|
||||||
|
|
||||||
|
runBlocking { eager.move(3, 2) }
|
||||||
|
Thread.sleep(300)
|
||||||
|
|
||||||
|
assertEquals(
|
||||||
|
"b2",
|
||||||
|
eager.state.value.item
|
||||||
|
?.id,
|
||||||
|
)
|
||||||
|
assertEquals(3L, runBlocking { queues.snapshot().upNextSize })
|
||||||
|
eager.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun movingAQueuedTrackBeforeTheCurrentConsumesTheRightTrackOnSkip() {
|
||||||
|
seedAlbum(3, sourceId = 1)
|
||||||
|
seedSong("b1", "al2")
|
||||||
|
seedSong("b2", "al2")
|
||||||
|
seedSong("c1", "al3")
|
||||||
|
val eagerHandle = FakePlayerHandle()
|
||||||
|
val inline =
|
||||||
|
object : kotlinx.coroutines.CoroutineDispatcher() {
|
||||||
|
override fun dispatch(
|
||||||
|
context: kotlin.coroutines.CoroutineContext,
|
||||||
|
block: Runnable,
|
||||||
|
) = block.run()
|
||||||
|
}
|
||||||
|
val eager = PlaybackController(sources, queues, FakePlayerConnection(eagerHandle), dispatcher = inline)
|
||||||
|
|
||||||
|
eager.playAlbum(1, "al1", 0)
|
||||||
|
await {
|
||||||
|
eager.state.value.item
|
||||||
|
?.id == "s1"
|
||||||
|
}
|
||||||
|
eager.addToQueue(1, QueueKind.Album, "al2")
|
||||||
|
eager.addToQueue(1, QueueKind.Song, "c1")
|
||||||
|
await { runBlocking { eager.upcomingItem()?.id } == "b1" }
|
||||||
|
eager.playAt(1)
|
||||||
|
await {
|
||||||
|
eager.state.value.item
|
||||||
|
?.id == "b1"
|
||||||
|
}
|
||||||
|
|
||||||
|
runBlocking { eager.move(3, 1) }
|
||||||
|
await {
|
||||||
|
eager.state.value.item
|
||||||
|
?.id == "b1"
|
||||||
|
}
|
||||||
|
|
||||||
|
eagerHandle.advanceTo(eagerHandle.currentIndex + 1)
|
||||||
|
await {
|
||||||
|
eager.state.value.item
|
||||||
|
?.id == "b2"
|
||||||
|
}
|
||||||
|
|
||||||
|
val queued = runBlocking { queues.snapshot().upNext.map { it.entry.refId } }
|
||||||
|
assertTrue("c1" in queued)
|
||||||
|
eager.close()
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun nextFollowsTheReorderedQueue() {
|
fun nextFollowsTheReorderedQueue() {
|
||||||
seedAlbum(3, sourceId = 1)
|
seedAlbum(3, sourceId = 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user