From 9d8914fbf3fcefca7b1146b627101e8ca10b585c Mon Sep 17 00:00:00 2001 From: Rocky <72559939+hkgood@users.noreply.github.com> Date: Sun, 26 Jul 2026 12:34:43 +0800 Subject: [PATCH] docs(ios): plan PiP voice session lifecycle Define a phased, privacy-first path for keeping keyboard dictation responsive while releasing the microphone between utterances. --- docs/ios-pip-voice-session-plan.md | 385 +++++++++++++++++++++++++++++ 1 file changed, 385 insertions(+) create mode 100644 docs/ios-pip-voice-session-plan.md diff --git a/docs/ios-pip-voice-session-plan.md b/docs/ios-pip-voice-session-plan.md new file mode 100644 index 0000000..228612a --- /dev/null +++ b/docs/ios-pip-voice-session-plan.md @@ -0,0 +1,385 @@ +# iOS PiP 语音会话保活规划 + +> **文档状态**:产品与架构规划(待验证,未进入实现) +> **适用范围**:iOS 主 App + 键盘扩展 + Live Activity +> **目标版本**:待产品验证后确定 +> **创建日期**:2026-07-26 + +--- + +## 1. Executive Summary + +### 1.1 目标 + +在不要求 OSGKeyboard 长时间占用麦克风的前提下,尽量保持主 App 可响应键盘扩展的听写指令,降低用户在宿主 App 与 OSGKeyboard 之间反复切换的频率。 + +核心方向是将两个当前耦合的能力拆开: + +1. **会话可用性**:主 App 仍可接收键盘命令。 +2. **麦克风采集**:仅在用户明确开始听写时启用,完成后立即释放。 + +PiP(画中画)只承担系统可见的多任务会话载体,不绕过麦克风授权,也不应使用静音音频循环伪造后台活动。 + +### 1.2 核心结论 + +| 决策 | 规划选择 | +|------|----------| +| 产品定位 | 将 PiP 作为可选的「免切换模式」,不替代普通 Flow | +| 麦克风策略 | PiP 空闲时关闭;键盘点按听写后按需激活 | +| 默认策略 | 保留当前隐私友好的 5 分钟 Flow;PiP 由用户主动开启 | +| 降级路径 | PiP 不可用或失效时回落到现有 `startflow` 冷启动流程 | +| 状态展示 | PiP 显示有意义的语音会话状态;Live Activity 继续负责锁屏与灵动岛 | +| 禁止方案 | 不播放静音文件保活,不使用定位或 VoIP 等无关后台模式 | +| 上线方式 | 先做真机技术验证和 TestFlight 审核验证,再决定正式产品化 | + +### 1.3 非目标 + +- 不让键盘扩展直接访问麦克风;这是 iOS 平台限制。 +- 不承诺 App 被用户强制退出后仍可免切换听写。 +- 不承诺电话、Siri、相机或其他录音 App 抢占音频设备时继续录音。 +- 不用 PiP 绕过麦克风权限、隐私提示或系统音频策略。 +- 第一阶段不重写 ASR、润色、App Group 或 Darwin 通知管线。 + +--- + +## 2. 问题定义 + +### 2.1 平台约束 + +iOS 键盘扩展无法直接申请或使用麦克风。系统级语音键盘因此必须采用: + +```text +键盘扩展 + → 发送开始/停止命令 + → 主 App 采集并转写 + → App Group 返回结果 + → 键盘插入文本 +``` + +当主 App 被系统挂起或终止时,键盘无法即时启动录音,只能打开主 App 重新建立会话。当前 Flow 通过持续运行 `AVAudioEngine` 输入链路换取后台可用性,但会带来麦克风长期占用、橙色隐私指示、电量消耗和音频冲突。 + +### 2.2 用户问题 + +| 用户感知 | 当前根因 | 目标变化 | +|----------|----------|----------| +| 频繁跳转主 App | 后台主进程不可响应 | PiP 有效时直接响应键盘命令 | +| 麦克风指示长时间亮起 | Flow 会话级连续采集 | 空闲时释放麦克风 | +| 耗电或发热 | 音频引擎持续采样和处理 | 仅听写期间采样 | +| 其他 App 无法使用麦克风 | OSGKeyboard 持有输入设备 | 听写结束后主动释放 | +| 不知道会话是否可用 | Flow、麦克风和进程状态混为一体 | 分开展示「免切换已就绪」和「正在录音」 | + +### 2.3 成功定义 + +PiP 模式下,用户应能: + +1. 在 OSGKeyboard 主 App 中主动开启免切换模式。 +2. 将 PiP 小窗收纳到屏幕边缘。 +3. 回到微信、邮件等宿主 App。 +4. 点击键盘麦克风后直接开始听写。 +5. 停止听写后收到文本,同时麦克风在短时间内释放。 +6. PiP 失效时收到明确提示,并能通过现有冷启动路径恢复。 + +--- + +## 3. 竞品与行业模式 + +### 3.1 Typeless + +Typeless iOS 1.9.0 将该能力命名为 Picture in picture / Skip app switching: + +- 用户先在主 App 中主动开启。 +- PiP 可拖到屏幕边缘收纳。 +- 用户在其他 App 的 Typeless 键盘中开始说话。 +- 官方产品说明强调麦克风空闲时关闭,以降低电量消耗。 + +其公开资料无法证明具体内部实现,因此本规划只借鉴产品模型,不假定其私有代码结构。 + +### 3.2 Wispr Flow、TypeWhisper 与同类开源项目 + +常见架构是主 App 持有 `AVAudioEngine`,键盘通过 App Group 与 Darwin 通知控制句子开始和停止。优点是首字延迟低,缺点是会话期间通常持续占用音频输入。 + +OSGKeyboard 当前 Flow 已属于此模式,并已具备: + +- 主 App 会话所有权; +- 键盘与主 App IPC; +- 连续采集与 utterance gate; +- App Group 结果回传; +- Live Activity; +- 冷启动与恢复流程。 + +因此 PiP 应作为会话生命周期的新载体,而不是重建整条语音管线。 + +### 3.3 SuperWhisper / App Intents 路线 + +更保守的方案是不做长期后台保活,使用 App Intents、Action Button、快捷指令或显式 App 切换启动录音。该方案最符合系统预期,但无法完全满足键盘内即时听写。 + +OSGKeyboard 应保留这类入口作为稳定降级,而不是依赖 PiP 达到 100% 可用。 + +### 3.4 合规边界 + +以下方式不应采用: + +- 循环播放静音音频以防止挂起; +- 声明与产品无关的定位、VoIP 后台能力; +- 使用不可见或无实际产品意义的伪视频,仅为延长进程生命; +- 在用户未明确开启会话时自动恢复麦克风。 + +PiP 内容需要能被解释为真实的语音会话控制面,例如展示: + +- 「免切换已就绪」; +- 「正在聆听」及音量反馈; +- 「正在转写」; +- 暂停、结束或返回 App 操作。 + +--- + +## 4. 目标产品模型 + +### 4.1 三层可用性 + +```text +层级 0:冷启动 + 主 App 不可用 + → 键盘打开 startflow + → 主 App 建立语音会话 + +层级 1:短时 Flow + AVAudioEngine 会话保持 + → 最低首字延迟 + → 默认 5 分钟无活动后结束 + +层级 2:PiP 免切换模式 + PiP 保持用户可见的多任务会话 + → 空闲时麦克风关闭 + → 键盘命令触发按需开麦 +``` + +三个层级必须共用同一份 `FlowSessionBridge` 状态合约,键盘不应根据实现细节分别写三套逻辑。 + +### 4.2 用户入口 + +建议在首页提供独立状态卡,而不是继续扩张设置开关: + +- 未开启:`开启免切换模式` +- 启动中:`正在准备画中画` +- 已就绪:`免切换已就绪 · 麦克风未使用` +- 录音中:`正在聆听` +- 失效:`会话已断开,点击恢复` + +首次开启时应明确说明: + +1. 屏幕上会出现可收纳的 PiP 小窗。 +2. 空闲时不会使用麦克风。 +3. 用户关闭 PiP、强制退出 App 或系统回收进程后,需要重新开启。 + +### 4.3 键盘状态 + +键盘麦克风状态应从「主 App 是否活着」升级为明确能力状态: + +| 状态 | 表现 | 点击结果 | +|------|------|----------| +| 不可用 | 灰色 | 引导权限或 Full Access | +| 需恢复 | 橙色 | 打开主 App 恢复会话 | +| PiP 就绪、麦克风关闭 | 绿色 | 请求主 App 按需开麦 | +| 正在激活麦克风 | 绿色加载态 | 等待真实音频 proof | +| 正在录音 | 红色/波形 | 发送停止命令 | +| 正在转写 | 处理中 | 等待结果 | + +--- + +## 5. 目标架构 + +### 5.1 组件边界 + +```text +Keyboard Extension + └─ FlowSessionBridge / Darwin command + ↓ +Host App + ├─ VoiceSessionCoordinator + │ ├─ FlowSessionManager + │ ├─ PiPVoiceSessionController + │ └─ AudioCaptureLifecycle + ├─ FlowContinuousCapture + ├─ ASR + Polish pipeline + └─ Live Activity +``` + +规划职责: + +- `PiPVoiceSessionController`:只管理 PiP 生命周期和展示状态。 +- `AudioCaptureLifecycle`:管理按需激活、音频 proof、停止及释放。 +- `FlowSessionManager`:继续负责命令、ASR、润色和结果回传。 +- `FlowSessionBridge`:发布跨进程能力快照,不让键盘猜测主 App 状态。 + +### 5.2 状态机 + +```text +inactive + → preparingPiP + → pipReadyMicOff + → activatingMic + → recording + → processing + → releasingMic + → pipReadyMicOff + +任意状态 + → interrupted + → recovering 或 inactive +``` + +重要不变量: + +1. `pipReadyMicOff` 必须确认音频输入已停止并释放。 +2. 键盘只有在收到 `recording` 和真实 audio proof 后才显示正在录音。 +3. PiP 存活不能等价于麦克风可用。 +4. 电话/Siri 中断后不得静默恢复录音。 +5. 任何超时都要回收麦克风并写入明确错误。 + +### 5.3 PiP 内容方案 + +技术验证阶段应比较两类 Apple 官方能力: + +1. 基于 `AVPlayerLayer` 的媒体 PiP; +2. 基于 `AVSampleBufferDisplayLayer` / 视频通话内容源的实时 PiP。 + +选择标准不是「哪种最容易保活」,而是: + +- 是否符合 OSGKeyboard 的真实产品用途; +- 能否展示动态语音会话状态; +- 麦克风激活/释放是否稳定; +- 收纳、锁屏、音频中断行为是否可预测; +- App Review 是否能清楚理解其用途。 + +在完成真机和审核验证前,不冻结具体 AVKit 实现。 + +--- + +## 6. 实施阶段 + +### Phase 0:技术与审核可行性验证 + +目标:证明「PiP 存活 + 闲时关麦 + 键盘触发按需开麦」在目标 iOS 版本可行。 + +验证项: + +- PiP 启动、收纳、恢复与关闭; +- 空闲 30 分钟后主 App 是否仍能响应; +- 空闲期间系统麦克风指示是否消失; +- 键盘命令到首个有效音频帧的延迟; +- 连续 20 次开始/停止是否稳定; +- 电话、Siri、蓝牙切换、锁屏、低电量模式; +- 用户关闭 PiP 后的降级行为; +- TestFlight / App Review 说明是否被接受。 + +退出标准: + +- 空闲时没有麦克风占用; +- P95 命令到有效音频帧小于 1 秒; +- 20 次连续听写无僵尸录音或失联状态; +- 失败后都能回到冷启动路径; +- 没有使用静音循环或无关后台能力。 + +### Phase 1:内部可用版本 + +- 新增 PiP 会话控制器; +- 将持续采集改造成可重复激活/释放; +- 扩展跨进程状态快照; +- 键盘增加激活中、PiP 就绪和失效状态; +- 复用现有 ASR、润色、结果回传和 Live Activity; +- 添加状态机与 IPC 单元测试。 + +### Phase 2:产品化 + +- 首页免切换状态卡; +- 首次开启说明与 PiP 收纳引导; +- 中英文文案与隐私说明; +- 诊断页增加 PiP、音频会话和最近中断原因; +- 增加遥测指标,但不采集音频内容。 + +### Phase 3:灰度与决策 + +- TestFlight 小流量开启; +- 比较 PiP 与普通 Flow 的成功率、首字延迟和耗电; +- 根据审核反馈决定默认入口和长期支持范围; +- 若 PiP 不稳定或审核风险不可接受,保留为实验功能或停止上线。 + +--- + +## 7. 测试矩阵 + +### 7.1 功能场景 + +| 场景 | 预期 | +|------|------| +| PiP 空闲 | 主 App 可响应,麦克风未占用 | +| 键盘开始听写 | 按需激活并获得真实音频帧 | +| 停止听写 | 完成转写并及时释放麦克风 | +| 连续多句 | 每句均重新激活成功,无第二句无音频 | +| PiP 被关闭 | 键盘切为需恢复,不显示假就绪 | +| App 被强退 | 清除旧 generation 和僵尸状态 | +| 电话/Siri 中断 | 当前句失败并提示,不自动偷录 | +| 蓝牙设备变化 | 音频格式重建,不崩溃 | +| 网络失败 | 本地 ASR 保留;润色按现有策略降级 | + +### 7.2 设备与系统 + +- 最低支持 iOS 版本、当前稳定版和最新 beta; +- 刘海机、灵动岛机型、iPad; +- AirPods、普通蓝牙耳机、车载音频、有线设备; +- 微信、信息、邮件、Slack、Notes 及自定义文本输入控件; +- 锁屏、横竖屏、多窗口、低电量和后台刷新关闭状态。 + +--- + +## 8. 指标与验收 + +### 8.1 核心指标 + +| 指标 | 定义 | 目标 | +|------|------|------| +| 免切换成功率 | PiP 就绪时无需打开主 App完成听写 | ≥ 98% | +| 麦克风空闲占用 | 非录音期间仍占麦的时长比例 | 接近 0 | +| 首帧延迟 P95 | 键盘点击到真实音频 proof | < 1 秒 | +| 结果回传成功率 | 停止后键盘收到最终结果 | ≥ 99% | +| 僵尸状态率 | 键盘显示可用但主 App无法响应 | < 0.5% | +| 恢复成功率 | 失效后通过冷启动恢复 | ≥ 99% | + +### 8.2 观察指标 + +- 每日 PiP 开启人数与启用留存; +- PiP 被用户主动关闭的比例; +- 每小时耗电和温升相对普通 Flow 的变化; +- 音频中断类型分布; +- 用户因橙色麦克风指示或隐私产生的反馈; +- App Review 反馈和政策变化。 + +--- + +## 9. 风险与应对 + +| 风险 | 等级 | 应对 | +|------|------|------| +| PiP 被认定与媒体用途不匹配 | 高 | 提供真实会话 UI、明确审核说明;先 TestFlight/审核验证 | +| 系统版本改变 PiP 行为 | 高 | 保持冷启动降级;按系统版本做兼容验证 | +| 按需开麦首字丢失 | 中 | 激活态 + audio proof + 预录缓冲,不提前向键盘宣告录音 | +| 频繁激活导致音频路由异常 | 中 | 串行状态机、格式重建、媒体服务重置恢复 | +| PiP 与 Live Activity 状态冲突 | 中 | 单一 coordinator 发布状态,两个 UI 只消费 | +| 用户误解 PiP 仍在监听 | 中 | 空闲状态明确写「麦克风未使用」,隐私说明可验证 | +| 其他 App 抢占麦克风 | 中 | 显式中断提示,不承诺并发录音 | + +--- + +## 10. 产品决策门 + +进入代码实现前,需要确认: + +1. 是否接受 PiP 作为用户主动开启、系统可见且可收纳的产品形态; +2. 是否优先「空闲关麦」而接受约数百毫秒的重新激活延迟; +3. PiP 中展示哪些真实功能,确保它不是纯保活黑窗; +4. 是否将现有「跳过 App 切换」重命名并拆为普通 Flow / PiP 两种模式; +5. 最低支持系统和目标测试设备; +6. 技术验证失败或审核风险过高时,是否接受回退到短 Flow + App Intents。 + +在上述决策和 Phase 0 证据完成前,不建议直接进入正式实现。