Files
zyt/LOCAL_RECORDING_DEBUG_GUIDE.md
T
2026-03-24 16:32:56 +08:00

359 lines
9.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 本地录制调试指南(第二版)
## 问题描述
本地录制总是失败,`blob` 为空,导致无法保存通话录制文件。
## 根本原因分析
从实际日志分析发现:
1. **录制启动太晚**:原来的 1200ms 延迟导致录制在对方挂断后才启动
2. **SDK 提前销毁轨道**`stopLocalVideo/stopLocalAudio``exitRoom` 之前就销毁了视频轨道
3. **时间窗口太短**:从对方离开到轨道销毁只有约 200-300ms
关键时间线(问题场景):
```
15:30:51.841 - 对方离开(USER_LEAVE 事件)
15:30:51.xxx - beginStopNow 被调用(但录制还未启动)
15:30:52.072 - stopLocalVideo/stopLocalAudio(轨道被销毁)
15:30:52.102 - exitRoom
15:30:52.374 - MediaRecorder 已启动(太晚了!)
15:30:52.xxx - 数据块数: 0(因为轨道已销毁)
```
## 已实施的修复(第二版)
### 1. 大幅缩短录制启动延迟
- **从 1200ms 缩短到 500ms**
- 使用 300ms 间隔重试(最多 10 次)
- 更快地检测和启动录制
```typescript
// 延迟 500ms 后开始尝试(给视频元素一点加载时间)
startLocalRecTimer = setTimeout(() => attemptStartRecording(0), 500)
```
### 2. 在通话结束事件时立即停止录制
监听 TUICallEngine 事件,在检测到通话结束时立即停止:
```typescript
const onEarlyStopRecording = () => {
console.log('[chat-dialog] 检测到通话结束事件,立即停止录制')
if (localCallRecorder.isRecording()) {
console.log('[chat-dialog] 录制进行中,触发 beginStopNow')
localCallRecorder.beginStopNow()
}
}
// 监听: CALL_END, USER_LEAVE, ON_CALL_NOT_CONNECTED
```
### 3. 缩短停止延迟
- **从 300ms 缩短到 100ms**
- 减少等待时间,避免 SDK 提前销毁轨道
```typescript
// 缩短延迟到 100ms,避免 SDK 提前销毁轨道
setTimeout(() => {
mr.stop()
}, 100)
```
### 4. 增强日志输出
在关键位置添加了详细的 console.log
- **录制启动阶段**
- 检查容器和通话状态
- 视频元素数量和就绪状态
- MediaRecorder 创建和启动状态
- Canvas 和音频混音初始化
- **录制进行阶段**
- 数据块收集(ondataavailable
- MediaRecorder 状态变化
- **录制停止阶段**
- beginStopNow 调用时机
- 数据块数量
- Blob 生成结果
### 5. 智能视频元素检测
改进了视频元素查找逻辑:
```typescript
// 1. 首先检查容器内的视频
let videos = Array.from(root.querySelectorAll('video'))
// 2. 如果没有,检查 bodyTUICallKit Teleport
if (videos.length === 0) {
const bodyVideos = Array.from(document.body.querySelectorAll('video'))
videos = bodyVideos.filter(v => {
const so = v.srcObject
if (!(so instanceof MediaStream)) return false
return so.getVideoTracks().some(t => t.readyState === 'live')
})
}
// 3. 检查视频是否有有效数据
const hasReadyVideo = videos.some(v =>
v.readyState >= HTMLMediaElement.HAVE_CURRENT_DATA
)
```
## 调试步骤
### 1. 打开浏览器控制台
在发起视频通话前,打开浏览器开发者工具(F12),切换到 Console 标签。
### 2. 发起视频通话
点击视频通话按钮,观察控制台输出。
### 3. 关键日志检查点
#### 通话接通后(约 0.5-1 秒)
应该看到:
```
[chat-dialog] 尝试启动本地录制(第 1 次)
[chat-dialog] 在 body 中找到活跃视频: 2
[chat-dialog] 视频元素已就绪,立即启动录制
[call-local-recorder] 使用 MIME 类型: video/webm;codecs=vp9,opus
[call-local-recorder] Canvas 视频轨道数: 1
[call-local-recorder] 找到视频元素数量: 2
[call-local-recorder] 输出流轨道: { video: 1, audio: 2 }
[call-local-recorder] MediaRecorder 创建成功
[call-local-recorder] MediaRecorder.start() 调用成功
[call-local-recorder] MediaRecorder 已启动
[chat-dialog] ✅ 本地录制已成功启动
```
#### 录制过程中(每 250ms
应该看到:
```
[call-local-recorder] 收到数据块,大小: 12345 (总计: 1 块)
[call-local-recorder] 收到数据块,大小: 15678 (总计: 2 块)
...
```
**重要**:如果没有看到数据块,说明录制有问题!
#### 对方挂断或己方挂断时
应该看到:
```
[chat-dialog] 检测到通话结束事件,立即停止录制
[chat-dialog] 录制进行中,触发 beginStopNow
[call-local-recorder] beginStopNow 被调用
[call-local-recorder] 准备停止 MediaRecorder,当前状态: recording 已收集数据块: 15
[call-local-recorder] 调用 requestData()
[call-local-recorder] 延迟后数据块数: 16
[call-local-recorder] 调用 stop()
[call-local-recorder] MediaRecorder onstop 触发,数据块数: 16
[call-local-recorder] 生成 Blob: 1234567 bytes, type: video/webm
[chat-dialog] localCallRecorder.stop() 返回: 1234567 bytes
[chat-dialog] 开始上传录制文件
[chat-dialog] 上传成功,文件 URL: https://...
[chat-dialog] 关联录制文件到诊单
本地录制已上传并关联到通话记录
```
## 常见问题诊断
### 问题 1:录制启动太晚
**症状**
```
[call-local-recorder] MediaRecorder 已启动
(出现在 stopLocalVideo 之后)
```
**原因**
- 视频元素加载慢
- 重试次数不够
**解决**
- 已缩短初始延迟到 500ms
- 增加重试次数到 10 次
- 每次重试间隔 300ms
### 问题 2:找不到视频元素
**症状**
```
[chat-dialog] 未找到视频元素,300ms 后重试
(重复多次)
```
**原因**
- TUICallKit 将视频 Teleport 到 body
- 视频元素尚未渲染
**解决**
- 代码已添加 body 视频检查
- 添加了活跃视频流过滤
### 问题 3:视频元素未就绪
**症状**
```
[chat-dialog] 视频元素未就绪,300ms 后重试
```
**原因**
- 视频流尚未加载
- readyState < HAVE_CURRENT_DATA
**解决**
- 代码已添加就绪检查和重试
- 最多重试 10 次
### 问题 4:没有收到数据块
**症状**
- 录制启动成功
- 但从未看到 "收到数据块" 日志
- 最终 `数据块数: 0`
**可能原因**
1. Canvas 绘制失败(跨域问题)
2. MediaRecorder 不支持当前 MIME 类型
3. 视频流被中断或为空
**检查方法**
```javascript
// 在控制台手动检查
const videos = document.querySelectorAll('video')
videos.forEach((v, i) => {
console.log(`Video ${i}:`, {
readyState: v.readyState,
videoWidth: v.videoWidth,
videoHeight: v.videoHeight,
srcObject: v.srcObject,
videoTracks: v.srcObject?.getVideoTracks().map(t => ({
id: t.id,
label: t.label,
readyState: t.readyState,
enabled: t.enabled
})),
audioTracks: v.srcObject?.getAudioTracks().map(t => ({
id: t.id,
label: t.label,
readyState: t.readyState,
enabled: t.enabled
}))
})
})
```
### 问题 5:通话时间过短
**症状**
```
[call-local-recorder] 生成 Blob: null
[call-local-recorder] 生成 Blob: 1234 bytes(很小)
```
**原因**
- 通话时长 < 1 秒
- 录制启动后立即挂断
**解决**
- 确保通话时长 > 2 秒
- 测试时保持通话至少 5 秒
### 问题 6:SDK 提前销毁轨道
**症状**
```
[call-local-recorder] MediaRecorder 错误
数据块数为 0
```
**原因**
- SDK exitRoom 在 MediaRecorder.stop() 之前执行
- 视频轨道被销毁
**解决**
- 已在 USER_LEAVE 事件时立即停止录制
- 已在多个挂断入口添加钩子
- 缩短停止延迟到 100ms
## 测试建议
### 最小测试场景
1. 发起视频通话
2. 等待接通(看到对方画面)
3. **保持通话至少 5 秒**(重要!)
4. 正常挂断
5. 检查控制台日志和录制结果
### 关键检查点
- [ ] 录制在接通后 1 秒内启动
- [ ] 看到 "✅ 本地录制已成功启动"
- [ ] 看到持续的 "收到数据块" 日志
- [ ] 数据块数 > 0(至少 4-5 块)
- [ ] 生成的 Blob 大小 > 10KB
- [ ] 上传成功并关联到诊单
### 压力测试场景
1. 快速挂断(2-3 秒)
2. 对方挂断
3. 网络中断
4. 切换会话后挂断
5. 关闭聊天窗口
## 预期改进效果
### 修复前
```
通话接通 -> 等待 1200ms -> 尝试启动录制 -> 对方挂断 -> SDK 销毁轨道 -> 录制失败
```
### 修复后
```
通话接通 -> 等待 500ms -> 快速启动录制 -> 开始收集数据 ->
检测到挂断 -> 立即停止录制 -> 生成 Blob -> 上传成功
```
## 浏览器兼容性
### 支持的浏览器
- Chrome/Edge 85+
- Firefox 78+
- Safari 14.1+
### MIME 类型优先级
1. `video/webm;codecs=vp9,opus` (最佳)
2. `video/webm;codecs=vp8,opus`
3. `video/webm;codecs=vp9`
4. `video/webm` (兜底)
### 检查浏览器支持
```javascript
const mimes = [
'video/webm;codecs=vp9,opus',
'video/webm;codecs=vp8,opus',
'video/webm;codecs=vp9',
'video/webm'
]
mimes.forEach(m => {
console.log(m, MediaRecorder.isTypeSupported(m))
})
```
## 性能优化建议
1. **Canvas 分辨率**:当前 1280x720,可根据需要调整
2. **数据块间隔**:当前 250ms,可调整为 500ms 减少开销
3. **音频混音**:如果不需要音频,可以禁用 AudioContext
## 下一步优化(如果问题仍存在)
1. **进一步缩短启动延迟**:从 500ms 减少到 300ms
2. **增加录制状态监控**:定期检查 MediaRecorder 状态
3. **添加降级方案**:如果本地录制失败,提示用户依赖云端录制
4. **添加手动录制控制**:让用户手动开始/停止录制
5. **使用 MutationObserver**:监听视频元素的添加,立即启动录制
## 联系支持
如果按照本指南操作后问题仍未解决,请提供:
1. 完整的控制台日志(从通话开始到结束)
2. 浏览器版本和操作系统
3. 通话时长(秒)
4. 是否看到视频画面
5. 是否有任何错误提示
6. 数据块数量(如果有)