原生Js基于WebRTC实现Web端屏幕录制、回放、下载功能

引言

一切的一切都开始于那个夜黑风高的夜晚......

那晚,我急需要一款录屏软件,找来找去,可是:

付费去广告、付费去水印、付费高清、付费下载😅......

等等,突然我灵光乍现,我还有一条路: 自己做!

于是原生Js基于WebRTC实现的Web端实时录屏功能 就出来了!文末附开源地址😎

先来试用一下成品吧→SHTRec←移动端暂不支持哦

技术选型

  • WebRTC
  • 原生Js

核心代码实现

html

整个的录屏功能区域都放在一个类名为main的div里面

类名为btn的div放上开始录屏button,并给button绑定start()函数,类名为rec

html 复制代码
<div class="main">
    <div class="btn">
        <button onclick="start()" class="rec">点击录屏</button>
    </div>
</div>

Js

js这里大体分为四部分:开始、结束、播放、下载

js 复制代码
function start() {
     ......
     /* 开始 */
}
function stop() {
     ......
     /* 结束 */
}
function replay() {
     ......
     /* 播放 */
}
function download() {
     ......
     /* 下载 */
}

为了提高代码可读性,先声明一些需要用到的全局变量

js 复制代码
let stream;
let mediaRecorder;
let blobs;
let blob;
let url;
let videoTracks;
let ale;
let span;
let word;
let delLastSpan;
let delSpan;
let video;
let a;

start()

我们使用Web API中的navigator.mediaDevices.getDisplayMedia方法来获取屏幕分享(包括音频和视频)的流

js 复制代码
function start() {
    navigator.mediaDevices.getDisplayMedia({ audio:true,video: true })
}

navigator.mediaDevices是Web API的一部分,它提供了访问用户的媒体输入设备(如摄像头、麦克风)和输出设备(如显示器、扬声器)的接口。

但我们只需要获取表示屏幕或应用程序窗口的媒体流,所以用到了getDisplayMedia
{ audio:true, video: true } 是一个约束对象,它指定了我们想要的媒体流的类型。我们需要获取的是包含音频(audio: true)和视频(video: true)的媒体流。

这个方法会返回一个Promise,该Promise在成功时解析会为一个MediaStream对象,该对象表示屏幕和音频的媒体流。随后我们将这个流传递给<video>元素

Promise成功后,会执行一个then异步操作方法

js 复制代码
.then(function (s) {
            stream = s;
            mediaRecorder = new MediaRecorder(stream, {
                mimeType: 'video/webm'
            });
            blobs = [], mediaRecorder;
            videoTracks = stream.getVideoTracks();
            if (videoTracks.length > 0) {
                mediaRecorder.start(100);
                mediaRecorder.ondataavailable = (e) => {
                    blobs.push(e.data);
                };
                document.body.innerHTML = `
                <div class="main">
                    <div class="container">
                        <span style="--d: 7"></span>
                        <span style="--d: 6"></span>
                        <span style="--d: 5"></span>
                        <span style="--d: 4"></span>
                        <span style="--d: 3"></span>
                        <span style="--d: 2"></span>
                        <span style="--d: 1"></span>
                        <span style="--d: 0"></span>
                        <span style="--d: 1"></span>
                        <span style="--d: 2"></span>
                        <span style="--d: 3"></span>
                        <span style="--d: 4"></span>
                        <span style="--d: 5"></span>
                        <span style="--d: 6"></span>
                        <span style="--d: 7"></span>
                    </div>
                </div> `;
            }
        })

s 是Promise的结果。

然后我们将Promise的结果赋值给 stream 变量: stream = s;

mediaRecorder = new MediaRecorder(stream, {mimeType: 'video/webm'});:创建一个新的 MediaRecorder 对象,它将用于记录 stream 中的媒体内容。在这里我们设置了mimeType为 'video/webm',因此记录的媒体内容将是 webm 格式的视频。

大家是不是会认为blobs = [], mediaRecorder;这行代码有些问题?它试图将两个不同的对象赋值给 blobs,这在 JavaScript 中是不合法的。你认为应该这样写:blobs = [mediaRecorder];?然而并不是,这行代码的意思是创建一个新的数组,其中包含两个元素:一个空数组(用来存放mediaRecorder 的可用数据)和 mediaRecorder 对象,然后将这个新数组赋值给 blobs 变量。
videoTracks = stream.getVideoTracks();是从 stream 中获取所有的视频轨道,并将它们存储在 videoTracks 变量中。

if (videoTracks.length > 0) {...}这行代码是在检查 videoTracks 中是否有视频轨道。如果有,我们才能将我们的项目继续下去

mediaRecorder.start(100);这行代码将开始 mediaRecorder 的录制,参数100意味着每100毫秒,mediaRecorder 会生成一个新的数据片段。

mediaRecorder.ondataavailable = (e) => {blobs.push(e.data);};这行代码是在当 mediaRecorder 开始录制后产生可用数据时,将数据添加到 blobs 数组中。

document.body.innerHTML = "...";这行代码是在将HTML代码赋值给 document.body.innerHTML,以动态改变html内容的方式来营造一个完美的用户体验,添加的HTML代码可以是一个动画来表示正在录屏,如下所示: 这个动画对应的css样式为

