web录屏方案实现

手机游戏开发者 2024-9-22 20:34:06 70 0 来自 中国
配景

研发过程中,对于部分偶发时间,非常监控体系仅仅只能告诉步调堕落,而不能清楚告知错误的复现路径,因而录制视频的需求应运而生,比方可在在线测验 长途监考 屏幕共享 保险产物购买回溯等场景中应用到此技术,这也为以后的在线监控创造了条件。
范例:

有感录屏

什么是有感录屏

有感录屏一样平常指通过得到用户的授权大概关照用户接下来的操纵将会被录制成视频,并且在录制过程中,用户有权关闭停止录屏。即无论在录屏前还是录屏的过程中,用户都始终可以大概决定录屏可否举行。
技术实现方案

基于 WebRTC 的有感录屏
常见的有感录屏方案重要是通过 WebRTC 录制。WebRTC 是一套基于音视轨的及时数据传播播的技术方案。由欣赏器提供的原生 API navigator.mediaDevices.getDisplayMedia 方法实现提示用户选择和授权捕获展示的内容或窗口,进而将获取 stream (录制的屏幕音视流)。我们可以对 stream 举行转化处理惩罚,转成相对应的媒体数据,并将其数据存储。后续必要回溯该次录制内容时,则取出媒体数据举行播放。
详细的有感录屏流程如下:

实现初始化录屏和数据存储
利用navigator.mediaDevices.getDisplayMedia 初始化录屏,触发弹窗获取用户授权,效果图如下所示:


实现 WebRTC 初始化录屏核心代码如下:
const tracks = []; // 媒体数据const options = {  mimeType : "video/webm; codecs = vp8", // 媒体格式};let mediaRecorder;// 初始化哀求用户授权监控navigator.mediaDevices.getDisplayMedia(constraints).then((stream) => {  // 对音视流举行操纵  startFunc(stream);});// 开始录制方法function start(stream) {  // 创建 MediaRecorder 的实例对象,对指定的媒体流举行录制  mediaRecorder = new MediaRecorder(stream, options);  // 当天生媒体流数据时触发该变乱,回调传参 event 指本次天生处理惩罚的媒体数据  mediaRecorder.ondataavailable = event => {     if(event?.data?.size > 0){      tracks.push(event.data); // 存储媒体数据    }  };  mediaRecorder.start();  console.log("************开始录制************")};// 竣事录制方法function stop() {  mediaRecorder.stop();  console.log("************录制竣事************")}// 界说constraints数据范例interface constraints {  audio: boolean | MediaTrackConstraints, // 指定是否哀求音轨大概束缚轨道属性值的对象  video: boolean | MediaTrackConstraints, // 指定是否哀求视频轨道大概束缚轨道属性值的对象}实现录屏回溯
获取该次录屏的媒体数据,可以将其转成 blob 对象,并且天生 blob对象的 url 字符串,再赋值 video.src 中,便可以回放到录制效果,回溯的视频效果如下:


录屏回溯方法的核心代码如下所示:
// 回放录制内容function replay() {  const video = document.getElementById("video");  const blob = new Blob(tracks, {type : "video/webm"});  video.src = window.URL.createObjectURL(blob);  video.srcObject = null;  video.controls = true;  video.play();}欣赏器兼容性

4.png
无感录屏

什么是无感录屏

无感录屏指在用户无感知的情况,对用户在页面上的操纵举行录制。实现上与有感录制区别在于,无感录制通常是利用记载页面的 DOM 来举行录制。常见的有 canvas 截图绘制视频和 rrweb 录制等方案。
canvas截图绘制视频

用户在欣赏页面时,可以通过 canvas 绘制多个 DOM 快照截图,再将多个截图归并成一段录屏视频。但是思量到假设视频帧数为 30 帧,帧数代表着每秒所需的截图数目,为了视频的流通和清楚,每张截图为 400 KB ,那么当视频长度为 1 分钟,则必要上传 703.125 MB 的资源,这么大的带宽浪费无疑会造成性能,以致影响用户体验,不保举利用,也不在此详细先容本方案实现。
rrweb录制

是什么

rrweb (record and replay the web) 是一个对于 DOM 录制的支持性非常好,利用当代欣赏器所提供的强大 API 录制并回放恣意 web 界面中的用户操纵,可以大概将页面 DOM 结构通过相应算法高效转换 JSON 数据的开源库。相比力于利用 canvas 绘制录屏,rrweb 在包管录制不掉帧的底子上,让网络传输数据更加速速和轻量化,极大地优化了网络性能。
焦颔首脑

