Reported: on poor coverage a track ends and the next (uncached) track
never starts — streams, hangs, no retry. Root cause: a buffering stall
emits NO error event so the onError path never fires and there was no
stall watchdog; even on a real error _handlePlaybackError immediately
skipped the literal-next (likely also-unreachable) source with no retry.
- _reconcileStallWatchdog: while playing+buffering, a 15s window; if
buffered position hasn't advanced it's a dead stream → recover; if
progressing, re-arm (slow-but-downloading is fine). Driven from
_broadcastState like the idle/position reconcilers.
- _recoverPlayback unifies stall + onError: retry the SAME track once
(skipToQueueItem rebuilds a fresh source/HTTP — a transient blip no
longer loses it); on exhaustion, surface via the #58 SnackBar and
skip to the next cached track, else pause (no thrashing through
unreachable streams).
- per-track retry budget resets when a track reaches ready+playing.
- _handlePlaybackError now delegates into the unified path.
Core playback change — device-verify on a throttled connection.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>