通过服务器在指定时间将网页录制成视频
通过服务器在指定时间将网页录制成视频#
为什么有这样的需求?#
笔者最近的工作在前端数据可视化领域,会出现一些对长时间运行的前端页面进行监控的需求。以往我的解决办法是通过一些现有的平台,在个人PC上通过浏览器进行录制,或者更早的方法是通过一些录屏工具进行录制。
在这样的方式中,经常会遇到以下问题:
- 分辨率不够还原
- 录制的日志格式难以解析
- 需要长期的打开个人电脑
- 通过平台录制的,往往不是视频,而是一段DOM-Mirror的记录。这样的记录很难分享给其他人进行问题排查
- DOM-Mirror记录进行回放时,对于后端返回的实时数据渲染,缺少价值(因为当时的时间点已经错过了,回放时无法回放后端当时的服务状态)
- 并发录制个数受限于个人电脑的性能
- 录制后的文件不好管理
我的目标#
So,基于上述的需求,我们需要达到以下的要求:
- 能在网页要求的原始分辨率情况下进行录制
- 能在服务端而不是个人电脑上进行录制
- 能录制通用的视频和日志文件,可以方便的分享给他人
- 能进行并发录制
- 视频帧数要足够流畅(至少4K下)
- 为录制的文件提供静态资源访问服务
技术栈的选择#
- 基础语言和框架——js&nodejs
- 对于指定时间运行任务 —— cron job
- 对于打开网页 —— puppeteer
- 对于视频录制有以下备选方案
- 使用浏览器api
getDisplayMedia
进行录制 - 使用puppeteer按帧数截图,然后对图片用ffmpeg进行压制
- 使用xvfb将虚拟桌面的视频流直接通过ffmpeg进行编码录制
- 使用浏览器api
- 对于录制日志 —— puppeteer提供的devtools相关事件
- 对于并发处理 —— 引入加权计算
- 对于视频处理 —— ffmpeg
具体的实现方式#
一、现行方案#
该方案主要规避解决的问题:#
- 使用
getDisplayMedia
时,受到浏览器的协议限制。这个api只在访问协议为https下可用,且音频的录制需要依赖其他的api。 getDisplayMedia
的性能,在多网页并发录制时优化空间小,而且最致命的问题时,录制过程的性能开销,是由浏览器负担的。这意味着,如果页面本身对性能比较敏感,使用这个api基本无法录制出网页正常运行的情况。- puppeteer按帧数截图受到了chrome-devtools本身的限制,导致一秒只能截取出10+图。在数据可视化的场景中,大量的实时数据渲染,显然也是无法接受的。
核心流程#
关键点:#
- 使用node调用xvfb,创建虚拟桌面:开源库
node-xvfb
存在一些问题,创建的虚拟桌面,似乎共享了同一个流的缓冲区,在并发录制时,会出现抢占的情况,导致视频内容出现加速,所以需要封装一个新的node调用xvfb的功能
1 | import * as process from 'child_process'; |
- 服务器并发录制时进行负载均衡。这个功能是为解决并发录制视频编码时,服务器CPU的负载过高问题。所以为了尽可能的提高并发录制数量,我记录了每个服务器正在和将要执行的任务数量,将这个数量标记为服务的权重,当创建一个新的录制任务时,先检测当前服务器的权重,然后在权重最低的服务器上创建录制任务,并在录制完成和手动终止任务时,降低权值。
1 | import { CronJob } from 'cron'; |
启动puppeteer时,需要提供一系列参数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22const browser = await puppeteer.launch({
headless: false,
executablePath: '/usr/bin/google-chrome',
defaultViewport: null,
args: [
'--enable-usermedia-screen-capturing',
'--allow-http-screen-capture',
'--ignore-certificate-errors',
'--enable-experimental-web-platform-features',
'--allow-http-screen-capture',
'--disable-infobars',
'--no-sandbox',
'--disable-setuid-sandbox',//关闭沙箱
'--start-fullscreen',
'--display=:' + display,
'-–disable-dev-shm-usage',
'-–no-first-run', //没有设置首页。
'–-single-process', //单进程运行
'--disable-gpu', //GPU硬件加速
`--window-size=${width},${height}`,//窗口尺寸
],
});
方案性能(docker中)#
- 标准1k分辨率下:双核CPU 2.3Ghz; 4G ram下,并发数10个
- 标准2k分辨率下:双核CPU 2.3Ghz; 4G ram下,并发数4个
二、尝试过的方案#
getDisplayMedia模式#
关键点#
该api的调用,会导致chrome弹出选择具体录制哪个网页的交互窗口。关闭这个窗口需要在启动puppeteer时启用以下参数
1
2
3
4
5
6
7
8
9'--enable-usermedia-screen-capturing',
`--auto-select-desktop-capture-source=recorder-page`,
'--allow-http-screen-capture',
'--ignore-certificate-errors',
'--enable-experimental-web-platform-features',
'--allow-http-screen-capture',
'--disable-infobars',
'--no-sandbox',
'--disable-setuid-sandbox',执行录制时,需要通过puppeteer
page.exposeFunction
注入函数进行执行。
Q&A#
Q:为什么要引入xvfb?
A:在尝试的方案中,getDisplayMedia需要运行环境提供一个桌面环境。在现行方案中,则是需要把xvfb的视频流直接推入到ffmpeg中
Q:为什么对内存有一定要求?
A:提供chrome的最小运行内存
项目地址#
https://github.com/sadofriod/time-recorder
通过服务器在指定时间将网页录制成视频