1)DOM快照
当我们想要查察用户在投保过程中某一时候的页面状态时,我们只必要将那一刻的页面 dom 结构,以及页面中的 css 样式记载下来,然后在欣赏器中重新渲染出来就能达到回溯的效果了。
const cloneDoc  = document.documentElement.cloneNode(true); // 录制document.replaceChild(cloneDoc, document.documentElement); // 回放如许我们就实现了某一时候 DOM 快照的功能。但是这个录制的 cloneDoc 还只是内存中的一个对象,并没有实现长途录制。
序列化
为了实现长途录制,我们必要将 cloneDoc 这个对象序列化成字符串,生存到服务端,然后在回放的时间从服务器上取出来,交给欣赏器重新渲染。
const serializer = new XMLSerializer(); // XMLSerializer 是欣赏器自带的 api,可以将 dom 对象序列化成 stringconst str = serializer.serializeToString(cloneDoc);document.documentElement.innerHTML = str;至此,我们就完成了对用户界面某一时候的长途录制功能。
2)定时快照
但是我们的目标是录制视频,只有一个 dom 快照显然是不敷的。相识动画的同学都应该知道,动画是由每秒至少 24 帧的画面按次序播放而产生的。在这里趁便科普一下这块的知识,当我们人眼观察到一个物体之后,这个画面会在我们的视网膜中停顿 16.7ms 左右的的时间,专业名词叫做视觉停顿,那么详细到给我们的感觉就是这个画面是“徐徐”消散的。
那么当我们在播放动画的时间,当第一帧画面在我们的视网膜中刚刚消散的时间,把第二帧放出来,那么给人的感觉就是画面是连续的,是在动的。但是动画里的人物动作给人的感觉还是有点卡顿、有点不自然的,为什么呢?我们来算一下: 1 秒/24 帧 = 41.7 毫秒,远远低于人眼可分辨的 16ms 的隔断,所以我们会以为有点卡卡的。
为了达到更加流通的画面,很多游戏和影戏都会接纳 60 帧/秒的速率来放映画面,由于 1 秒/60 帧 = 16.7ms,和人眼视觉停顿的时间差不多,所以会感觉到画面很流通。可以看一下你的电脑屏幕,一样平常的革新率也是 60 帧。

扯远了,我们回归正题。由上面的知识我们知道,既然我们想要录制视频,那么至少每秒必要 24 帧的数据,也就是说 1000ms/24 帧 = 41.7 毫秒要 clone 一遍网页内容。
setInterval(() => {    const cloneDoc  = document.documentElement.cloneNode(true)    const str = serializer.serializeToString(cloneDoc);    axios.post(address,str); // 生存到服务端}, 41.7)如今我们可以让画面动起来了,但是轻微细想便可知道这种方法根本行不通,缘故起因有一下几点:
每秒 clone 24 次整个页面内容,对性能斲丧巨大,严峻影响用户体验
每秒要将 24 帧的页面内容上传到服务端,对网络开销也是巨大的
回放时,每秒要渲染 24 个完备的 html 内容,欣赏器根本做不到这么快
另有,要是页面没变动,那么 24 帧的数据大概是完全一样的,根本没须要 clone 这个多次。
3)增量快照
基于以上定时快照的缺点,实在我们可以只在页面初始化完成之后 clone 一次完备的页面内容,比及页面有变动的时间,只记载变革的部分。如许一来,利益就显而易见了:
只记载变革的部分,比起记载整个网页要小的多。如许对网页的性能、网络的开销都会小很多。
我们只在页面有变动的时间才记载,如许一来,大量重复数据的标题也给办理了。
回放时,我们只必要起首将第一帧(完备的页面内容)先渲染出来,然后在按照记载的时间,按次序将变革的部分渲染到页面。如许就可以像看视频一样往返溯用户的操纵流程了。

6.png 举个例子,如上图所示,页面中一共有 4 个 div。页面有两次变革,第一次 dom2 酿成了赤色,第二次变革 dom4 酿成了绿色。那么我们记载的数据大抵是这个样子
var events = [    {完备的 html 内容},    {        id: 'dom2',        type: '#fff -> red'    },    {        id: 'dom4',        type: '#fff -> green'    }]记载的数据是一个数组,数组中有 3 个原始,第一个元素是完备的 html 内容,第二个元素形貌的是 dom2 酿成了赤色,第三个元素形貌的是 dom4 酿成了绿色。 然后我们根据上诉记载的数据,就可以起首将 events[0] 渲染出来,然后实验 events[1] 将 dom2 酿成赤色,再将 dom4 酿成绿色。 如许我们在理论上就完成了从页面的录制,到生存到长途服务器,再到末了回放,形成了功能上的完备的闭环。
4)MutationObserver
在上一步中,我们已经从理论上实现了录制和回放的功能。但是详细实现呢?我们怎么才气知道页面什么时间变革呢?变革了哪些东西呢? 实际上欣赏器已经为我们提供了非常强大的 API,叫做 MutationObserver。它会以批量的方式返回 dom 的更新记载。 还是拿上面的例子来阐明,改变一下 dom2 和 dom4 的配景致
setTimeout(() => { let dom2 = document.getElementById("dom2");  dom2.style.background = "red";  let dom4 = document.getElementById("dom4");  dom4.style.background = "green";}, 5000);const callback = function (mutationsList, observer) {  for (const mutation of mutationsList) {    if (mutation.type === "childList") {      console.log("子元素增长大概删除.");    } else if (mutation.type === "attributes") {      console.log("元素属性发生改变");    }  }};document.addEventListener("DOMContentLoaded", function () {  const observer = new MutationObserver(callback);  observer.observe(document.body, {    attributes: true,    childList: true,    subtree: true,  });});得到的回调数据是如许的

