webRTC初体验:实现屏幕和摄像头的录制、回放、下载

4,698 阅读4分钟

前言

相信大家都使用过web版的视频会议,视频面试,在线视频教育等功能。而这些功能的实现都离不开一项技术,那就是webRTC,webRTC是一项实时通讯技术。

MDN对WebRTC的定义:

WebRTC (Web Real-Time Communications) 是一项实时通讯技术,它允许网络应用或者站点,在不借助中间媒介的情况下,建立浏览器之间点对点(Peer-to-Peer)的连接,实现视频流和(或)音频流或者其他任意数据的传输。WebRTC包含的这些标准使用户在无需安装任何插件或者第三方的软件的情况下,创建点对点(Peer-to-Peer)的数据分享和电话会议成为可能。

其实这项技术早在很多年前就已经出现,只是那时候的应用场景并不丰富,使用的比较少,并且还不够成熟。随着时代与技术的发展,这项技术将变得越来越稳定与成熟,使用面也将越来越广泛,特别是疫情时代。

今天我们来快速入下门,做个简单的demo,直观感受下webRTC到底能做什么。我们将会实现电脑屏幕的录制、摄像头的录制,以及回放录制的内容,并且支持下载。

2.gif

实现思路

浏览器提供了navigator.mediaDevices.getDisplayMedia 和 navigator.mediaDevices.getUserMedia两个API。

  • navigator.mediaDevices.getDisplayMedia是获取屏幕的流
  • navigator.mediaDevices.getUserMedia 是获取和用户相关的流,也就是麦克风、摄像头这些流。

获取到流之后设置到video容器的 srcObject 属性上就可以实现播放。

如果想要录制视频,需要用 MediaRecorder 的 api,它可以监听流中的数据,我们可以把获取到的数据保存到数组中。然后回放的时候设置到另一个视频容器的 srcObject 属性就可以了。

下载也是基于 MediaRecorder 录制的数据,转成 blob 后通过 a 标签触发下载。

代码实现

实时播放

首先在页面放两个video标签,一个用于实时的观看录制的视频,另一个用于回放。然后再放几个操作按钮。

  <div>
    <video autoplay id="player"></video>
    <video id="recordPlayer"></video>
</div>
<div>
    <button id="startScreenBtn">开启录屏</button>
    <button id="startCameraBtn">开启摄像头</button>
    <button id="replyBtn">回放</button>
    <button id="downloadBtn">下载</button>
</div>

接下来,给开启录屏和开启摄像头按钮添加点击事件,两者都是为了开启录制,只不过方式不同,我们可以根据传入不同的形参去实现不同的操作。

let blobs = [], mediaRecorder;

startScreenBtn.addEventListener('click', () => {
    record('screen');
});

startCameraBtn.addEventListener('click', () => {
    record('camera');
});

async function record(recordType) {
    // getDisplayMedia是获取屏幕数据,getUserMedia是获取麦克风、摄像头数据
    const getMediaMethod = recordType === 'screen' ? 'getDisplayMedia' : 'getUserMedia';
    
    // 获取相关数据为异步任务
    // 可以指定下视频宽高和帧率等参数
    const stream = await navigator.mediaDevices[getMediaMethod]({
        video: {
            width: 500,
            height: 300,
            frameRate: 20
        }
    });
    
    // 把返回的流设置到video容器的srcObject属性上,就可以实时看到对应的音视频
    player.srcObject = stream;
    
    // 录制需要用到MediaRecorder
    // 我们需要把上面获取到的流stream传入,并且配置类型
    mediaRecorder = new MediaRecorder(stream, {
        mimeType: 'video/webm'
    });
    
    // 监听dataavailable事件,在其中把获取到的数据保存到blobs数组中
    mediaRecorder.ondataavailable = (e) => {
        blobs.push(e.data);
    };
    
    // 然后调用start方法,开启录制。start参数是分割的大小,传入100代表每100ms保存一次数据
    mediaRecorder.start(100);
}

回放

我们将拿到的blobs数组通过Blob转一下,再经过URL.createObjectURL的处理,这样就能作为video的url来被播放。

replyBtn.addEventListener('click', () => {
    const blob = new Blob(blobs, {
        type: 'video/webm'
    });
    recordPlayer.src = URL.createObjectURL(blob);
    recordPlayer.play();
});

下载

下载的原理与回放一样,我们只需要生成一个隐藏的a标签,设置download属性就可以下载。然后触发click事件。

 downloadBtn.addEventListener('click', () => {
    const blob = new Blob(blobs, {
        type: 'video/webm'
    });
    const url = URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    a.download = 'record.webm';
    a.style.display = 'none';
    a.click();
});

总结

此处我们只演示了录制屏幕,录制摄像头的操作与录制屏幕完全一样。

涉及到的 api 有 3 个:

  • navigator.mediaDevices.getUserMedia: 获取麦克风、摄像头的流
  • navigator.mediaDevices.getDisplayMedia: 获取屏幕的流
  • MediaRecorder:监听流的变化,实现录制
  1. 我们分别用前两个api获取到了屏幕、麦克风、摄像头的流,然后用MediaRecorder做了录制,把数据保存到数组中,之后生成了Blob。
  2. video可以设置srcObject属性为一个流,这样能直接播放,如果设置Blob的话需要用URL.createObjectURL处理下。
  3. 下载的实现是通过a标签指向Blob对象的object url,通过download属性指定下载行为。然后手动触发点击事件来下载。

最后,再贴一份完整的代码,谢谢大家的观看。

<html>

<head>
    <title>webRTC初体验</title>
</head>

<body>
    <div>
        <video autoplay id="player"></video>
        <video id="recordPlayer"></video>
    </div>
    <div>
        <button id="startScreenBtn">开启录屏</button>
        <button id="startCameraBtn">开启摄像头</button>
        <button id="replyBtn">回放</button>
        <button id="downloadBtn">下载</button>
    </div>

    <script>
        let blobs = [],
            mediaRecorder;

        async function record(recordType) {
            const getMediaMethod = recordType === 'screen' ? 'getDisplayMedia' : 'getUserMedia';
            const stream = await navigator.mediaDevices[getMediaMethod]({
                video: {
                    width: 500,
                    height: 300,
                    frameRate: 20
                }
            });
            player.srcObject = stream;

            mediaRecorder = new MediaRecorder(stream, {
                mimeType: 'video/webm'
            });
            mediaRecorder.ondataavailable = (e) => {
                blobs.push(e.data);
            };
            mediaRecorder.start(100);
        }

        startScreenBtn.addEventListener('click', () => {
            record('screen');
        });

        startCameraBtn.addEventListener('click', () => {
            record('camera');
        });

        replyBtn.addEventListener('click', () => {
            const blob = new Blob(blobs, {
                type: 'video/webm'
            });
            recordPlayer.src = URL.createObjectURL(blob);
            recordPlayer.play();
        });

        downloadBtn.addEventListener('click', () => {
            const blob = new Blob(blobs, {
                type: 'video/webm'
            });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = 'record.webm';
            a.style.display = 'none';
            a.click();
        });
    </script>
</body>

</html>