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

9.6 KiB
Raw Permalink Blame History

本地录制调试指南(第二版)

问题描述

本地录制总是失败,blob 为空,导致无法保存通话录制文件。

根本原因分析

从实际日志分析发现:

  1. 录制启动太晚:原来的 1200ms 延迟导致录制在对方挂断后才启动
  2. SDK 提前销毁轨道stopLocalVideo/stopLocalAudioexitRoom 之前就销毁了视频轨道
  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 次)
  • 更快地检测和启动录制
// 延迟 500ms 后开始尝试(给视频元素一点加载时间)
startLocalRecTimer = setTimeout(() => attemptStartRecording(0), 500)

2. 在通话结束事件时立即停止录制

监听 TUICallEngine 事件,在检测到通话结束时立即停止:

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 提前销毁轨道
// 缩短延迟到 100ms,避免 SDK 提前销毁轨道
setTimeout(() => {
  mr.stop()
}, 100)

4. 增强日志输出

在关键位置添加了详细的 console.log

  • 录制启动阶段

    • 检查容器和通话状态
    • 视频元素数量和就绪状态
    • MediaRecorder 创建和启动状态
    • Canvas 和音频混音初始化
  • 录制进行阶段

    • 数据块收集(ondataavailable
    • MediaRecorder 状态变化
  • 录制停止阶段

    • beginStopNow 调用时机
    • 数据块数量
    • Blob 生成结果

5. 智能视频元素检测

改进了视频元素查找逻辑:

// 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. 视频流被中断或为空

检查方法

// 在控制台手动检查
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 秒

问题 6SDK 提前销毁轨道

症状

[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 (兜底)

检查浏览器支持

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. 数据块数量(如果有)