Skip to content

Fix emulator freeze after iOS auto screen lock#66

Open
bokunoibasho wants to merge 1 commit into
ayvacs:mainfrom
bokunoibasho:fix/ios-lock-freeze
Open

Fix emulator freeze after iOS auto screen lock#66
bokunoibasho wants to merge 1 commit into
ayvacs:mainfrom
bokunoibasho:fix/ios-lock-freeze

Conversation

@bokunoibasho
Copy link
Copy Markdown

@bokunoibasho bokunoibasho commented May 27, 2026

Summary

On iOS Safari, an auto screen lock leaves the AudioContext reporting state === 'running' while the ScriptProcessor callback permanently stops firing. The audio buffer stays full, audioUnderrunAdjustment() throttles CPUCyclesTotal down to 0, and the emulator looks frozen on resume.

The existing setupWebAudio watchdog (originally for the Firefox/macOS scriptnode bug) already has the right recovery: XAudioJSWebAudioWatchDogLast is updated from inside the audio callback, so timeDiff > 500ms is a generic "node is dead" signal — it was just gated to Gecko. Two changes in modules/XAudioJS/XAudioServer.js:

  • Drop the navigator.userAgent.indexOf('Gecko/') gate so the watchdog runs everywhere.
  • Treat iOS's state === 'interrupted' the same as 'suspended' for context resume.

Net: +8 / −9 lines, single file.

iOS requires a user gesture to actually re-activate audio, so the user still has to tap once after unlock — but that single tap then reliably unsticks playback.

Test plan

  • iPhone Safari: 30s and 2min auto lock → unlock → tap → audio + emulation resume.
  • macOS Firefox / Chrome / Safari: long playback regression check (no spurious re-init).

On iOS Safari, an auto screen lock can leave the AudioContext with
state === "running" while the ScriptProcessor's onaudioprocess callback
permanently stops firing. The emulator's audio buffer then stays full,
audioUnderrunAdjustment() throttles CPUCyclesTotal down to 0 cycles
per timer iteration, and emulation appears frozen with all inputs
unresponsive even though the core setInterval is still ticking.

This reuses the existing watchdog primitive that was already in place
to recover Firefox/Mac OS X's "node randomly stops playing" bug -- the
recovery path (XAudioJSWebAudioWatchDogLast is updated from inside the
audio callback, so timeDiff > 500ms is a reliable "node is dead" signal)
applies equally to the iOS case. Two tiny changes:

- Drop the Gecko-only userAgent gate so the watchdog runs everywhere.
- Treat the iOS-specific state name "interrupted" the same as
  "suspended" so the context gets resumed on lock release too.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@bokunoibasho bokunoibasho marked this pull request as ready for review May 27, 2026 01:55
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.

1 participant