Files
OSGKeyboard/docs/ios-pip-voice-session-plan.md
T
Rocky 9d8914fbf3 docs(ios): plan PiP voice session lifecycle
Define a phased, privacy-first path for keeping keyboard dictation responsive while releasing the microphone between utterances.
2026-07-26 12:34:43 +08:00

14 KiB
Raw Blame History

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 键盘扩展无法直接申请或使用麦克风。系统级语音键盘因此必须采用:

键盘扩展
  → 发送开始/停止命令
  → 主 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 三层可用性

层级 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 组件边界

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 状态机

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 证据完成前,不建议直接进入正式实现。