AI 写了一个BUG,排查了几十轮才解决!
视频弹窗「关闭重开黑屏」排查复盘
背景:某视频监控类 Web 项目中的球机播放弹窗(演示模式:下拉选择摄像机播放),功能上线后发现「刷新后首次打开能播、关闭面板再打开黑屏、下拉切换能播」,前后排查数十轮,最终定位为前端播放触发逻辑缺陷。本文为三个问题的诚实复盘。
问题 1:当初写代码时犯了什么错
核心错误:把"播放触发"写成了依赖时序巧合,而不是显式事件。
| 错误 | 具体表现 | 后果 |
|---|---|---|
| 依赖隐式时序 | 播放触发 = onMounted → setupPlayer,但 setupPlayer 能否工作依赖 selectedCamera 恰好已被 watch demoCameras(immediate) 设置好 |
弹窗 v-if 销毁重建(关闭再打开)时,这条"恰好"链条断裂 → 播放被跳过 |
| 静默失败 | setupPlayer 里 if (!cameraNo) return ——无日志、无兜底 |
失败完全无声,用户看到黑屏,代码无任何痕迹,为后面十几轮排查埋雷 |
| 忽略核心生命周期 | 弹窗的显隐是 visible prop(打开/关闭),这是它真正的生命周期事件,却用 onMounted(只执行一次)+ watch 链代替 |
visible 从 false→true 没有任何代码响应,重开=重新挂载=重走脆弱链 |
一句话:该用 watch(visible) 驱动播放(显式、每次打开都触发),却写成了 onMounted + 依赖 selectedCamera 恰好就绪(隐式、时序脆弱)。
问题 2:为什么查了这么多轮,从什么时候开始找到原因
分两个阶段:
阶段一:完全在错误层面打转,约 8 轮。
用户反馈"关闭重开失败"后,尝试了:黑屏检测、弹窗 v-show 常驻、防重入、对照组件同构、1s 延时——全部在"播放机制"层面改(重试/重建/时序),没有一次去验证"重开时播放代码到底执行了没有"。期间还带着"流媒体服务僵尸流"假设查了一轮后端。
阶段二:开始取证,2 轮找到。
- 第一步:在
setupWebrtc入口加日志 - 第二步:用户抓的第一份日志出现关键迹象(重开时刻无 ICE 日志)
- 第三步:走查代码发现
setupPlayer的静默 return 点 - 第四步:实施修复(
watch visible显式触发 + 代际机制) - 第五步:完整日志确认"重开时
setupWebrtc从未执行" - 第六步:实测通过
结论:真正定位从第一份带入口日志的 Console 输出开始,之后走查代码确认,最后日志铁证。
问题 3:为什么之前多轮没找到
| 原因 | 说明 |
|---|---|
| 没先取证,靠猜 | 一直"猜根因→改→让用户测",没有先设计"一次测试拿全证据"的手段。若第一次收到反馈时就加全入口日志,一轮就能看到"setupPlayer 没执行或 cameraNo 空" |
| 混淆两类问题 | "播放失败"(连接建了但没画面)vs "播放没被触发"(代码根本没跑)——一直在按前者修。最直接的证据被浪费:黑屏检测挂在 ontrack 里启动,播放没开始它永远不会触发——"黑屏检测从不触发"本身就是线索,没抓住 |
| "切换能播"没追到底 | 这是最强线索:切换走 watch cameraNo 路径能播 = 播放逻辑本身没问题,问题在触发源。应由此直接对比"打开路径"和"切换路径"的差异,而不是去查流媒体服务/ICE |
| 覆盖式修复掩盖真相 | videoRef 重试"修好"了首次打开,但让"触发链脆弱"这个本质问题藏得更深——后续机制越叠越多(重试+黑屏检测+play+v-show+常驻),越难看出"重开时压根没触发" |
| 内网环境放大成本 | 看不到实时状态,每轮只能靠用户反馈,"猜-改-测"循环成本极高。正确做法是一次性加全关键路径日志,一轮测试拿到完整链路证据 |
沉淀的教训
- 编码:组件显隐是生命周期事件,用
watch(visible)显式驱动,不依赖onMounted+ watch 链的时序巧合;失败路径不允许静默 return。 - 排查:先取证后改码;机制无效本身就是证据(黑屏检测不触发 = 播放没开始);"X 能播"要追到"X 走了哪条代码路径",用它对比失败路径,而不是拿去猜服务端。
最大教训:"能跑"和"对"是两回事——代码在"首次打开"这个时序下能跑,不代表它是对的,它依赖的时序假设在其他场景(关闭重开)会断。

浙公网安备 33010602011771号