从前端视角从0认识Web实时通讯

1,210 阅读6分钟

啥是WebRTC啊

Web实时通讯 WebRTC是 Web Real-Time Communication的缩写,它可以帮助我们完成实时(注1)音视频聊天、发送消息文件等功能的非常强大的工具。

WebRTC名字虽然Web占了一半,但实际上WebRTC是开源工具可以运行在服务端、Android、IOS上,并且可以做到功能更强大,因为其他端都可以改WebRTC的代码,而前端只能使用浏览器提供的标准RTC,但标准RTC也能覆盖到绝大多数的场景。

为什么要从前端视角来看,因为浏览器提供的WebRTC接口RTCPeerConnection比较简洁,省去了底层的接口,对前端来说比较友好。

唤起本地摄像头

在浏览器中可以使用navigator.mediaDevices这个接口暴露出来的方法来获取操作本地的摄像头,使用方法也非常简单

async function getLocalStream() {
    const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
    // <video autoplay></video>
    video.srcObject = stream;
}
  • 出于安全策略考虑,调用摄像头只能在localhost或者https域名才能使用
  • 第一次调用会有询问过程,如果允许下次就不会提示,如果禁止了下次就直接失败了

image.png

这里简单介绍navigator.mediaDevices.getUserMedia的用法,更详细的用法与示例可以看mediaDevices开启你本地视频之旅行

如何做到音视频通话

视频通话的原理简单来说就双方各采集本地视频流,然后发送给对方。这个时候就需要RTCPeerConnection这个对象了,如果你没有听过这个API很正常,正经的前端谁用这个啊。当我打开MDN的示例运行的时候,满脸黑人问号,并且没有跑起来(注2)。

如何进行p2p连接

然后又打开了WebRTC的MDN介绍,第一篇是架构介绍,看到一堆英文缩写,真是劝退:

  • ICE
  • STUN
  • NAT
  • TURN
  • SDP

image.png

怀着忐忑的心,打开了第二篇,想着万一能看懂呢,题目是信令与视频通话,还好基本都是人话,根据小学二年级传授的知识还是可以看懂的,只是新概念也挺多,需要耐心多看几遍。

现在使用毕生所学将知识归纳一下:

  1. WebRTC的延迟之所以能做到如此之低,是因为使用p2p直连,媒体流直接对发,不经过服务器,但是用户一般都是藏在一层层的路由器后面的,不可能像连服务器一样直连,因此需要知道各自的公网地址,然后发送给对方,这样对方才能找到你,这块的技术是WebRTC的ICE模块完成的,ICE又用到了STUN、NAT、TURN等技术,因此只要知道ICE能收集本地公网地址,并拿到对方的公网地址后直连的技术;

  2. 如何能拿到对方的公网地址呢,这样时候就需要服务器转发一下消息,一般是使用websocket实现的称之为信令服务器;

如何收发媒体流

通过上面的方式,已经建立起了p2p连接,就可以愉快的发送本地媒体流了,但是发送之前还需要双方确认一件事情,即本地的编解码能力(注3),这个过程就是双方发送一段文本,表示自己有什么能力,这段文本称之为SDP(Session Description Protocol),这个交换的过程也是需要依赖信令服务器;

协商完毕后就可以将本地的流添加到peerConnection这个对象上

stream.getTracks().forEach(track => peerConnection.add(track, stream));

以webrtc-p2p demo来梳理下流程

因为MDN上的代码跑不起来,因此在github上找了这个50行代码完成视频通话 (WebRTC + WebSocket)项目作为demo来讲解流程

使用node index.js就可以将项目跑起来,然后通话的发起方和接收方都是使用p2p.html这一个页面,使用query中的参数作为区分。

信令连接

信令就是帮忙双方交换一个信息

const socket = new WebSocket('ws://localhost:8080');
socket.onopen = () => {
        message.log('信令通道创建成功!');
        target === 'offer' && (button.style.display = 'block');
}
socket.onerror = () => message.error('信令通道创建失败!');
socket.onmessage = e => {}

实例化PeerConnection

因为众所周知的原因这个API是有兼容性,获取到正确对象后,进行实例化,其实是有options参数的,这里简单处理直接省略

const PeerConnection = window.RTCPeerConnection || window.mozRTCPeerConnection || window.webkitRTCPeerConnection;
!PeerConnection && message.error('浏览器不支持WebRTC!');
const peer = new PeerConnection();

p2p连接

onicecandidate事件就是收集本地可用的公网地址,收到后通过信令服务器发送给对方,对方操作也是一样

peer.onicecandidate = e => {
    if (e.candidate) {
        message.log('搜集并发送候选人');
        socket.send(JSON.stringify({
            type: `${target}_ice`,
            iceCandidate: e.candidate
        }));
    } else {
        message.log('候选人收集完成!');
    }
};

在收到对方的ice信息后,使用peer.addIceCandidate(iceCandidate);即可完成配对

采集视频

使用navigator.mediaDevices.getUserMeidaAPI,没啥好说的,获取到stream后,将音视频轨添加到peer实例中。因为音视频轨道是分别传输的,所以这里需要以track的维度添加到peer中

stream.getTracks().forEach(track => {
    peer.addTrack(track, stream);
});

媒体能力协商

添加音视频后,就可以获取SDP了,主要是使用createOffer这个API

message.log('创建本地SDP');
const offer = await peer.createOffer();
await peer.setLocalDescription(offer);

message.log(`传输发起方本地SDP`);
socket.send(JSON.stringify(offer));

获取后就将SDP传输给对方,对方收到后,会使用createAnswer来产生一个回复消息,本地再通过信令收到后,使用setRemoteDescription即可完成协商。

对方想发布也是同样的流程,完成后即可开始发送音视频流。

接收对方的音视频流

最终接收到的也是stream对象,交由video标签播放即可

peer.ontrack = e => {
    if (e && e.streams) {
        message.log('收到对方音频/视频流数据...');
        remoteVideo.srcObject = e.streams[0];
    }
};

总结

  1. PeerConnection的核心方法基本就是上述使用的方法,非常精简,使用上比较简单,但其实是WebRTC底层做了很多,是有人在替你负重前行;
  2. 概念比较多,需要多看,每一个概念拿出来就是一大块知识,刚开始只要了解每个模块是做什么的即可,主要是了解流程;

注释

  1. 实时:一般我们在网页上看直播的时候最少也会2-3s的延迟,尤其是看世界杯的时候,隔壁的大爷都在欢呼了,你还不知道在干什么,因为直播的链路很长,耗时在所难免。但是聊天场景就需要更低的延迟,使用WebRTC就可以达到200ms(甚至更低)的延迟,所以称的上是实时通讯;
  2. 代码没有实现通话的原因是需要服务器帮忙交换一种信息,而示例代码中是没有的,想体验的话可以看官方示例,这个是在1个页面中实现了,省去了服务器,如果想拿nodejs来中转的话可以看下50行代码完成视频通话 (WebRTC + WebSocket)这个项目,非常基础;
  3. 编解码:原始采集的视频非常非常大,无法在网络间传输,需要压缩这个过程称之为编码,反之称为解码,这里有一些图像知识的介绍digital_video_introduction

END

欢迎大家添加我wx:18618261774 一起来学习