如何将录制的DOM转成视频文件

760 阅读2分钟

「这是我参与2022首次更文挑战的第16天,活动详情查看:2022首次更文挑战」。

Dear,大家好,我是“前端小鑫同学”,😇长期从事前端开发,安卓开发,热衷技术,在编程路上越走越远~


背景说明:

     最近有在看GitHub上的rrweb项目,确实是一款DOM录制的神器,在使用文档中提供了很多我们会用到的场景和对应的示例,我们今天来看一下其中一个场景《转换为视频》,虽然rrweb直接回放的效果最佳但还是会遇到需要转为视频进行存储的要求,通过查看rrweb提供的rrvideo项目后决定写一下整个转换的过程,大致的流程图如下:

绘图1.png

环境配置:

  1. 安装FFmpeg:用于将逐帧的图片数据转换为视频。
  2. 安装puppeteer:用于在后台加载网页。
  3. 安装rrweb-player:用于播放rrweb录制的events数据。

使用puppeteer打开空白页面:

  1. 获取browser对象实例:browser = await puppeteer.launch({ headless: true });
  2. 打开新页签:page = await browser.newPage();&await page.goto("about:blank");
  3. 通过page.exposeFunction在window对象上挂载开始和结束录制的调用函数;
  4. 将需要播放的events数据使用page.setContent()加载进页面。
    try {
      browser = await puppeteer.launch({ headless: true });
      page = await browser.newPage();
      await page.goto("about:blank");
      // 扩展启动录制函数
      await page.exposeFunction("onReplayStart", async () => {
        await startReplay();
      });
      // 扩展结束录制函数
      await page.exposeFunction("onReplayFinish", async () => {
        await finishReplay();
      });
      // 读取原数据
      const events = JSON.parse(
        fs.readFileSync(path.resolve(process.cwd(), _input), "utf-8")
      );
      await page.setContent(getHtml(events));
    } catch (error) {
      console.log("openPage:", error);
    }
    

组装最简支持rrweb-player播放的DOM结构:

  1. 获取安装到node_modules内的rrweb-player包的内容,便于插入到DOM中;

    // 获取rrweb-player的脚本插入到DOM中
    const rrwebScriptPath = path.resolve(
      require.resolve("rrweb-player"),
      "../../dist/index.js"
    );
    const rrwebStylePath = path.resolve(rrwebScriptPath, "../style.css");
    const rrwebRaw = fs.readFileSync(rrwebScriptPath, "utf-8");
    const rrwebStyle = fs.readFileSync(rrwebStylePath, "utf-8");
    
  2. 拼装满足rrweb-player播放的基础DOM,其中在replayer.play()函数执行前开启录制并在监听到播放完成后结束录制:

    const html = `
            <html>
              <head>
                  <style>${rrwebStyle}</style>
              </head>
              <body>
                <script>
                  ${rrwebRaw};
                  /*<!--*/
                  const events = ${JSON.stringify(events).replace(
                    /<\/script>/g,
                    "<\\/script>"
                  )};
                  /*-->*/
                  window.replayer = new rrwebPlayer({
                    target: document.body,
                    props: {
                      events,
                      showController: false,
                    },
                  });
                  window.onReplayStart();
                  window.replayer.play();
                  window.replayer.addEventListener('finish', () => {
                      window.onReplayFinish()
                  });
                </script>
              </body>
            </html>
            `;
    

通过puppeteer提供的screenshot函数定时截屏获取数据流:

  1. 获取到需要录制的元素对象:const wrapperEl = await page.$(".replayer-wrapper");
  2. 通过screenshot来截取当前帧的画面,返回数据类型为二进制数据。
    const buffer = await wrapperEl?.screenshot({
      encoding: "binary",
    });
    

执行ffmpeg命令并将截屏数据输入到ffmpeg进程:

  1. 我们使用NodeJs提供的spawn函数来执行FFmpeg命令,此处未配置环境变量而直接引用的FFmpeg的绝对路径:

    const ffmpegProcess = spawn("D:\\ffmpeg\\bin\\ffmpeg", [
        // fps
        "-framerate",
        "15",
        // input
        "-f",
        "image2pipe",
        "-i",
        "-",
        // output
        "-y",
        _output,
      ]);
    
  2. 将截图得到的二进制数据写入ffmpegProcess进程的标准输入流中:ffmpegProcess.stdin.write(buffer);

总结说明:

  1. 以上就是对rrvideo流程拆解一些关键点说明,完整代码在GitHub
  2. rrvideo还提供了常用的一些配置项来便于调整视频的尺寸等信息。
  3. puppeteer是继上次做自动生成骨架屏后的第二次使用。

欢迎关注我的公众号“前端小鑫同学”,原创技术文章第一时间推送。