Skip to content

fix(audio-cue): recover when resume() is rejected or wakes non-running#757

Open
appergb wants to merge 1 commit into
betafrom
fix/audio-cue-resume-recover
Open

fix(audio-cue): recover when resume() is rejected or wakes non-running#757
appergb wants to merge 1 commit into
betafrom
fix/audio-cue-resume-recover

Conversation

@appergb

@appergb appergb commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

User description

问题

长时间使用后,「开始录音」提示音在 macOS 上会莫名消失,重启 App 才恢复。

根因

提示音是前端 Web Audio 即时合成(src/lib/audioCue.ts),后端不参与。长时间运行中,本地 ASR / 录音反复抢占系统音频会话,WKWebView 的共享 AudioContext 被系统推进 suspended 或 WebKit 非标准的 interrupted。此前的修复(b4636f9)只在 closed 时重建,漏掉了真实的退化路径:

  1. resume() 被拒 → 旧逻辑 .catch(() => undefined) 把失败静默吞掉,不重建、不重试;
  2. resume() 名义 resolve 但 ctx 仍非 running → 在冻结的 currentTime 上排期,oscillator 排到过去时刻,静默且堆积节点

两条路都让提示音永久静默,直到进程重启 —— 与用户「重启 App 后恢复」的现象吻合。

修复

把这两种「唤不醒」态统一判为可恢复:丢弃坏死的 ctx、用全新 ctx 重试一次(限一次,防止在坏死 ctx 上无限递归),让共享 AudioContext 自愈。处置决策抽成纯函数 cueActionAfterResume 以便单测。仅改 audioCue.tsplayRecordStartCue() 对外签名与契约不变。

测试

  • 新增 cueActionAfterResume 回归用例:running→排期 / superseded 或迟到→丢弃 / resume 被拒或唤不醒→重建重试 / 重试后仍失败→放弃(防死循环)。
  • npx tsx src/lib/audioCue.test.ts 全绿;tsc --noEmit 零报错(均在干净 beta 上验证)。

验证建议

此 bug 需长时间暴露。建议出测试版后:频繁触发录音(尤其切到本地 ASR,最重度抢占音频会话)数十~上百次,或挂后台长时间,确认「开始叮咚」不再消失;若设置页「试听」也哑,即共享 ctx 已退化,本修复会在下次播放时自动重建恢复。


PR Type

Bug fix, Tests


Description

  • Introduce cueActionAfterResume for resume outcome decisions

  • Handle resume rejection or non-running state by recreating context (once)

  • Add unit tests for the new decision logic

  • Fix silent audio cue failures due to frozen AudioContext


Diagram Walkthrough

flowchart LR
  start["start"] --> getContext["getContext"]
  getContext --> isRunning{"ctx running?"}
  isRunning -- "yes" --> schedule["scheduleCueVoices"]
  isRunning -- "no" --> resume["ctx.resume()"]
  resume --> settle{"settle(runningAfterResume)"}
  settle -- "running & shouldPlay" --> schedule
  settle -- "running & !shouldPlay" --> drop["drop"]
  settle -- "!running & shouldPlay" --> allowRecreate{"allowRecreate?"}
  allowRecreate -- "yes" --> recreate["discardContext + playRecordStartCueOnce(false)"]
  allowRecreate -- "no" --> drop
Loading

File Walkthrough

Relevant files
Tests
audioCue.test.ts
Add unit tests for resume recovery logic                                 

openless-all/app/src/lib/audioCue.test.ts

  • Added regression tests for the new cueActionAfterResume function
  • Tests cover all four scenarios: running & shouldPlay, running &
    !shouldPlay, not running & allowRecreate, not running & !allowRecreate
+36/-0   
Bug fix
audioCue.ts
Fix audio cue resume recovery with bounded retry                 

openless-all/app/src/lib/audioCue.ts

  • Extracted cueActionAfterResume pure function to decide action after
    resume
  • Added discardContext helper to clean up and recreate dead AudioContext
  • Restructured playRecordStartCue into playRecordStartCueOnce with
    bounded retry (once)
  • Resume rejection or non-running state now triggers context recreation
    instead of silent failure
+82/-25 

Long sessions park the shared AudioContext in 'suspended' / WebKit
'interrupted' after the system audio session is repeatedly preempted by
ASR and recording. The earlier fix only recreated a 'closed' context: a
rejected resume() was swallowed and a resume() that resolved without
reaching 'running' scheduled voices on a frozen currentTime, both of
which leave the start cue permanently silent until the app restarts.

Treat both "won't wake" states as recoverable — discard the dead context
and retry once on a fresh one, bounded to avoid infinite recreation. The
decision is extracted into the pure, unit-tested cueActionAfterResume.
@github-actions

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ No major issues detected

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant