| name | ios-recording-system-integration |
| description | Use when building iOS recording features that integrate with Live Activities (Dynamic Island), the Action Button (App Intents), background audio, or realtime ASR over WebSocket. Covers the hard iOS boundaries (first mic activation must be foreground, Action Button assignment is unreadable, Live Activity is not a streaming channel), an intent state machine with audioSessionPrimed fallback, Dynamic Island debugging, and two ASR protocol paradigms (OpenAI-Realtime JSON vs binary frame). |
iOS 录音类系统集成(灵动岛 / Action Button / 后台录音 / 实时 ASR)
灵动岛、Action Button、后台录音是一个耦合系统。开发前先接受四条 iOS 硬边界,产品设计必须围绕它们收敛:
- 初次麦克风录音的
AVAudioSession 激活必须在前台发生——第三方 App 没有系统级「桌面直接后台起录」的私有通道。
- Action Button 是否已分配给本 App 不可读——没有公开 API,也不能深链到其设置页。
- Live Activity 的 update 是系统托管的内容更新,不是实时流通道——高频 update 会被限流,表现为「上岛如同没上」。
- 后台不能触发权限请求——后台调
requestAuthorization 会把 App 拉到前台。
1. Live Activity / 灵动岛
三层结构:共享 ActivityAttributes(ContentState 含 statusText/elapsedSeconds/timerStartDate/isRecording/waveformSamples)→ actor 控制器(start/update/end)→ Widget target 的 ActivityConfiguration + DynamicIsland(compact/expanded/minimal + 锁屏视图)。
「能显示但像没显示」三段式排查:
- 更新频率:转写等实时流不触发 update,后台更新节流到 ≥2 秒。
- 计时不要依赖推送:用
Text(timerStartDate, style: .timer) 系统自走时;防秒数抖动用稳定文本 + 关隐式动画 + .monospacedDigit()。
- update 前
guard activityState == .active,不对失效 activity 推送。
其他要点:
- 点岛路由:
.widgetURL(...) 要挂在 DynamicIsland 本体上,不只是锁屏视图;App 回 active 时兜底检查录音状态。
- 桌面入口起的后台录音会话与应用内录音页要接同一套预览组件,否则两条路径体验分裂。
- Live Activity 可做真开关(是否创建是 App 自己的行为);Action Button 状态不可读所以不能做开关。
- Widget target 用
-D WIDGET_EXTENSION 编译标记让共享源码在扩展里走 fallback,扩展进程不得依赖主 App 录音服务。
2. Action Button(App Intents)
3. 实时 ASR WebSocket 接入
统一抽象:protocol SpeechTranscribing + 按设置分发的包装器(Apple Speech / 提供商 A / 提供商 B),错误收敛到单一 error 类型。
两种协议范式:
- OpenAI-Realtime 风格 JSON 事件流(如通义千问):Bearer 鉴权 +
OpenAI-Beta: realtime=v1;握手 session.created → session.update(pcm/16k、server_vad、language)→ session.updated 后才进收循环;上行 input_audio_buffer.append + base64;下行增量 text → completed 段落定稿 → session.finish 拿最终稿。拿到最终稿直接保存,不做二次转写。
- 自定义二进制帧(如火山豆包):多 header 鉴权(App-Key/Access-Key/Resource-Id);4 字节头(version/msgType/flags/serial/compression)+ 大端长度 + Gzip 压缩 payload;时序 fullClientRequest → audioOnly 帧 → 空 payload + lastPacket 收尾。
- 方言/语言能力按接口型号确认(流式与非流式支持面常不同),产品文案不要把「自动识别」包装成「多语种选择」。
通用音频管线(所有提供商复用):AVAudioConverter 重采样到 PCM 16kHz/Int16/mono,累积 200ms 再分块发送。
4. 后台录音与权限
- 会话配置:
.playAndRecord + [.mixWithOthers, .allowBluetoothHFP];停止时 setActive(false)。
Info.plist:NSSupportsLiveActivities、UIBackgroundModes=[audio]、麦克风/语音识别 usage 描述。
- 后台只用
authorizationStatus() 判断权限,绝不 request;未授权降级为只录音。
- 启动顺序:先起
AVAudioEngine 录音硬件,成功后再挂实时转写;转写不可用不能让录音失败。
5. 工程惯例
- XcodeGen 管工程:改
project.yml → xcodegen generate,绝不手改 pbxproj。
- 跨进程状态(App / Widget 扩展 / Intent)用 UserDefaults 传递(isRecording / pendingAction / audioSessionPrimed)。
- 并发:控制器与 ASR core 用 actor;音频回调
@unchecked Sendable + 锁。
- 调试方法论:先拿真机 Console 日志 → FourCC 解码错误码 →
rg SDK swiftinterface 确认协议边界,不猜;反复翻车后先列 3-5 条修复路径再选最小可靠方案。