android / Build + lint + test (push) Failing after 1m11s
Two changes for the Sonos stutter the operator describes as rapid play-pause-play at the start of a track. The measurable one: nothing in the diagnostics could see it. player_state records source/loading/error but not whether we are playing, track_change needs the queue index to move, and the heartbeat samples once every 45s. A few seconds of oscillation that changes no index fell through all three, which is why the symptom has been described repeatedly and measured never. The poll loop now publishes raw GetTransportInfo readings on change, and TransportFlapDetector turns a burst of them into one summary event carrying the sequence alongside local-vs-Sonos track and position -- enough to tell a cursor disagreement from the renderer rebuffering. It samples at the 1Hz poll cadence, so a faster oscillation lands aliased; that still answers whether the renderer is leaving PLAYING, which is the open question. The suspect one: during a cast the wrapped ExoPlayer was paused, not stopped. pause() is only playWhenReady=false -- LoadControl keeps loading, so the phone went on downloading the track the renderer was streaming, over the same WiFi, re-arming at every track change via syncLocalCursorToRemote's seekTo. At FLAC bitrates that is a second full-rate download competing with the speaker, beginning exactly when a new track does. stop() ends it; Media3 keeps media items, index and position, and getPlaybackState() already reports STATE_READY while remote, so cursor sync and handoff are unaffected. The route teardown re-prepares for local playback. Whether that download is the cause is unproven -- hence the instrument landing alongside it rather than after it.