scrobble: only dedupe when the track was already announced

The now-playing suppression keyed off the item id alone, so a track loaded while paused (which sets the tracked id without announcing) never sent its first now-playing when playback then started from the beginning. Gate the suppression on nowPlayingSent as well, and cover that case plus the transition at the combine boundary.
This commit is contained in:
austinried
2026-10-09 12:31:48 +00:00
parent cfb663c250
commit 20c6ba526f
3 changed files with 40 additions and 2 deletions
@@ -170,6 +170,14 @@ class ScrobblePolicyTest {
)
}
@Test
fun announcesWhenATrackLoadedWhilePausedStartsFromTheBeginning() {
val item = track(id = "s1")
assertNull(policy.advance(item, 200_000, isPlaying = false, positionMs = 0))
assertNull(policy.advance(item, 200_000, isPlaying = false, positionMs = 2_500))
assertEquals(Scrobble.NowPlaying("s1"), policy.advance(item, 200_000, isPlaying = true, positionMs = 0))
}
@Test
fun aTransitionDoesNotReannounceTheNewTrackOnAStaleLowPosition() {
val first = track(id = "s1")
@@ -190,4 +190,34 @@ class ScrobblerTest {
enabled.value = true
assertEquals(listOf(Call.NowPlaying("s1")), sink.calls)
}
@Test
fun aTransitionSendsNowPlayingOnceForTheNewTrack() =
runTest {
val sink = RecordingSink()
val scrobbler = Scrobbler(sink = sink, enabled = flowOf(true), scope = testScope(), policy = ScrobblePolicy(now = { 1_000L }))
val state = MutableStateFlow(PlaybackState())
val position = MutableStateFlow(0L)
scrobbler.attach(state, position)
val first = QueueItem("s1", "One", "Artist", "Album", null, durationMs = 30_000)
val second = QueueItem("s2", "Two", "Artist", "Album", null, durationMs = 30_000)
state.value = PlaybackState(item = first, isPlaying = true)
var tick = 500L
while (tick <= 15_000L) {
position.value = tick
tick += 500
}
position.value = 0L
state.value = PlaybackState(item = second, isPlaying = true)
assertEquals(
listOf(
Call.NowPlaying("s1"),
Call.Scrobble("s1", 1_000L),
Call.NowPlaying("s2"),
),
sink.calls,
)
}
}