引言
一切的一切都开始于那个夜黑风高的夜晚......
那晚,我急需要一款录屏软件,找来找去,可是:
付费去广告、付费去水印、付费高清、付费下载😅......
等等,突然我灵光乍现,我还有一条路: 自己做!
于是原生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
元素具有autoplay
和controls
属性,意味着视频将在加载后自动播放,并且用户可以控制视频的播放。
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开源地址,如果这篇文章对你有所启发,欢迎点赞收藏+转发😁