fix(desktop): suspend idle poof audio context - #2907
Conversation
Signed-off-by: kyinhub <kevinpyin@gmail.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. 🚀 Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
Signed-off-by: kyinhub <kevinpyin@gmail.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Keep it up! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
Thanks for picking this up — the node-disconnect plus debounced-suspend logic reads right for the lifecycle the issue describes. Flagging one path I don't think it covers, based on macOS measurements I've just posted on #2868.
On macOS it can't: when the embedder allows autoplay (the webview default Buzz takes), a freshly constructed Since One wrinkle if you do arm the suspension at construction: a plain // in createPoofAudioPlayer(), alongside play()
function armIdleSuspend(context: AudioContext) {
// WebKit returns a suspended context but auto-resumes it when the embedder
// allows autoplay, so an idle context can reach "running" with no playback.
// Re-arm on every transition instead of assuming play() is the only path.
context.addEventListener("statechange", () => {
if (context.state === "running" && activePlaybacks === 0) {
scheduleSuspend(context);
}
});
if (context.state === "running") scheduleSuspend(context);
}
return { armIdleSuspend, play };called once where the singleton is created: function getPoofAudioContext() {
try {
if (!poofAudioContext) {
poofAudioContext = new AudioContext({ latencyHint: "interactive" });
poofAudioPlayer.armIdleSuspend(poofAudioContext);
}
return poofAudioContext;
} catch {
return null;
}
}This composes with what you already have rather than duplicating it: The alternative is to not construct a hardware context at startup at all — decode via Happy to test either against my setup — the assertion is directly observable via |
|
Thanks for the concrete WebKit trace. Addressed in the latest commit: the singleton now arms an idle-suspend statechange listener when the AudioContext is created, so an autoplay-enabled context that transitions to running without any poof is suspended after the debounce. Active playback still cancels the timer before resume, and the listener only arms while there are zero active playbacks. Added a focused suspended → auto-running regression; the poof lifecycle suite passes 8/8. |
Signed-off-by: Kevin Yin <182213728+yinkev@users.noreply.github.com>
79560c2 to
49c1627
Compare
Summary
AudioBufferSourceNodeandGainNodewhen playback ends, keeping the WebAudio graph flatAudioContext1.5 seconds after the final sound and cancel that idle suspension when another poof startssuspend()so rapid clicks resume cleanly instead of starting into a newly suspended contextThis stays focused on the measured lifecycle leak. Sample-rate policy, offline decoding, and a native-audio rewrite remain separate work.
Related issue
Fixes #2868.
Closest existing PR: #2804 changes the poof asset codec for Linux decoding; this PR is orthogonal and fixes the context/node lifetime after playback.
Testing
pnpm checkpnpm typecheckNo visual changes.