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

@appergbappergb commented May 6, 2026

Copy link
Copy Markdown
Collaborator

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

github-actionsBot commented May 6, 2026

Copy link
Copy Markdown
Contributor

PR Reviewer Guide 🔍

(Review updated until commit https://github.com/appergb/openless/commit/5c5b5758b61ceb820313371fbc1d38c3f47c099e)

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

@chatgpt-codex-connectorchatgpt-codex-connectorBot left a comment

Copy link
Copy Markdown

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
CollaboratorAuthor

@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
Contributor

@appergb
appergb merged commit ab65432 into mainMay 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 freeto 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

@appergb