advance past a duplicate song when the playing queued track is deleted

Removing the currently playing track resolved its id against the updated
queue afterwards; if the same song also exists in the context, the lookup
matched that copy and jumped the cursor to it instead of advancing. When the
removed position was the playing one, use the fallback (next in the block /
slot fill) rather than a by-id lookup.

Pre-existing on main, exposed while testing the shuffle rework. Regression
test fails without the fix.
This commit is contained in:
austinried
2026-10-04 00:30:01 +00:00
parent 7f674f04be
commit 902f556fe6
2 changed files with 46 additions and 3 deletions
@@ -350,9 +350,23 @@ class PlaybackController(
}
var target =
(
currentId?.let {
if (wasUpNext) queueRepository.combinedIndexOf(updated, it) else queueRepository.combinedContextIndexOf(updated, it)
} ?: fallback
if (position == current) {
// The playing entry was removed; resolving its song id afterwards can match
// an identical song elsewhere (a duplicate in the context or the block), so
// advance from the fallback instead.
fallback
} else {
currentId?.let {
if (wasUpNext) {
queueRepository.combinedIndexOf(
updated,
it,
)
} else {
queueRepository.combinedContextIndexOf(updated, it)
}
} ?: fallback
}
).coerceIn(0, updated.size - 1)
// Removing the playing context track while shuffled leaves the block anchored to some
// other track; re-anchor it to the track the cursor landed on so the queued tracks
@@ -461,6 +461,35 @@ class PlaybackControllerTest {
}
}
@Test
fun removingThePlayingQueuedTrackAdvancesPastADuplicateInTheContext() {
seedAlbum(4, sourceId = 1)
seedSong("y1", "al2")
controller.playAlbum(1, "al1", 0)
await {
controller.state.value.item
?.id == "s1"
}
// "s2" is both queued and in the context; removing the queued copy must advance to the
// next queued track, not jump to the identical song in the context.
controller.addToQueue(1, QueueKind.Song, "s2")
controller.addToQueue(1, QueueKind.Song, "y1")
await { runBlocking { controller.upcomingItem()?.id } == "s2" }
controller.playAt(1)
await {
controller.state.value.item
?.id == "s2"
}
runBlocking { controller.removeAt(1) }
await {
controller.state.value.item
?.id == "y1"
}
}
@Test
fun playNextFromInsideTheBlockGoesAfterTheCurrentTrack() {
seedAlbum(3, sourceId = 1)