css 复制代码
:root {
    --h: 280px;
  }
  .container {
    display: flex;
    margin: 0 auto;
    align-items: center;
    height: var(--h);
  }
  .container span {
    background: linear-gradient(to top, #d299c2 0%, #fef9d7 100%);
    width: 50px;
    height: 20%;
    border-radius: calc(var(--h) * 0.2 * 0.5);
    margin-right: 4px;
    animation: loading 2.5s infinite linear;
    animation-delay: calc(0.2s * var(--d));
  }
  .container span:last-child {
    margin-right: 0px;
  }
  @keyframes loading {
    0% {
      background-image: linear-gradient(to right, #fa709a 0%, #fee140 100%);
      height: 20%;
      border-radius: calc(var(--h) * 0.2 * 0.5);
    }
    50% {
      background-image: linear-gradient(to top, #d299c2 0%, #fef9d7 100%);
      height: 100%;
      border-radius: calc(var(--h) * 1 * 0.5);
    }
    100% {
      background-image: linear-gradient(to top, #a8edea 0%, #fed6e3 100%);
      height: 20%;
      border-radius: calc(var(--h) * 0.2 * 0.5);
    }
  }

同时呢用户可能会拒绝给你权限来录制屏幕或发生一些其他的错误,那么就需要针对Promise的错误情况来做一些处理

js 复制代码
.catch(function (error) {
    console.log(error);
});

.catch(function (error) {...})是Promise的catch方法,它将在Promise发生错误时执行。error 是发生的错误对象。

然后我们用console.log(error);在控制台中打印出错误信息,让我们可以在控制台中看到错误的详细信息,从而更容易地调试和解决问题。

stop()

开始录制当然也要停止录制了,通过创建一个stop函数来控制录制

js 复制代码
function stop() {
    if (stream) {
        stream.getTracks().forEach(track => track.stop());
    }
    document.body.innerHTML = `
    <div class="main">
        <div class="scr">
            <span class="stopLog">录制完毕!</span>
        </div>
    </div> `;
}  

我们首先检查是否存在一个名为stream的变量。如果stream存在(也就是说,它不是null或者undefined),那么代码就会执行花括号里面的语句来停止录制。

如果stream不是null或者undefined``那么stream.getTracks()方法就会被调用。这个方法返回一个包含流中所有轨道的数组。

然后,用forEach()方法被用于遍历这个数组。对于数组中的每一个轨道(track),都会调用它的stop()方法,以停止该轨道。

document.body.innerHTML = "...";这行代码是在将HTML代码赋值给 document.body.innerHTML,以动态改变html内容的方式来提示用户录制结束,如下图所示:

replay()

当录制完毕后我们需要将录制好的视频供用户播放。

js 复制代码
function replay() {
    blob = new Blob(blobs, {
        type: 'video/webm'
    });
    document.body.innerHTML = `
    <div class="main">
        <div class="scr">
            <video class="vid" autoplay controls width="800px" height="280px"></video>
        </div>
    </div> `;
    video = document.querySelector(".vid");
    video.src = URL.createObjectURL(blob);
}

首先创建一个新的Blob对象,并使用先前存储的blobs数组和媒体类型'video/webm'
Blob对象表示一段二进制数据,在这里,它表示的是视频文件。

document.body.innerHTML = "...";这行代码和之前的是一个道理,将HTML代码赋值给 document.body.innerHTML,以动态改变html内容的方式来显示录制好的视频

接下来,获取这个新的video元素,并将其src属性设置为新创建的Blob对象的URL。这是通过调用URL.createObjectURL(blob)实现的,这个方法会返回一个新的URL,代表blob对象。这样,video元素就可以加载并播放这个视频了。
video元素具有autoplaycontrols属性,意味着视频将在加载后自动播放,并且用户可以控制视频的播放。

download()

使用录屏功能的根本目的还是为了下载到本地,所以我们再创建一个下载函数

js 复制代码
function download() {
    blob = new Blob(blobs, {
        type: 'video/webm'
    });
    url = URL.createObjectURL(blob);
    a = document.createElement('a');
    a.href = url;
    a.download = 'record.webm';
    a.style.display = 'none';
    a.click();
}

这里和replay()函数的原理大体一致,通过调用URL.createObjectURL(blob)返回一个新的URL,代表blob对象。

接下来创建一个新的<a>元素,并将其href属性设置为新创建的URL

然后将<a>元素的download属性设置为'record.webm',也就是将视频文件将被命名为record.webm

接着将<a>元素的style.display属性设置为'none',这样用户在页面上就看不到这个链接了

最后调用<a>元素的click()方法,这会模拟用户点击链接的行为,从而自动下载视频文件。

结语

看到到这里,实现屏幕的录制、回放、下载这个功能实现起来是不是也没有很难了?

下面是GitHub开源地址,如果这篇文章对你有所启发,欢迎点赞收藏+转发😁

Github已开源,欢迎star⭐

相关推荐
LuciferHuang2 小时前
震惊!三万star开源项目竟有致命Bug?
前端·javascript·debug
GISer_Jing2 小时前
前端实习总结——案例与大纲
前端·javascript
姑苏洛言3 小时前
编写产品需求文档:黄历日历小程序
前端·javascript·后端
知识分享小能手4 小时前
Vue3 学习教程,从入门到精通,使用 VSCode 开发 Vue3 的详细指南(3)
前端·javascript·vue.js·学习·前端框架·vue·vue3
姑苏洛言4 小时前
搭建一款结合传统黄历功能的日历小程序
前端·javascript·后端
hackchen5 小时前
Go与JS无缝协作:Goja引擎实战之错误处理最佳实践
开发语言·javascript·golang
你的人类朋友5 小时前
🤔什么时候用BFF架构?
前端·javascript·后端
知识分享小能手6 小时前
Bootstrap 5学习教程,从入门到精通,Bootstrap 5 表单验证语法知识点及案例代码(34)
前端·javascript·学习·typescript·bootstrap·html·css3
我命由我123457 小时前
前端开发问题:SyntaxError: “undefined“ is not valid JSON
开发语言·前端·javascript·vue.js·json·ecmascript·js
Jokerator7 小时前
深入解析JavaScript获取元素宽度的多种方式
javascript·css