Skip to content

fix(recording): 录音条出现时 mic 已 capture,不再吞开头字#289

Merged
appergb merged 2 commits into
mainfrom
fix/recording-no-swallow-first-words
May 6, 2026
Merged

fix(recording): 录音条出现时 mic 已 capture,不再吞开头字#289
appergb merged 2 commits into
mainfrom
fix/recording-no-swallow-first-words

Conversation

@appergb
Copy link
Copy Markdown
Collaborator

@appergb appergb commented May 6, 2026

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 启动延迟

窗口期内:

  • 视觉:录音条已显示 → 用户以为可以说话了
  • 物理:mic 还没产生第一帧 PCM
  • 用户开口的前几个字 物理上无法被 capture(不是丢字,是没录到)

`DeferredAsrBridge` 已经覆盖 「recorder 已开 → ASR 没连上」的间隙(缓存 PCM 等 ASR attach 后按顺序后送),但 「录音条已显 → recorder 没开」之前没人覆盖。

修复(surgical)

`emit_capsule(Recording)` 从 `begin_session()` 挪到 `start_recorder_for_starting()` 内部 `Recorder::start` 成功的 `Ok` 分支:

```diff

  • emit_capsule(inner, CapsuleState::Recording, 0.0, 0, None, None); // 提前 emit 删除
  • // 不在这里 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 ...");

  •        // ★ 录音器实际启动后再发 Recording capsule
    
  •        emit_capsule(inner, CapsuleState::Recording, 0.0, 0, None, None);
       }
    

```

三条 ASR 路径(Local Qwen3 / Whisper / Volcengine)共用 `start_recorder_for_starting`,自动受益。代价是用户按热键到看到录音条多 ~50-200ms 延迟,但「录音条出现 = mic 100% 在 capture」体感诚实。

不动的部分(已合规)

  • `DeferredAsrBridge` 按顺序缓冲 PCM + ASR attach 后 drain — 保留
  • `finish_starting_session` cancel race 检查 — 保留
  • error / cancel 路径的 emit_capsule(Error) — 保留

Test plan

  • `cargo check` 通过
  • CI `Windows Tauri checks` + `pr_agent_job`
  • 真机 mac:按 Option 立刻说"测试一二三",转录出"测试一二三"完整无丢
  • 真机 mac:按 Option 立刻看到录音条 + 50-200ms 延迟体感可接受

PR Type

Bug fix


Description

  • 延后 Recording capsule 发送时机

  • 改为录音器启动成功后再显示录音条

  • 增加 stop/cancel 竞态保护

  • 避免短按热键时状态闪回


Diagram Walkthrough

flowchart LR
  A["begin_session"] -- "prepare session" --> B["start_recorder_for_starting"]
  B -- "Recorder::start succeeds" --> C["emit Recording capsule"]
  B -- "stop/cancel pending" --> D["skip overwrite"]
  C -- "mic already capturing" --> E["user speaks safely"]
Loading

File Walkthrough

Relevant files
Bug fix
coordinator.rs
Delay recording capsule until mic starts                                 

openless-all/app/src-tauri/src/coordinator.rs

  • Removed the early emit_capsule(Recording) from begin_session().
  • Emit Recording only after Recorder::start returns Ok.
  • Added a state check for Starting, pending_stop, and cancelled.
  • Prevents overwriting stop/cancel transitions during startup.
+24/-2   

用户反馈:按 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)
保持不变。
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

PR Reviewer Guide 🔍

(Review updated until commit 5c5b575)

Here are some key observations to aid the review process:

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 一致。
@appergb
Copy link
Copy Markdown
Collaborator Author

appergb commented May 6, 2026

@codex P2 已采纳:commit 5c5b575

修法按你的建议:

  1. emit_capsule(Recording) 挪到 stop_recorder_if_pending_start_stop 之前
  2. emit 之前在同一 lock 内检查 session_id == session_id && phase == Starting && !pending_stop && !cancelled;只有 recorder 真的会继续运行时才 emit
  3. 不满足条件就让 stop / cancel 路径接管 UI 信号,不再无条件覆盖

短按场景下不再出现 Recording 闪烁覆盖 Transcribing/Cancelled 的问题。

@chatgpt-codex-connector
Copy link
Copy Markdown

To use Codex here, create an environment for this repo.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 6, 2026

Persistent review updated to latest commit 5c5b575

@appergb appergb merged commit ab65432 into main May 6, 2026
2 checks passed
@appergb appergb deleted the fix/recording-no-swallow-first-words branch May 6, 2026 11:52
appergb pushed a commit that referenced this pull request May 6, 2026
主要修复
─────────
- 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
  问题。
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