[Bug][Mobile][iOS] Cindy 原声语音输入约十几到二十秒后自行停止,全程无任何提示
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 401
- Avg merge
- 21h 48m
- Merged PRs (30d)
- 776
Description
## 环境
- 设备:iPhone Air / iOS 27
- 移动端:当前最新版(对应桌面端 v0.1.55)
- 语音链路:Cindy 原声语音输入
- 屏幕状态:亮屏,手持,**未锁屏**
## 现象
连续说话时,录到大约**十几到二十秒**就自己停了。
- 不是我手动停的
- 停之前没有任何倒计时、进度或即将超时的提示
- 停之后也没有任何错误提示、toast 或状态变化说明发生了什么
- 表现上就像「正常结束」,但我话还没说完
对长句口述场景(这正是语音输入的主场景)基本不可用:一段完整的想法说不到一半就断了。
## 已排除的方向
基于 `main` 当前快照翻了一遭,先把几个容易误判的方向排掉:
**不是客户端硬编码超时**。`apps/mobile/src/session/mobileRealtimeAsrProvider.ts:41-46` 里的常量没有任何接近 10-20s 的录音时长上限:
```ts
const DEFAULT_CONNECT_TIMEOUT_MS = 5_000; // 连接超时
const DEFAULT_FLUSH_TIMEOUT_MS = 4_000; // flush 超时
const RECOVER_TIMEOUT_MS = 3_000;
```
**不是 400ms server VAD**。`QWEN_SERVER_VAD_SILENCE_DURATION_MS = 400` 只对 `qwen-asr-server-vad` profile 生效(`:578`、`:707` 均有 profile 门控),Cindy 原声走的不是这条。
**不是锁屏打断**(区别于 #2727)。全程亮屏手持。
## Cindy 原声实际走的链路
`apps/mobile/src/session/mobileCindyVoiceSession.ts:195-203`:
```ts
{
provider: 'litellm-volcengine-sauc-asr',
mode: 'provider-native-websocket',
endpointPath: '/volcengine/api/v3/sauc/bigmodel_async',
protocolProfile: 'volcengine-sauc-duration',
resourceId: 'volc.seedasr.sauc.duration',
}
```
注意 `resourceId` 是 **`.duration`** 类型资源。建议优先排查这条:上游对单次 WebSocket 会话是否有时长 / 无声 / 单 utterance 上限,到点后**主动正常关闭连接**。
(以下为推断,未经真机拓包验证,请以实测为准。)
## 为什么「没有任何反馈」—— 这是独立的第二个问题
无论中断的真实原因是什么,**静默失败本身就是一个应该单独修的缺陷**。
错误反馈通道是存在的 —— `apps/mobile/src/session/mobileVoiceController.ts:390-401` 会把失败本地化后抛给 `options.onError?.(...)`。但它**只在「错误」时触发**。
如果上游是正常关闭 WebSocket(而非报错),状态机会走 `done` 而不是 `error`(参考 `:334-336`、`:349` 的 done / error 分支),于是:
> 录音静默结束 → 没有 onError → UI 无任何提示 → 用户以为自己手滑了
这与用户实际观察完全一致。
## 期望行为
1. **长录音不应该被静默截断**。若上游确有单次会话上限,客户端应在到点前**无感重连 / 接续会话**,保证连续口述不断
2. **确实无法继续时必须给反馈**。至少一句人话:「录音已结束(达到单次时长上限),已识别内容已保留」—— `done` 路径也要能区分「用户主动停」与「被动结束」
3. **提前预告**。若存在硬性上限,在剩余 3-5 秒时给视觉提示,让用户能把话说完
4. **已识别内容必须保留**。中断不得丢弃已落地文本
## 验收标准
- [ ] iOS 真机亮屏持续说话 **90 秒**,录音不中断,文本连续落地
- [ ] 若因上游限制必须断开,客户端自动重连且用户无感;重连失败才提示
- [ ] 任何非用户主动的录音结束,**100% 有可见提示**,且文案说清楚原因与内容去向
- [ ] 中断后已识别文本完整保留在草稿里
- [ ] 补日志:记录 WS 关闭码 / 关闭原因 / 录音时长,便于定位是上游限制还是客户端问题
## 建议的排查路径
1. 真机复现,拓包 / 日志看 `/volcengine/api/v3/sauc/bigmodel_async` 这条 WS 的**实际关闭码与关闭时间点**
2. 确认断开是上游主动关闭、iOS `AVAudioSession` 中断,还是客户端自身状态机提前进了 `stopping`
3. 计时是否稳定(固定秒数 = 硬性上限;浮动 = 网络 / 音频中断)
4. 对比其他 profile(qwen / gpt-realtime-whisper)是否同样复现 —— 可快速判定是否为 volcengine 专有
## 与已有 issue 的区别
- #2727(手机端语音输入被自动锁屏打断):触发条件是锁屏,且那条至少「识别暂停但内容保留」有可观察状态;本条全程亮屏手持,且**零反馈**
- #1692(长按语音输入 ANR 闪退):Android 小米机型崩溃;本条 iOS 不崩溃,只是静默停止
- #2975(语音输入总是追加到末尾):同一次使用中遇到的另一个问题,但属于插入位置,与本条无关
## 相关代码
- `apps/mobile/src/session/mobileCindyVoiceSession.ts:195-203`(Cindy 原声 profile 与 resourceId)
- `apps/mobile/src/session/mobileRealtimeAsrProvider.ts:41-46`(超时常量,均不匹配 10-20s)
- `apps/mobile/src/session/mobileVoiceController.ts:334-336`(failed / done 竞态处理)
- `apps/mobile/src/session/mobileVoiceController.ts:349`(done / error 分支)
- `apps/mobile/src/session/mobileVoiceController.ts:390-401`(onError 本地化与上报,仅错误路径触发)
可配合提供真机录屏或指定时间段的客户端日志。
Contributor guide
Research direction
Reproduce on an iOS device, then inspect logs for the WebSocket at /volcengine/api/v3/sauc/bigmodel_async, including its close code, reason, and timing. Read apps/mobile/src/session/mobileCindyVoiceSession.ts, mobileRealtimeAsrProvider.ts, and mobileVoiceController.ts, especially the done/error paths. Done means 90 seconds of continuous speech remains usable, unexpected endings are visible and explained, recognized text is preserved, and closure diagnostics are logged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, typescript
- Domain
- api, mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100