playback: retain durable reset identity through Media3

This commit is contained in:
austinried
2026-11-14 12:05:28 +00:00
parent 01ccc6475d
commit fd0eb0a9a0
4 changed files with 44 additions and 6 deletions
@@ -266,6 +266,7 @@ internal fun MediaItem.resetItem(): QueueResetItem? {
upNext = extras.getBoolean(EXTRA_RESET_UP_NEXT),
entryOccurrence = extras.getInt(EXTRA_RESET_ENTRY_OCCURRENCE),
occurrence = extras.getLong(EXTRA_RESET_OCCURRENCE),
originEntryId = extras.takeIf { it.containsKey(EXTRA_RESET_ORIGIN_ENTRY_ID) }?.getLong(EXTRA_RESET_ORIGIN_ENTRY_ID),
)
}
@@ -291,6 +292,7 @@ private const val EXTRA_RESET_POSITION = "resetPosition"
private const val EXTRA_RESET_UP_NEXT = "resetUpNext"
private const val EXTRA_RESET_ENTRY_OCCURRENCE = "resetEntryOccurrence"
private const val EXTRA_RESET_OCCURRENCE = "resetOccurrence"
private const val EXTRA_RESET_ORIGIN_ENTRY_ID = "resetOriginEntryId"
internal fun MediaItem?.replayGain(): ReplayGain {
val extras = this?.mediaMetadata?.extras ?: return ReplayGain()
@@ -348,6 +350,7 @@ internal fun queueMediaItem(
putBoolean(EXTRA_RESET_UP_NEXT, reset.upNext)
putInt(EXTRA_RESET_ENTRY_OCCURRENCE, reset.entryOccurrence)
putLong(EXTRA_RESET_OCCURRENCE, reset.occurrence)
reset.originEntryId?.let { putLong(EXTRA_RESET_ORIGIN_ENTRY_ID, it) }
}
},
).build()
@@ -48,6 +48,15 @@ class FakePlayerHandle : PlayerHandle {
@Volatile
var beforeRemoveFirst: (() -> Unit)? = null
@Volatile
var roundTripItemsThroughMedia3 = false
private fun storedItem(item: QueueItem): QueueItem {
if (!roundTripItemsThroughMedia3) return item
val mediaItem = queueMediaItem(item, local = null, stream = "http://stream/${item.id}", transcode = false)
return queueItemFromMediaItem(mediaItem)
}
private fun checkThread() {
val expected = expectedThread ?: return
check(Thread.currentThread() == expected) { "Expected $expected, was ${Thread.currentThread()}" }
@@ -92,7 +101,7 @@ class FakePlayerHandle : PlayerHandle {
item: QueueItem,
) {
operations += "replaceItem($index, ${item.id})"
if (index in items.indices) items[index] = item
if (index in items.indices) items[index] = storedItem(item)
}
override fun setWindow(
@@ -102,7 +111,7 @@ class FakePlayerHandle : PlayerHandle {
) {
operations += "setWindow(size=${items.size}, start=$startIndex, position=$startPositionMs)"
this.items.clear()
this.items += items
this.items += items.map(::storedItem)
index = startIndex
positionMs = startPositionMs
idle = true
@@ -115,13 +124,13 @@ class FakePlayerHandle : PlayerHandle {
override fun addFirst(item: QueueItem) {
operations += "addFirst(${item.id})"
items.add(0, item)
items.add(0, storedItem(item))
index++
}
override fun addLast(item: QueueItem) {
operations += "addLast(${item.id})"
items += item
items += storedItem(item)
}
override fun insertAt(
@@ -129,7 +138,7 @@ class FakePlayerHandle : PlayerHandle {
item: QueueItem,
) {
operations += "insertAt($index, ${item.id})"
items.add(index, item)
items.add(index, storedItem(item))
if (index <= this.index) this.index++
}
@@ -106,7 +106,15 @@ class MediaItemTest {
@Test
fun theItemKeepsItsResetIdentity() {
val resetItem =
QueueResetItem(QueueOrigin(QueueKind.Playlist, 1, "p1"), "s1", 2, upNext = false, entryOccurrence = 1, occurrence = 1)
QueueResetItem(
QueueOrigin(QueueKind.Playlist, 1, "p1"),
"s1",
2,
upNext = false,
entryOccurrence = 1,
occurrence = 1,
originEntryId = 7,
)
val item = queueMediaItem(song.copy(resetItem = resetItem), local = null, stream = "http://server/stream?s=s1", transcode = false)
@@ -3611,6 +3611,24 @@ class PlaybackControllerTest {
assertEquals(3L, controller.state.value.position)
}
@Test
fun aResetKeepsAMovedSecondDuplicateContextEntryByDurableIdentity() {
handle.roundTripItemsThroughMedia3 = true
seedPlaylist(2)
runBlocking { queues.replace(listOf(queues.playlistEntry(1, "p1"), queues.playlistEntry(1, "p1"))) }
controller.connect()
await { controller.state.value.item != null }
controller.playAt(3)
await { handle.currentItem?.id == "s2" && controller.state.value.position == 3L }
runBlocking { controller.move(2, 0) }
val point = runBlocking { queues.captureResetPoint() }
runBlocking { queues.resetChangedOrigins(setOf(QueueOrigin(QueueKind.Playlist, 1, "p1")), point) }
assertEquals(1L, controller.state.value.position)
assertEquals("s2", handle.currentItem?.id)
}
@Test
fun repeatedResetsKeepTheSecondDuplicateUpNextEntry() {
seedAlbum(3, sourceId = 1)