feat(android/diagnostics): per-skip track-change event
android / Build + lint + test (push) Successful in 3m35s
android / Build + lint + test (push) Successful in 3m35s
Heartbeats are 45s apart and missed a rapid skip burst (local_index 16→22 in one gap). Add a 'playback' track_change event emitted on each queue-index / current-track change, snapshotting local vs Sonos index+position + server_health + upnp_loading + route — so a transient skip-induced desync is captured at the instant it happens. (uiState is a conflated StateFlow, so a very rapid burst may coalesce intermediate indices; we still get the boundaries + the snapshot.) Refs Scribe M9 (#119), task #1210. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01K55iTxn95BtshocgdE1shW
This commit is contained in:
@@ -102,6 +102,7 @@ class DiagnosticsReporter @Inject constructor(
|
||||
launch { collectServerHealth() }
|
||||
launch { collectUpnpDrops() }
|
||||
launch { collectPlayerState() }
|
||||
launch { collectTrackChanges() }
|
||||
launch { collectRoutes() }
|
||||
launch { heartbeatLoop() }
|
||||
}
|
||||
@@ -162,6 +163,30 @@ class DiagnosticsReporter @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
// Emit a snapshot at each track/queue-index change — captures a
|
||||
// skip-induced local↔Sonos desync at the INSTANT it happens, which the
|
||||
// 45s heartbeat misses. Note: uiState is a conflated StateFlow, so a
|
||||
// very rapid skip burst may coalesce intermediate indices (we still get
|
||||
// the boundaries + the local-vs-Sonos snapshot).
|
||||
private suspend fun collectTrackChanges() {
|
||||
playerController.uiState
|
||||
.map { it.queueIndex to it.currentTrack?.id }
|
||||
.distinctUntilChanged()
|
||||
.collect { (index, _) ->
|
||||
val ui = playerController.uiState.value
|
||||
record("playback", buildJsonObject {
|
||||
put("event", "track_change")
|
||||
put("local_index", index)
|
||||
put("local_pos_ms", ui.positionMs)
|
||||
put("sonos_track", remoteState.trackNumber)
|
||||
put("sonos_pos_ms", remoteState.positionMs)
|
||||
put("upnp_loading", ui.isUpnpLoading)
|
||||
put("server_health", networkStatus.state.value.name)
|
||||
put("route", outputPicker.routesState.value.current.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun collectRoutes() {
|
||||
// 'playback' — route changes happen for all outputs. This only ever
|
||||
// logs the ACTIVE route (routesState.current), so no "connected" flag.
|
||||
|
||||
Reference in New Issue
Block a user