跳到主内容
Zhimalab

从零到顺滑:我与 AI 搭档,打造网页端实时视频合成器的调试实录

Mon Dec 01 2025 08:00:00 GMT+0800 (中国标准时间)

这是一篇来自开发者视角的调试记录。我想要一个“网页端实时合成视频”的小工具:以摄像头作为背景,再把用户选择的窗口/屏幕画面叠加上去;还能预览、录制、全屏,甚至按指定分辨率导出。借助 AI 作为我的结对搭档,我们一路把它从想法打磨成了一个顺滑可用的工具,并在过程中解决了几个关键的音视频/Canvas 小坑。

备注:文中标注“[待补截图]”的位置,我会后续补充实际运行截图,便于参考。

需求拆分

  • 摄像头画面作为背景,屏幕/窗口作为叠加层(画中画)。
  • 支持两路都全屏、支持分屏(水平/垂直)。
  • 支持全屏预览、实时录制(WebM)、FPS 可调。
  • 清晰度要好:小字号文字要尽量可读(锐化/HiDPI)。
  • 画面不要变形:可选输出分辨率,尽量保持比例。

[待补截图:主界面与控制面板]

实现路线(与 AI 的迭代)

  1. React + Astro 集成
  • 延续项目现有模式:工具是 React 组件,页面用 Astro 挂载。
  • 新建 src/components/tools/VideoComposer.jsx + src/pages/tools/video-composer.astro
  1. 基础能力搭建
  • 采集:getUserMedia 获取摄像头、getDisplayMedia 获取屏幕/窗口。
  • 合成:用 <canvas> 作为合成画布,drawImage 叠加两路视频。
  • 录制:canvas.captureStream(fps) + MediaRecorder → WebM 下载。
  1. 易用性增强
  • 画中画叠加层:支持缩放、位置、圆角、透明度、拖拽移动。
  • 背景源切换:可由“摄像头”或“屏幕”充当背景源。
  • 全屏预览:requestFullscreen 进入/退出全屏。
  1. 清晰度与布局
  • 锐化开关:ctx.imageSmoothingEnabled = !sharp,让小字体更清楚。
  • HiDPI 支持:按 devicePixelRatio 放大底层像素,保持锐利。
  • 布局模式:画中画(pip)/ 水平分屏 / 垂直分屏。
  1. 分辨率控制
  • 下拉预设:1024x5761280x7201920x1080 等常见档位。
  • 目的:减少变形现象、与录屏目标/直播参数对齐。

[待补截图:分屏、画中画与分辨率选择]

关键问题与修复

1. “ReferenceError: Cannot access ‘hiDPI’ before initialization”

现象:一次大改后,组件报 hiDPI 在初始化前被访问。根因是补丁插入顺序错乱,变量在声明之前被引用,且有代码片段被插到函数外。

解决:回退并重写受影响片段,确保状态声明顺序与作用域正确;将分辨率逻辑封装到 setupCanvasSize(),避免散落的“裸代码”。

小结:大的结构调整最好原子化替换,避免多次小补丁导致文件结构被破坏。

2. 全屏预览变形/模糊

现象:进入全屏后,窗口中文字变形或变糊。

优化:

  • HiDPI:底层画布用 canvas.width = w * dpr; canvas.height = h * dpr;,再 ctx.scale(dpr, dpr)
  • 锐化:可选关闭插值平滑(imageSmoothingEnabled=false),让细小文字更清楚;
  • 分辨率预设:输出逻辑尺寸可控,减少拉伸。

3. 1920×1080 仍出现“右/下边缺一条边”(大约几十像素)

现象:系统分辨率和输出分辨率都设为 1920×1080,但右侧与下方被“吃掉”了一点。

根因:高分屏下我们对 ctx 做了缩放(ctx.scale(dpr, dpr)),但绘制与拖拽逻辑使用了 canvas.width/height(设备像素)进行计算,等于“重复缩放”了一次,导致右/下边被裁切。

修复:统一使用“逻辑尺寸”(CSS 像素)进行一切坐标/尺寸计算:

// 1) 在 setupCanvasSize 中设置底层像素并记录逻辑尺寸
const dpr = hiDPI ? window.devicePixelRatio || 1 : 1;
canvas.width = Math.floor(w * dpr);
canvas.height = Math.floor(h * dpr);
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.scale(dpr, dpr);
setCanvasLogicalSize({ w, h });

// 2) 在 draw/drag 中使用逻辑尺寸(w/h),而不是 canvas.width/height
const w = canvasLogicalSize.w || canvas.width;
const h = canvasLogicalSize.h || canvas.height;
ctx.drawImage(bgVideo, 0, 0, w, h);

效果:在 1920×1080 下不再发生右/下边裁切,全屏与录制画面一致。

[待补截图:裁切前后对比]

与 AI 协作编程:优势与心得

  • 思路加速:我提需求,AI 很快给出实现骨架与关键 API(MediaRecorder、captureStream、getDisplayMedia 等)。
  • 迭代效率:从“画中画”到“分屏”、“锐化/HiDPI”、“分辨率预设”,我只需描述体验问题,AI 就能补全改动范围并给出补丁。
  • 排错定位:出现 ReferenceError 与 HiDPI 裁切问题时,AI 能快速推演根因并给出最小修复面。
  • 认知扩展:围绕 Canvas 与媒体流的细节(如 DPR 下的坐标系),AI 会辅助我建立更系统的心智模型。

建议的协作姿势:

  • 目标清晰 + 期望行为:描述“你看到的现象”和“你想要的感觉”,而不是仅仅“怎么实现”。
  • 小步提交:复杂改动尽量拆分,并请求 AI 提供“原子化”补丁,降低合并风险。
  • 用真实参数测试:比如固定到 1920×1080,观察清晰度、黑边、变形、裁切与 FPS 实际效果。

注意事项(踩坑清单)

  • 浏览器权限:媒体采集需要 HTTPS 或 localhost 环境,并确认用户授权。
  • 录制兼容:MediaRecorder 在不同浏览器的编码支持不同(vp9/vp8/webm),需要按 isTypeSupported 回退。
  • DPR 坐标系:HiDPI 下要区分“逻辑尺寸 vs 底层像素”,避免双重缩放。
  • 画面比例:当源与目标分辨率比例不一致时,考虑提供“适配模式”(contain/cover/拉伸)开关。
  • 流结束事件:getDisplayMedia 的轨道会触发 ended,注意清理 UI 状态与流对象。
  • 大补丁风险:多次小打小闹容易把文件结构搞乱,必要时直接“整体重写替换”。

下一步规划

  • 自定义分辨率输入与等比适配模式(contain/cover)。
  • 分屏比例拖动条(而非按源分辨率比例分配)。
  • 叠加层“贴边吸附/安全边距”与辅助线。
  • 音频混流(如需要系统音频/麦克风)。

[待补截图:全屏预览、录制出片]

结语

这次和 AI 的协作既高效又安全:我负责目标与验收,AI 负责实现和修复建议;每一步都能解释清楚“为什么这样做”。如果你也在做网页端音视频类的小工具,欢迎把这份踩坑单和修复经验拿去直接用。

有任何细节想看源码片段或需要截图说明的地方,留言告诉我,我补图/补代码。