啥是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域名才能使用
- 第一次调用会有询问过程,如果允许下次就不会提示,如果禁止了下次就直接失败了
这里简单介绍navigator.mediaDevices.getUserMedia
的用法,更详细的用法与示例可以看mediaDevices开启你本地视频之旅行
如何做到音视频通话
视频通话的原理简单来说就双方各采集本地视频流,然后发送给对方。这个时候就需要RTCPeerConnection
这个对象了,如果你没有听过这个API很正常,正经的前端谁用这个啊。当我打开MDN的示例运行的时候,满脸黑人问号,并且没有跑起来(注2)。
如何进行p2p连接
然后又打开了WebRTC的MDN介绍,第一篇是架构介绍,看到一堆英文缩写,真是劝退:
- ICE
- STUN
- NAT
- TURN
- SDP
怀着忐忑的心,打开了第二篇,想着万一能看懂呢,题目是信令与视频通话,还好基本都是人话,根据小学二年级传授的知识还是可以看懂的,只是新概念也挺多,需要耐心多看几遍。
现在使用毕生所学将知识归纳一下:
-
WebRTC的延迟之所以能做到如此之低,是因为使用p2p直连,媒体流直接对发,不经过服务器,但是用户一般都是藏在一层层的路由器后面的,不可能像连服务器一样直连,因此需要知道各自的公网地址,然后发送给对方,这样对方才能找到你,这块的技术是WebRTC的ICE模块完成的,ICE又用到了STUN、NAT、TURN等技术,因此只要知道ICE能收集本地公网地址,并拿到对方的公网地址后直连的技术;
-
如何能拿到对方的公网地址呢,这样时候就需要服务器转发一下消息,一般是使用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.getUserMeida
API,没啥好说的,获取到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];
}
};
总结
- PeerConnection的核心方法基本就是上述使用的方法,非常精简,使用上比较简单,但其实是WebRTC底层做了很多,是有人在替你负重前行;
- 概念比较多,需要多看,每一个概念拿出来就是一大块知识,刚开始只要了解每个模块是做什么的即可,主要是了解流程;
注释
- 实时:一般我们在网页上看直播的时候最少也会2-3s的延迟,尤其是看世界杯的时候,隔壁的大爷都在欢呼了,你还不知道在干什么,因为直播的链路很长,耗时在所难免。但是聊天场景就需要更低的延迟,使用WebRTC就可以达到200ms(甚至更低)的延迟,所以称的上是实时通讯;
- 代码没有实现通话的原因是需要服务器帮忙交换一种信息,而示例代码中是没有的,想体验的话可以看官方示例,这个是在1个页面中实现了,省去了服务器,如果想拿nodejs来中转的话可以看下50行代码完成视频通话 (WebRTC + WebSocket)这个项目,非常基础;
- 编解码:原始采集的视频非常非常大,无法在网络间传输,需要压缩这个过程称之为编码,反之称为解码,这里有一些图像知识的介绍digital_video_introduction;
END
欢迎大家添加我wx:18618261774 一起来学习