这是一篇来自开发者视角的调试记录。我想要一个“网页端实时合成视频”的小工具:以摄像头作为背景,再把用户选择的窗口/屏幕画面叠加上去;还能预览、录制、全屏,甚至按指定分辨率导出。借助 AI 作为我的结对搭档,我们一路把它从想法打磨成了一个顺滑可用的工具,并在过程中解决了几个关键的音视频/Canvas 小坑。
备注:文中标注“[待补截图]”的位置,我会后续补充实际运行截图,便于参考。
需求拆分
- 摄像头画面作为背景,屏幕/窗口作为叠加层(画中画)。
- 支持两路都全屏、支持分屏(水平/垂直)。
- 支持全屏预览、实时录制(WebM)、FPS 可调。
- 清晰度要好:小字号文字要尽量可读(锐化/HiDPI)。
- 画面不要变形:可选输出分辨率,尽量保持比例。
[待补截图:主界面与控制面板]
实现路线(与 AI 的迭代)
- React + Astro 集成
- 延续项目现有模式:工具是 React 组件,页面用 Astro 挂载。
- 新建
src/components/tools/VideoComposer.jsx+src/pages/tools/video-composer.astro。
- 基础能力搭建
- 采集:
getUserMedia获取摄像头、getDisplayMedia获取屏幕/窗口。 - 合成:用
<canvas>作为合成画布,drawImage叠加两路视频。 - 录制:
canvas.captureStream(fps)+MediaRecorder→ WebM 下载。
- 易用性增强
- 画中画叠加层:支持缩放、位置、圆角、透明度、拖拽移动。
- 背景源切换:可由“摄像头”或“屏幕”充当背景源。
- 全屏预览:
requestFullscreen进入/退出全屏。
- 清晰度与布局
- 锐化开关:
ctx.imageSmoothingEnabled = !sharp,让小字体更清楚。 - HiDPI 支持:按
devicePixelRatio放大底层像素,保持锐利。 - 布局模式:画中画(pip)/ 水平分屏 / 垂直分屏。
- 分辨率控制
- 下拉预设:
1024x576、1280x720、1920x1080等常见档位。 - 目的:减少变形现象、与录屏目标/直播参数对齐。
[待补截图:分屏、画中画与分辨率选择]
关键问题与修复
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 负责实现和修复建议;每一步都能解释清楚“为什么这样做”。如果你也在做网页端音视频类的小工具,欢迎把这份踩坑单和修复经验拿去直接用。
有任何细节想看源码片段或需要截图说明的地方,留言告诉我,我补图/补代码。