Skip to content

Commit 1742468

Browse files
committed
docs(adr): ADR-013 实时会话预热与播放暂停驱动决策记录
1 parent 4e9ccf6 commit 1742468

1 file changed

Lines changed: 90 additions & 0 deletions

File tree

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# ADR-013: 实时会话引擎预热与播放暂停驱动(预备线程 + 共享暂停标志)
2+
3+
## 状态
4+
5+
已接受(2026-08-19 用户需求评审:课堂助手三项优化——停止即时/播放暂停驱动/引擎预热)
6+
7+
## 日期
8+
9+
2026-08-19
10+
11+
## 背景
12+
13+
课堂助手实时捕获存在三处体验缺口:
14+
15+
1. **初始化时机**:`StreamingAsrEngine::load`(SenseVoice + 标点恢复,数秒)在点"开始"后的会话线程内执行,启动延迟全部落在用户点击之后。
16+
2. **暂停语义**:REQ-125 已能检测视频播放/暂停(暂停图标颜色统计,落 PlayerBehavior 事件),但只落库不驱动捕获——视频暂停时应用仍持续采集/OCR。
17+
3. **停止响应**:停止后 drain 宽限 8s 内捕获线程持续喂块,队列永不空,`stop_active` 5s 有界等待必然超时(detach 日志常态出现)。
18+
19+
约束:`StreamingAsrEngine` 含 FFI 句柄(非 Send),**不能跨线程移交**;同时期 A1 硬暂停(WASAPI 端点 Stop/Start + 时间戳补偿)已落地共享 `SessionPause` 机制。
20+
21+
## 决策
22+
23+
1. **引擎预热 = 预备线程 + channel 交接**(不移动引擎):
24+
- 新命令 `prepare_live_session`(进课堂助手页即调用)起"预备线程":加载引擎 → 置 `Ready` → park 在**原线程**等 Start/Cancel(500ms 轮询,15min TTL)。
25+
- `start_live_session` 优先交接:有界等待就绪 ≤5s(防双引擎内存翻倍)→ channel 发 Start 消息(参数/会话 id/停止标志/暂停状态)→ 预备线程就地转为会话线程,**开始毫秒级**;加载失败/等待超时/交接失败 → 取消预备线程并回退内联加载(现状路径,start 永不因预热缺席失败)。
26+
- `release_live_prepare`(离开页面调用)有界 join ≤1s;引擎加载中不可中断时 detach(加载完即退出)。
27+
2. **播放暂停驱动捕获 = 复用 A1 共享暂停标志**:
28+
- 屏幕 worker 的 REQ-125 检测在检测到暂停时置 `SessionPause.paused`(首检基线同置,不写假事件)——音频捕获线程/主循环沿既有 A1 边沿执行真实暂停(端点 Stop、不喂 ASR、不写 WAV、时间戳补偿)。
29+
- 自动暂停期间 worker 进入**轻量轮询**(区别于手动暂停的全冻结):1s 一拍仅取帧刷新 `latest_frame`(恢复检测信号源)+ 5s 一拍检测;检测到恢复 → 清标志 + 落 Play 事件,链路沿边沿自动恢复。
30+
- 暂停边沿的断句隔离由"喂 100ms 静音"(A1 原方案,不足以触发 sherpa 端点规则)改为 **flush 尾句落库 + 引擎 `reset()`**(flush_tail_and_persist 与停止路径共用,防实现漂移;时间戳统一补偿暂停时长)。
31+
3. **停止即时**:主循环观察到停止标志时**先 `audio.stop()`**(捕获线程 ≤100ms 退出 → channel 断开),再 drain 残留块(通常 <1s),`Disconnected` 后退出;8s 宽限保留为理论兜底。`stop_live_session` 补 `spawn_blocking`(此前异步 command 直接阻塞运行时 5s)。
32+
4. **A1 补漏**:`SessionPause` 按会话复位(`reset()`)——暂停标志/补偿时长跨会话残留会让新会话起始即暂停、时间戳偏移。
33+
34+
## 备选方案
35+
36+
### 方案 A:预备线程 + channel 交接(选定)
37+
- 优点:引擎不跨线程(规避非 Send 约束);预热与开始解耦(模型加载移出点击路径);TTL/释放命令双保险控内存;失败回退内联零回归。
38+
- 缺点:预热线程 park 期间占用内存(~数百 MB);交接路径需处理加载中/失败竞态(有界等待 + 回退 + send 失败取回参数)。
39+
- 适用场景:引擎加载耗时且生命周期与 UI 流程解耦的场景。
40+
41+
### 方案 B:预热结果放共享槽、会话线程自取
42+
- 缺点:引擎非 Send 无法存入跨线程共享槽(结构上不可行)。
43+
- 适用场景:无。
44+
45+
### 方案 C:自动暂停独立实现(自有标志 + 循环内丢块)
46+
- 优点:与 A1 硬暂停解耦。
47+
- 缺点:重复实现暂停机制(WAV 间隙/时间戳补偿/事件三处漂移);A1 已有真实端点停采+补偿,叠加自实现违背"复用存量"原则。
48+
- 适用场景:无(A1 机制已存在,直接驱动更优)。
49+
50+
## 选择理由
51+
52+
- 引擎非 Send 是硬约束——方案 A 是唯一能"提前加载 + 线程内使用"的结构。
53+
- 播放暂停自动化的成本集中在检测端(worker 轻量轮询),驱动端完全复用 A1 已验证的硬暂停(端点 Stop/Start 无重连风险、时间戳补偿无跳跃)。
54+
- 停止即时化改动最小(一处 `audio.stop()` 前置),同时消除 detach 常态日志并修复 stop 阻塞运行时线程的问题。
55+
56+
## 影响
57+
58+
### 正面影响
59+
- 点"开始"到录制开始从数秒降至毫秒级(预热就绪时)。
60+
- 视频暂停自动停采(省 OCR/磁盘/静音音频),恢复自动续采,时间轴无跳跃。
61+
- 停止响应 <1s,detach 日志仅真卡死时出现;暂停前后句子不连句。
62+
63+
### 负面影响 / 代价
64+
- 预热引擎常驻内存(~数百 MB),离开页面/TTL 才释放——受 15min TTL 与页面卸载释放约束。
65+
- 自动暂停依赖暂停图标可见(窗口前台、播放器显示图标);遮挡/最小化可能漏检或误判恢复(沿用 REQ-125 保守检测的既有局限)。
66+
- 预热后词表变更在首个端点时生效(现状语义,无回归)。
67+
68+
### 风险
69+
- 交接竞态(预备线程在 send 前退出):已用 SendError 取回参数回退内联 + 防御性报错兜底。
70+
- 手动恢复时视频仍暂停 → worker 兜底重新自动暂停(语义:捕获跟随视频状态;用户可再次手动继续,5s 内复判)。
71+
- 暂停边沿 flush 含 SenseVoice 重打分(有界 3s)——暂停/停止时主循环短暂阻塞,捕获已停无积压,可接受。
72+
73+
## 合规性验证
74+
75+
- `cargo test` 全绿(936 例);`cargo check --all-targets` 无新增告警。
76+
- 前端 `npm run build`(tsc + vite)通过。
77+
- 验收路径(真机):① 预热就绪后点开始 → `live:status recording` 毫秒级;② 视频暂停 → 5s 内 `live:paused` + 端点停采 + 屏幕零分析;恢复 → 自动续采、时间轴连续;③ 停止 → <1.5s 返回、无 detach 日志。
78+
- 手动暂停/自动暂停共用同一标志与事件,前端按钮与徽标零改动复用。
79+
80+
## 相关决策
81+
82+
- ADR-007: live-session-lifecycle(会话线程编排;本 ADR 在其上加预备交接)
83+
- ADR-008: 会话纪元统一(A1 时间戳;预备交接的纪元在移交后创建,无偏移)
84+
- ADR-012: 流式 ASR 质量修复(F1-2 flush 重打分兜底——flush_tail_and_persist 复用)
85+
- 2026-08 A1 硬暂停设计(docs/archive/2026-08-19/brainstorming-classroom-homepage-controls.md)
86+
87+
## 参考
88+
89+
- `docs/superpowers/specs/2026-08-19-classroom-live-optimizations-design.md`(本批设计规格)
90+
- `docs/standards/line-limit-exemptions.md`(相关文件行数登记)

0 commit comments

Comments
 (0)