导言

一切的一切都开端于那个夜黑风高的夜晚……
那晚,我急需求一款录屏软件,找来找去,可是:
付费去广告、付费去水印、付费高清、付费下载😅……
等等,忽然我灵光乍现,我还有一条路:
自己做!
所以原生Js基于WebRTC完成的Web端实时录屏功用就出来了!文末附开源地址😎
先来试用一下成品吧→SHTRec←移动端暂不支撑哦
demo.gif

技术选型

  • WebRTC
  • 原生Js

中心代码完成

html

整个的录屏功用区域都放在一个类名为main的div里边
类名为btn的div放上开端录屏button,并给button绑定start()函数,类名为rec

<div class="main">
    <div class="btn">
        <button onclick="start()" class="rec">点击录屏</button>
    </div>
</div>

Js

js这儿大体分为四部分:开端、完毕、播映、下载

function start() {
     ......
     /* 开端 */
}
function stop() {
     ......
     /* 完毕 */
}
function replay() {
     ......
     /* 播映 */
}
function download() {
     ......
     /* 下载 */
}

为了进步代码可读性,先声明一些需求用到的全局变量

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办法来获取屏幕共享(包括音频和视频)的流

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异步操作办法

.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></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></span>
                        <span></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代码可所以一个动画来表明正在录屏,如下所示:
image.png
这个动画对应的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的过错情况来做一些处理

.catch(function (error) {
    console.log(error);
});

.catch(function (error) {...})是Promise的catch办法,它将在Promise产生过错时履行。error 是产生的过错目标。

然后咱们用console.log(error);在操控台中打印出过错信息,让咱们能够在操控台中看到过错的详细信息,然后更容易地调试和解决问题。

stop()

开端录制当然也要中止录制了,通过创立一个stop函数来操控录制

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内容的办法来提示用户录制完毕,如下图所示:
image.png

replay()

当录制完毕后咱们需求将录制好的视频供用户播映。

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()

运用录屏功用的底子目的还是为了下载到本地,所以咱们再创立一个下载函数

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⭐