已接受(Accepted,2026-08-18;四项机制已实施,真机验收待执行)
2026-08-18
课堂助手实时采集链路(v0.2.0)存在三类生命周期缺口,破坏"持续不间断运行"承诺:
- 关闭即杀:应用无
CloseRequested拦截,用户点关闭直接退出进程——采集线程被杀,会话停留在 recording 状态,融合与 finish 不执行(仅mark_interrupted_sessions兜底标 failed,丢失停止流程产物) - 音频中断即死:WASAPI 捕获在设备失效/默认设备切换(插拔耳机、设备被占用)时,
run_capture_inner返回 Err → 捕获线程退出 → channelDisconnected→ 会话线程break→ 整个会话死亡,无重连机制 - 降级单向:DXGI 失效(锁屏/远程桌面/设备丢失)后永久降级 GDI,不重建 DXGI 主路径;
window_rect为启动时快照,窗口移动/缩放/分辨率变化后画面裁剪错位
已验证满足的行为(无需改动,需固化为测试):前端页面切换(display:none 不卸载,TD-004)与窗口最小化均不影响 Rust 后台采集线程。
约束:
- 本地优先:恢复机制必须纯本地(无云端心跳)
- 数据连续性:恢复后时间戳必须连续(会话时间轴基准不因重连重置)
- 可观测:恢复/降级事件须推送前端(用户可感知"采集中/恢复中")
- 关闭保护:仅在采集进行时拦截关闭;空闲时关闭行为与现在一致
我们将实施以下四项机制:
tauri::Builder::on_window_event注册CloseRequested处理:AppState.live_session有活动会话时event.prevent_close()+ emitapp:close-requested- 前端监听该事件 →
tauri-plugin-dialog(已注册)弹确认框:"当前正在进行采集,确定要停止并退出吗?"- 确认 →
stop_live_session(含 5s 有界等待)→ 成功后再window.close()(此时采集已停,CloseRequested 放行) - 取消 → 无操作,采集继续
- 确认 →
- 竞态(停止与关闭同时发生):关闭检查时 active 已取走则放行,残留由
mark_interrupted_sessions兜底
capture_loop外层包重试循环:run_capture返回 Err 且未收到 stop_flag → 指数退避(0.5s/1s/2s/4s…上限 10s)→ 重新走完整初始化(COM/设备/格式/环回)- 时间戳基准(
startedInstant)提升到重试循环外——重连不重置基准,时间轴连续("无缝续接"的关键) - 重连期间 channel 保持打开(会话线程
recv_timeout超时分支继续轮询,不会Disconnected)→ 会话不死亡 - 重连触发时 emit
live:recovering,成功后 emitlive:recovered(前端状态徽标) - 触发条件:初始化失败(无设备/设备被占用)与运行期错误(
AUDCLNT_E_DEVICE_INVALIDATED等)统一走重连
- DXGI 周期重建:GDI 降级期间,每隔固定间隔(30s 或 N 次捕获)尝试
DxgiState::create重建 DXGI 主路径;成功即切回(锁屏/远程桌面恢复后自动回 DXGI) - 窗口矩形每次捕获前刷新:
GetWindowRect不再只取启动快照——窗口移动/缩放/分辨率变化后裁剪自动跟随 - 目标窗口失效回退:
GetWindowRect失败(窗口已关闭)→ 自动回退全屏捕获(window_rect清零)+ emitlive:window-lost通知前端;画面链路不中断
- 前端 App 顶层监听
live:status/live:recovering/live:recovered,导航栏常驻采集状态徽标("🎙 采集中"),页面切换/最小化后用户仍可感知采集在跑
- 优点:三类缺口一次补齐;恢复机制全部本地;时间戳连续保证数据可用性
- 缺点:改动面较大(audio_loopback / dxgi_capture / lib / 前端);重连行为需实测调参(退避上限)
- 适用场景:当前需求
- 优点:改动最小(仅 lib.rs + 前端确认框)
- 缺点:音频中断即死问题仍在(插拔耳机即毁会话);DXGI 单向降级仍在
- 适用场景:不满足"持续不间断"承诺;否决
- 优点:隔离最强,UI 崩溃不影响采集
- 缺点:进程间通信/生命周期管理复杂度高(跨进程 DB 竞争、模型加载翻倍);MVP 阶段过度设计
- 适用场景:V1.0 后若出现"UI 频繁重启"需求再评估;当前否决
- 优点:重连响应最快(事件驱动而非错误触发)
- 缺点:COM 回调实现复杂度高(跨线程封送);与错误触发重连可叠加
- 适用场景:作为 2 的后续增强(P2);当前用错误触发 + 退避
- 三类缺口(关闭即杀/音频即死/降级单向)都是真实破坏"持续不间断"的行为,须一次补齐(方案 A)
- 音频错误触发 + 指数退避(方案 D 的简化版)已覆盖设备插拔/占用/失效三类场景,实现成本低且与现有错误路径天然结合(Err 已全路径返回)
- 时间戳基准外提是"无缝"的关键——不这样做,重连后数据时间轴断裂,恢复无意义
- 常驻服务化(方案 C)是 V1.0 之后的架构选项,MVP 阶段不做
- 关闭保护:采集进行时误关不丢数据;停止流程(融合/finish)保证执行
- 音频重连:插拔耳机/设备切换不毁会话;恢复后时间轴连续
- 屏幕自愈:锁屏/远程桌面/分辨率变化后自动回 DXGI 主路径;窗口移动裁剪不错位
- 可观测:恢复事件 + 全局徽标,用户对"后台仍在采集"有明确感知
- audio_loopback.rs / dxgi_capture.rs 结构改动(重试循环/周期重建)
- lib.rs 增加窗口事件处理;前端增加确认框与状态徽标
- 重连参数(退避上限、重建间隔)需实测校准
- 重连风暴:设备长期不可用时退避循环持续打印错误——需日志节流(仅首次 + 每次退避升级)
- 关闭竞态:确认后 stop 与用户再次点关闭的竞态窗口——以
active_session_id为唯一判定源 - 前端确认框依赖 dialog 插件权限(capabilities 已含?需核查 default.json)
- 采集进行中关闭应用 → 弹出确认框;取消后采集继续;确认后会话正常 finish(非 failed)
- 空闲时关闭应用 → 直接退出,无确认框
- 采集中拔掉音频输出设备再插回 → 会话不死亡,恢复后时间戳连续(无跳变)
- 锁屏/远程桌面往返 → DXGI 恢复后切回主路径(日志验证 dxgi 后端名)
- 拖动窗口/改分辨率 → 画面裁剪跟随窗口新位置
- 关闭目标窗口(网课窗口)→ 回退全屏 + 前端收到 window-lost 通知,会话继续
- 前端页面切换/窗口最小化 → 采集持续(已有行为,测试固化)
-
cargo test新增:重连退避状态机、时间戳连续性(纯逻辑部分)
- ADR-001: WASAPI 端点环回音频捕获方案(重连机制在其基础上扩展)
- ADR-002: DXGI 屏幕捕获与关键帧变化检测方案(自愈机制在其基础上扩展)
- ADR-004: 会话管理数据模型方案(failed 兜底语义不变)
- REQ-031: 融合停止异步化(关闭确认后的停止流程复用其异步化产物)
- 需求池 REQ-033(requirements-pool.md)
- Tauri 2
on_window_event/CloseRequestedAPI 文档