Skip to content

fix(desktop): bound wedged stream-swaps with a timeout + recover a missed first-frame race - #327

Merged
badbread merged 1 commit into
mainfrom
fix/desktop-wedged-stream-swap
Jul 20, 2026
Merged

fix(desktop): bound wedged stream-swaps with a timeout + recover a missed first-frame race#327
badbread merged 1 commit into
mainfrom
fix/desktop-wedged-stream-swap

Conversation

@badbread

Copy link
Copy Markdown
Owner

Summary

Fixes #318 — a wall tile's stream-swap (main/sub override, zoom-to-main) could wedge forever with no recovery, and permanently disable that tile's stall watchdog in the process.

_WallTileState._load keeps the old player painting while the replacement ("pending") player decodes toward its first keyframe, promoting it once _onFirstFrame sees a real frame. There was no timeout: if the newly-selected stream's URL never decodes, _pending is never cleared. Since _reconnectStalled (the stall watchdog's recovery path) refuses to run at all whenever _pending != null — reasoning that a user-initiated swap must be resolving on its own — a wedged swap also silently disabled the watchdog for any later, unrelated real freeze on that same tile.

Separately: if the replacement player's width stream event fires very early — during the await player.open() call, before _pending is assigned — _onFirstFrame's identical(player, _pending) check drops it, since _pending isn't set yet. That first-frame signal is then lost, leaving the swap dependent on a second signal that may never come.

Changes

  • New Timer? _pendingTimeout field + _pendingSwapTimeout (15s) constant. Armed whenever a player is parked in _pending; on expiry, _onPendingSwapTimedOut retires the wedged player (detached, per the existing _disposePlayerDetached/Desktop: ~10s UI freeze on maximize/restore transitions (main-stream player teardown blocks the UI thread) #105 discipline) and clears _pending, re-enabling the stall watchdog.
  • The timer is cancelled wherever _pending is otherwise cleared: _onFirstFrame (successful promotion) and dispose() (tile teardown).
  • Right after _pending = player is assigned, re-check player.state.width once and call _onFirstFrame synchronously if it's already positive — recovers the race where the first-frame signal arrived during the preceding open() await.

Verification

Not build-verified — the Flutter desktop client builds on a separate Windows host (winbuild) not available this session; no flutter analyze/flutter test was run. Read the full _WallTileState swap/promote/dispose lifecycle by hand to confirm the timer is armed/cancelled on every path that sets or clears _pending (swap-start, promote, timeout, and tile dispose) with no double-dispose or leaked timer. Please run flutter analyze and exercise a stream-swap to a dead URL on winbuild before merging.

🤖 Generated with Claude Code

…ssed first-frame race

A wall tile's stream-swap (main/sub override, zoom-to-main) parks the
replacement player in _pending and keeps the old player painting until the
replacement's first frame. If the new stream's URL never decodes, the swap
never completed and _reconnectStalled's "a swap is in flight" guard
(_pending != null) permanently disabled that tile's stall watchdog too - a
later, unrelated real freeze on the same tile was then never auto-reconnected
either.

Arm a 15s timeout whenever a player is parked in _pending; on expiry, retire
it (detached, #105) and clear _pending so the watchdog resumes normal
operation. Also re-check player.state.width once right after _pending is
assigned, since a first-frame width event that fires during the preceding
`await player.open()` (before _pending is set) is otherwise silently dropped
by _onFirstFrame's identical(player, _pending) check.

Fixes #318

Signed-off-by: badbread <badbread@users.noreply.github.com>
@badbread
badbread merged commit dadf6ec into main Jul 20, 2026
6 checks passed
@badbread
badbread deleted the fix/desktop-wedged-stream-swap branch July 20, 2026 19:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Desktop: wedged stream-swap tile freezes forever and disables its stall watchdog

1 participant