最近做了一个实验项目,目标是在浏览器请求摄像头时,不是调用本机摄像头,而是播放指定视频,以达到“模拟摄像头”的效果。
区别于传统做法(比如使用虚拟驱动/fakevideo),我采用的是拦截 + hook JS 的思路,更轻量,也无需系统级驱动支持。
一、浏览器摄像头调用原理
网页通过如下方式调用摄像头:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| navigator.mediaDevices.getUserMedia({ video: true, audio: false }) .then(stream => { video.srcObject = stream; }) ````
调用后,浏览器会请求系统的物理摄像头,生成一个 MediaStream 并赋给 `<video>` 元素的 `srcObject` 属性。
---
## 二、hookVideo 的思路与实现
核心思路:**拦截网页中 `getUserMedia()` 的调用**,将返回的摄像头流替换为我们自己的 **本地视频流**(通过 `canvas.captureStream()` 实现)。
### 示例:
```js function hookVideo(videoPath) { const video = document.createElement('video'); video.src = videoPath; video.muted = true; video.loop = true; video.play();
const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d');
video.addEventListener('loadeddata', () => { canvas.width = video.videoWidth; canvas.height = video.videoHeight;
function draw() { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); requestAnimationFrame(draw); } draw(); });
const fakeStream = canvas.captureStream();
const originalGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); navigator.mediaDevices.getUserMedia = async function (constraints) { if (constraints.video) { return Promise.resolve(fakeStream); } return originalGetUserMedia(constraints); }; }
|
✅ 上述代码实现了:替换网页摄像头请求 → 本地循环播放视频流 → 模拟“虚拟摄像头”效果。
三、为什么不是 fakevideo
传统做法 fakevideo(如 v4l2loopback)是在系统层注册一个虚拟摄像头设备,需要:
但我只是想在某个网站上替换视频,不需要改系统设备,所以选择了 JS Hook。
四、Python + OBS 的实现思路(扩展)
有时候我们希望传入的是实时生成的画面,而不是本地视频文件,可以用 Python + OBS 来实现一个“伪直播摄像头”:
- Python 脚本实时绘制画面(如 OpenCV + 图像识别)
- OBS 推流输出
- 使用 OBS-VirtualCam 插件创建虚拟摄像头设备
- 这样网页就能读取到 OBS 的合成画面作为摄像头输入
虽然这种方式仍需虚拟设备支持,但它适用于高级场景(如直播、AI 实时标注等)。
如果你也有这种定制需求,比如只想在某个学习/会议/网课网站替换摄像头,JS hook + 视频 canvas 是个非常轻便的方式,几行代码即可完成。