fix(recording): 录音条出现时 mic 已 capture,不再吞开头字#289
Conversation
用户反馈:按 Option 键后看到录音条,但开口的前几个字被吞掉。 根因:begin_session() 在 line 1128 立刻 emit_capsule(Recording),但 Recorder::start 还要再走 ~57 行(read creds / new ASR / new bridge / acquire mute / cpal init),其中 cpal init 在 macOS 上有 50-200ms 启动 延迟。窗口期内: - 视觉:录音条已显示 - 物理:mic 还没产生第一帧 PCM - 用户开口的前几个字物理上无法被 capture(不是丢字,是没录到) DeferredAsrBridge 已经能覆盖 "recorder 已开 → ASR 没连上" 的间隙(缓存 PCM 等 ASR attach 后按顺序后送),但 "录音条已显 → recorder 没开" 这段 之前没人覆盖。 修复(surgical):把 emit_capsule(Recording) 从 begin_session() 挪到 start_recorder_for_starting() 内部 Recorder::start 成功的 Ok 分支里。 三条 ASR 路径(Local Qwen3 / Whisper / Volcengine)共用同一函数,自动 受益。代价是用户按热键到看到录音条多 50-200ms 延迟,但「录音条出现 = mic 100% 在 capture」体感诚实,开头不再丢。 ASR 慢连接的兜底逻辑(DeferredAsrBridge 按顺序缓冲 + attach 后 drain) 保持不变。
PR Reviewer Guide 🔍(Review updated until commit 5c5b575)Here are some key observations to aid the review process:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 02dd8c56ea
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
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".
| // mic 还没开」的 50-200ms 窗口里开口讲话被吞。begin_session 把这一行 | ||
| // 挪到这里以后,三条 ASR 路径(Local / Whisper / Volcengine)共享。 | ||
| // ASR 连接慢的间隙仍由 DeferredAsrBridge 缓存 PCM,按顺序后送,不丢字。 | ||
| emit_capsule(inner, CapsuleState::Recording, 0.0, 0, None, None); |
There was a problem hiding this comment.
Avoid re-showing Recording after a queued stop
When the user stops or cancels while Recorder::start is still initializing, the stop path can already have emitted Transcribing/Cancelled or moved the session out of Starting; this new unconditional emit then overwrites the UI back to Recording after stop_recorder_if_pending_start_stop runs, and on the Volcengine path it can remain that way while open_session().await continues. Gate this emit on the session still being the active Starting session with no pending stop/cancel, or emit it before the pending-stop handling only when the recorder is actually going to keep running.
Useful? React with 👍 / 👎.
Codex P2 (PR #289):上一版我把 emit_capsule(Recording) 放到 stop_recorder_if_pending_start_stop 之后,且无条件 emit。这会让短按热键 (用户在 Recorder::start 仍在 cpal init 期间松开)的场景出现 UI 闪烁: stop / cancel 路径已经发出 Transcribing / Cancelled,本行又把 UI 覆盖 回 Recording,并在 Volcengine 路径的 open_session().await 期间持续保持。 修复(按 Codex 建议): 1. emit 挪到 stop_recorder_if_pending_start_stop 之前 2. emit 之前在同一 lock 内检查 phase / pending_stop / cancelled / session_id; 只有 recorder 真的会继续运行时才 emit 3. 不满足条件就让 stop / cancel 路径接管 UI 信号 不改变首次说话不丢字的修复目标(emit 仍在 Recorder::start 成功后), 只让 race 路径下的 UI 一致。
|
修法按你的建议:
短按场景下不再出现 Recording 闪烁覆盖 Transcribing/Cancelled 的问题。 |
|
To use Codex here, create an environment for this repo. |
|
Persistent review updated to latest commit 5c5b575 |
主要修复 ───────── - fix(windows): TSF DLL 静态链接 CRT,防 host 进程(QQ 等)私有 MSVCP140 劫持导致切微软拼音崩溃 0xc0000005(PR #287 + #289 + Codex P1 review)。 - fix(windows): 移除 DisableThreadLibraryCalls — /MT 下 CRT 需要 thread attach/detach 通知做 per-thread TLS 清理,禁用反而触发崩溃。 - fix(recording): 录音条出现时 mic 已 capture,按 Option 不再吞开头 字。emit Recording 加 race 检查避免短按时覆盖 stop/cancel UI 信号。 - fix(startup): splash 透明背景 + 卡片化,避免长启动时左半白屏。 - fix(vault): keyring chunks 用稳定 account name —— macOS Keychain Always-Allow 不再因 UUID 轮换失效;不再每次 load 都尝试删 9 个 legacy entries 触发 ACL 弹窗(与 PR #277 一起)。 新功能 ───────── - feat(security): 凭据从 plaintext JSON 迁移到平台 credential vault (macOS Keychain / Windows Credential Manager / Linux libsecret)。 Windows 2560 byte 限制下用 chunked storage;partial-write safe。 legacy 多源迁移完成后自动清理(PR #277)。 工程 ───────── - chore(ci): 暂时搁置 macos-13(Intel mac)matrix —— GH Actions runner pool 紧张到每次 dispatch queue 1-2h(已观测 4 次)。Apple Silicon 用户 dmg 仍发;Intel mac 用户保留 v1.2.20 dmg + Rosetta 说明(issue #299)。 - chore(devex): CI 加 macOS 矩阵 + 5-way 版本号一致性校验 + scripts/bump-version.sh 一行同步 5 处版本号。.gitignore 加 promo-openless-v2 / node_modules / dist / target 全局兜底(PR #294)。 - docs: docs/audit-2026-05-06.md 工程审计基线,覆盖架构 / UI bug / 多端一致性 / IPC 1:1 校验 / P0/P1/P2 改进路径。 - chore: 9 个改进 issue (#295-#303) 跟踪 audit 列出的所有 P1/P2/P3 问题。
User description
用户反馈
按 Option 键后已经看到录音条,但开口的前几个字被吞掉,应该立即开始录音。如果当时 ASR 没连上不要紧,按顺序发送即可(这部分 `DeferredAsrBridge` 已经做了,本次不动)。
根因
`coordinator.rs::begin_session()` 在 `line 1128` 就 `emit_capsule(Recording)`,但 `Recorder::start` 还要再走 ~57 行(read creds / new ASR / new bridge / acquire mute / cpal init),其中 cpal init 在 macOS 上有 50-200ms 启动延迟。
窗口期内:
`DeferredAsrBridge` 已经覆盖 「recorder 已开 → ASR 没连上」的间隙(缓存 PCM 等 ASR attach 后按顺序后送),但 「录音条已显 → recorder 没开」之前没人覆盖。
修复(surgical)
`emit_capsule(Recording)` 从 `begin_session()` 挪到 `start_recorder_for_starting()` 内部 `Recorder::start` 成功的 `Ok` 分支:
```diff
// 不在这里 emit Recording:让 start_recorder_for_starting 在 Recorder::start
// 成功后再发,确保「录音条出现」时 mic 已经在 capture。
match Recorder::start(consumer, level_handler) {
Ok((rec, runtime_errors)) => {
...
log::info!("[coord] recorder started ...");
```
三条 ASR 路径(Local Qwen3 / Whisper / Volcengine)共用 `start_recorder_for_starting`,自动受益。代价是用户按热键到看到录音条多 ~50-200ms 延迟,但「录音条出现 = mic 100% 在 capture」体感诚实。
不动的部分(已合规)
Test plan
PR Type
Bug fix
Description
延后
Recordingcapsule 发送时机改为录音器启动成功后再显示录音条
增加 stop/cancel 竞态保护
避免短按热键时状态闪回
Diagram Walkthrough
File Walkthrough
coordinator.rs
Delay recording capsule until mic startsopenless-all/app/src-tauri/src/coordinator.rs
emit_capsule(Recording)frombegin_session().Recordingonly afterRecorder::startreturnsOk.Starting,pending_stop, andcancelled.stop/canceltransitions during startup.