Repository navigation
Resume WebAudio after the audio service dies - #6
Merged
Merged
Conversation
When Chromium's audio service process dies, a page's AudioContext fires error and is suspended. Nothing resumes it, so a browser source with "Control audio via OBS" delivers no audio until the page reloads. tests/audio_recovery.py plays a local WebAudio tone through real browser sources, SIGKILLs only this run's audio service process and requires packets again in the fifth second after the kill: one kill, three kills on a page that replaces and freezes globals, a context the page suspended itself (must stay suspended), and two sources. All four scenarios fail on master: 0 packets after the kill. Refs obs-rust/obs-studio#2328
When Chromium's audio service process dies (a crash, or on Windows the hang monitor ending it), every running AudioContext fires "error" and is suspended. Nothing resumes it, so the source stays silent until the page reloads (obsproject/obs-studio#10586). OnContextCreated now runs a script in every frame before the page's own scripts. It wraps AudioContext (and webkitAudioContext, where present) in a Proxy, so pages still see the native constructor, prototype, name and methods, and watches each context's error event. A context the page wanted running is resumed; one the page suspended itself is left alone (suspend() is wrapped to track that). OfflineAudioContext is untouched. The script uses only what it captured before the page ran, so pages that replace or freeze globals do not break it. The first resume waits 3 s: with "Control audio via OBS", CEF keeps the dead capture stream until the page has been silent for 2 s, and a context resumed sooner keeps playing into it. Later attempts retry every 1 to 2 s. tests/audio_recovery.py: 4 of 4 scenarios pass; audio is back 3.1 to 3.2 s after each kill. Fixes obs-rust/obs-studio#2328
This was referenced Oct 25, 2026
zackees
approved these changes
Oct 25, 2026
zackees
left a comment
Member
There was a problem hiding this comment.
Coordinator review: approved.
- The injected script uses only functions it captured before the page runs (Reflect, Promise#then, addEventListener, WeakMap/Map methods, setTimeout), so pages that replace or freeze globals can't break it. Object spread in the descriptor copies can't be hooked by the page either.
- A suspend the page started itself turns off the resume through
wanted/pendingSuspends;closedends a chain; eacherrorstarts a new chain and ends the old one. That covers the repeated-kill case. constructorandsuspendare replaced only when the descriptor still holds the native value, and aliases share one proxy throughwrapped.instanceofand identity hold.- An Eval failure leaves the page with the native AudioContext. RED → GREEN is documented, and the harness lives in
tests/like the others; it can't run in CI (needs CEF and X).
This was referenced Oct 25, 2026
chriscoveries
pushed a commit
to chriscoveries/obsrust-fork
that referenced
this pull request
Oct 25, 2026
…s) (obs-rust#2378) Moves plugins/obs-browser from 7bab463c9 to 4583599fe, with: - obs-rust/obs-browser#3: the CEF sandbox where it can work on Linux (obs-rust#1092) - obs-rust/obs-browser#5: os_event_init results checked in module load and ExecuteOnBrowser - obs-rust/obs-browser#6: WebAudio resumes after the audio service dies (obs-rust#2328) - obs-rust/obs-browser#7: browser audio that arrives too late to mix is dropped (obs-rust#2335) - obs-rust/obs-browser#8: the fallback warning when the sandbox can't work (obs-rust#2218) One bump instead of three (obs-rust#2333, obs-rust#2351 and ours), as agreed on obs-rust#2351. docs/features.md gets the two audio lines from obs-rust#2333 and obs-rust#2351 as they are, a line for the sandbox, and the browser on the fallback warning line, which obs-rust#2238 merged on 2026-10-25. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs obs-rust/obs-studio#2328 (upstream obsproject/obs-studio#10586)
What and why
When Chromium's audio service process dies (a crash, a device change, or on Windows the audio hang monitor ending it after 3 minutes), every running
AudioContextfireserrorand is suspended. Chromium never resumes it, so a browser source that plays through WebAudio (alert boxes) stays silent until the page reloads or "Control audio via OBS" is toggled. Pages that usenew Audio()recover by themselves.BrowserApp::OnContextCreatednow evaluates a small script in every frame, before the page's own scripts, next to theobsstudiobindings:AudioContext(andwebkitAudioContextwhere present) is wrapped in aProxy, so pages still see the native constructor, prototype, name, length, property descriptors and[native code];instanceof, subclassing andctx.constructor === AudioContextbehave as before.errorevent, a context the page wanted running is resumed. One the page suspended itself (suspend()is wrapped, the same way, to track that) is left alone.OfflineAudioContextis untouched.Reflect,Promise.prototype.then,EventTarget.prototype.addEventListener,WeakMap,Function.prototype.call,setTimeoutorAudioContext.prototype.resumedo not break it.kRecentlyAudibleTimeoutinAlloyBrowserHostImpl), and only starts a new one after that; a context resumed sooner plays into the dead stream for as long as it stays audible. Later attempts retry every 1 to 2 s, and a new error starts a new chain.RED → GREEN
test: WebAudio after the audio service dies(9e1be0d):tests/audio_recovery.pyplays a local WebAudio tone through real browser sources withreroute_audio, SIGKILLs only this run's audio service process (found through the harness's process tree) and requires packets in the 5th second after the kill. On master all 4 scenarios fail with 0 packets:plain,repeated(3 kills, on a page that replaces and freezes globals),page-suspend,two-sources.Resume WebAudio after the audio service dies(654d2b9): 4 of 4 pass; audio is back 3.1 to 3.2 s after every kill. Inpage-suspendthe context the page suspended stays suspended;plainalso checks that the page sees an unchangedAudioContext(passes before and after).tests/harnesses it needs a CEF build and an X display, and it kills a Chromium process. Seetests/README.mdfor the command.Notes