replace only the queue items a delete actually changed

Deleting any download re-set the whole queue window, which makes the player
re-prepare the current item and audibly pauses. It now rebuilds only the items
whose local file appeared or went away, comparing what the window reports with
what the repository can resolve, and falls back to the full reload when the
window no longer lines up.
This commit is contained in:
austinried
2026-10-04 00:30:04 +00:00
parent eccf14aa58
commit 2459d7cb93
5 changed files with 80 additions and 3 deletions
@@ -61,6 +61,20 @@ class FakePlayerHandle : PlayerHandle {
override val currentItem: QueueItem? get() = items.getOrNull(index)
/** Overrides the URIs the window reports; defaults to a stream URI per item. */
@Volatile
var uris: List<String> = emptyList()
override fun itemUris(): List<String?> = uris.ifEmpty { items.map { "http://stream/${it.id}" } }
override fun replaceItem(
index: Int,
item: QueueItem,
) {
operations += "replaceItem($index, ${item.id})"
if (index in items.indices) items[index] = item
}
override fun setWindow(
items: List<QueueItem>,
startIndex: Int,
@@ -2300,7 +2300,7 @@ class PlaybackControllerTest {
}
@Test
fun refreshingMediaItemsRebuildsTheWindowAtTheSamePosition() {
fun refreshingMediaItemsLeavesAnUnaffectedQueueAlone() {
seedAlbum(3, sourceId = 1)
controller.playAlbum(1, "al1", 0)
await { handle.operations.contains("play") }
@@ -2308,12 +2308,28 @@ class PlaybackControllerTest {
val windows = handle.operations.count { it.startsWith("setWindow") }
controller.refreshMediaItems()
Thread.sleep(200)
await { handle.operations.count { it.startsWith("setWindow") } > windows }
assertEquals(windows, handle.operations.count { it.startsWith("setWindow") })
assertEquals("s1", handle.currentItem?.id)
assertEquals(42_000L, handle.positionMs)
}
@Test
fun refreshingMediaItemsReplacesOnlyAnItemWhoseLocalFileIsGone() {
seedAlbum(3, sourceId = 1)
controller.playAlbum(1, "al1", 0)
await { handle.operations.contains("play") }
val windows = handle.operations.count { it.startsWith("setWindow") }
handle.uris = handle.items.mapIndexed { index, item -> if (index == 0) "file:///gone" else "http://stream/${item.id}" }
controller.refreshMediaItems()
await { handle.operations.any { it.startsWith("replaceItem(0,") } }
assertEquals(windows, handle.operations.count { it.startsWith("setWindow") })
assertEquals(0, handle.operations.count { it.startsWith("replaceItem(1,") })
}
@Test
fun positionTickerAdvancesPositionWithoutEmittingPlaybackState() =
runBlocking {