7.jpeg 可以看到,MutationObserver 只记载了变革的 dom 元素(target),和变革的范例(type)。云云一来,我们便可以利用 MutationObserver 实现增量快照的思绪。
5)可交互元素
利用 MutationObserver 我们可以记载元素的增长、删除、属性的更改,但是它没法跟踪像 input、textarea、select 这类可交互元素的输入。 对于这种可交互的元素,我们就必要通过监听 input 和 change 来记载输入的过程,如许我们就办理了用户手动输入的场景。 但是有些元素的值是通过步调直接设置的,如许是不会出发 input 和 change 变乱的。这种情况下我们可以通过挟制对应属性的 setter 来达到监听的目标。
const input = document.getElementById("input");Object.defineProperty(input, "value", {  get: function () {    console.log("获取 input 的值");  },  set: function (val) {    console.log("input 的值更新了");  },});input.value = 123;以上就是欣赏器录制和回放的大要思绪,也是开源工具 rrweb(record replay  web)的焦颔首脑。固然 rrweb 中还记载了鼠标的移动轨迹、欣赏器窗口的巨细,增长了回放时的沙盒情况、时间校准等等,在这里不再赘述,有爱好的同学可以自行查阅 rrweb 官网的先容。
利用方法
以上篇幅重要先容了 rrweb 录制和回放的焦颔首脑,这里大抵先容一下它的利用方法。更多利用姿势请查察 rrweb 利用指南。 通过 npm 引入
npm install --save rrweb录制
const events = []let stopFn = rrweb.record({ emit(event) {   if (events.length > 100) {     // 当变乱数目大于 100 时停止录制     stopFn();     // 将 events 序列化成字符串,并保持到服务器   } },});回放
const events = []; //从服务端取出记载并反序列化成数组const replayer = new rrweb.Replayer(events);replayer.play();静态资源时效标题

8.png
可以看到录制的数据中存在外链的图片,也就是说在我们利用录制的数据举行回放的时间,必要依靠这张图片。但是随着项目标迭代,这张图片很大概早已不在,这时我们再回放时,页面中的图片就会加载不出来。 实在不但是图片,外链的 css、字体文件等等都有这个标题。再回到文章开头提到的保险场景,保额信息就在网站内的一张海报上,客户大概会说:“我当时看到的保额显着是 150 万,怎么如今酿成 100 万了?”,这时你要怎么证实当时海报上写的就是 100 万保额呢?
JSON 转视频
所以最稳妥的方案还是将 rrweb 录制的原始数据转换成视频,如许一来,不管网站怎么变革,迭代了多少版本,视频是不受影响的。 我的做法是通过 puppeteer 在服务端运行无头欣赏器,在无头欣赏器中回放录制的数据,然后每秒截取肯定命目标图片,末了通过 ffmpeg 合成视频。下面是大抵的流程图


帧率 我这里是一秒 50 帧,也就是说每隔 20ms 要截一张图。 截图时机 这里有个坑,puppeteer 截一张图的时间大概必要 300ms,假设页面在回放的过程中,我们利用 setInterval 每隔 20ms 实验一次截图,那么两次截图动作之间实在相隔了一次截图的时间,差了靠近 300ms。第二帧我们想要截取的是视频地 20ms 的数据,但是回放页面已经播放到 320ms 处了。
暂停播放 为办理截图耗时所带来的影响,在每次截图之前,我将回放视频暂停到对应的时间点,如许截取到的就是我们想要的画面了
updateCanvas () {    if (this.imgIndex * 20 >= this.timeLength) {      this.stopCut(); // 事先盘算整个视频必要截多少帧,截满了就竣事      return;    }    // 截图    this.iframe.screenshot({      type: 'png',      encoding: 'binary',    }).then(buffer => {      this.readAble.push(buffer) //生存截图数据到可读流中      this.page.evaluate((data) => {        window.chromePlayer.pause(data * 20); // 将回放页中的视频暂停到对应时间点      }, this.imgIndex)      this.updateCanvas(this.imgIndex++)     })}输出视频
  stopCut () {    this.readAble.push(null) // 截图完成后,必要给可读流一个 null,体现没有数据了    this.ffmpeg    .videoCodec('mpeg4')  // 视频格式,这里我输出的是 mp4    .videoBitrate('1000k') // 每秒钟视频所占用的巨细,这个是视频清楚度的关键指标    .inputFPS(50) // 帧率,这个是视频流通度的关键指标,必要和每秒截图的数目保持一致    .on('end', () => {      console.log('\n 视频转换乐成')    })    .on('error', (e) => {      console.log('error happend:' + e)    })    .save('./res.mp4') // 输出视频  }
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 16:46, Processed in 0.229126 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表