前言
WebRTC 是网络实时通信(Web Real-Time Communication)的缩写,通过它 Web 开发者能够轻易快捷地开发出丰富的实时多媒体应用,而无需下载安装任何插件。
WebRTC 允许网络应用建立浏览器之间的点对点连接,实现视频流、音频流或者其他任意数据的传输。
WebRTC常用的API详解
WebRTC 主要提供了三个核心的 API:
- getUserMedia:可以获取本地的媒体流,一个流包含几个轨道,比如视频和音频轨道。
- getDisplayMedia:获取电脑屏幕的视频流,不过暂时无法获取音频媒体流,如果需要音频流,手动添加到轨道内,使之同步播放。
- RTCPeerConnection:用于建立 P2P 连接以及传输多媒体数据。
- RTCDataChannel:建立一个双向通信的数据通道,可以传递多种数据类型。
通过这几个 API,我们可以获取本地的音视频流,然后与其他浏览器建立点对点连接并将音视频流发送给对方,还可以建立一个建立一个双向的数据通道,发送文本、文件等实时数据。
constraints 约束
const promise = navigator.mediaDevices.getUserMedia(constraints);
constraints 是一个对象,设置video,audio媒体轨道的配置。
video,audio支持boolean或者object类型。
简单使用: { audio: true, video: true },video还可设置宽高比,视频分辨率,帧频率等,audio可设置回声消除等功能。详细使用请查看 MediaTrackConstraints
RTCPeerConnection 连接
const pc = new RTCPeerConnection(): 创建通道
pc.addStream: 添加本地媒体流到通道内
pc.onaddstream:添加远端媒体流
pc.setLocalDescription:设置本地sdp描述信息
pc.setRemoteDescription:设置远端sdp描述信息
pc.onicecandidate:接收ICE candidate信息,用于NAT 穿透,本质就是地址信息
new RTCIceCandidate:创建ICE candidate信息对象
pc.addIceCandidate:将ICE candiate信息放入通道
getDisplayMedia
这个API用来获取电脑屏幕的视频媒体流,常用于视频共享或者屏幕录制等功能,具体使用同getUserMedia,只是该API只能获取视频流,无法获取音频流。如果需要音频流,需要手动调用getUserMedia获取音频流,然后加入到屏幕视频流的媒体轨道内。
navigator.mediaDevices.getDisplayMedia({ video: true })
.then((s) => {
// 增加音频流
navigator.mediaDevices.getMedia({audio: true})
.then((audioStream) => {
[audioTrack] = audioStream.getAudioTracks();
[videoTrack] = s.getVideoTracks();
stream = new MediaStream([videoTrack, audioTrack]); // 音视频流
})
.catch((err) => { console.log(err) });
})
.catch((err) => { console.log(err) })
流程(类似http握手流程,本质就是交换信息流程)
这里举例小徐和小罗的通话流程:
-
小徐首先创建PeerConnection对象,然后打开本地音视频设备,将音视频数据封装成MediaStream添加到PeerConnection中。
-
然后小徐调用PeerConnection的CreateOffer方法创建一个用于offer的SDP对象,通过PeerConnection的SetLocalDescription方法将该SDP对象保存起来,并通过信令服务器发送给小罗。
-
小罗接收到小徐发送过的offer SDP对象,通过PeerConnection的SetRemoteDescription方法将其保存起来,并调用PeerConnection的CreateAnswer方法创建一个应答的SDP对象,通过PeerConnection的SetLocalDescription的方法保存该应答SDP对象并将它通过信令服务器发送给小徐。
-
小徐接收到小罗发送过来的应答SDP对象,将其通过PeerConnection的SetRemoteDescription方法保存起来。
-
在SDP信息的offer/answer流程中,ClientA和ClientB已经根据SDP信息创建好相应的音频Channel和视频Channel并开启Candidate数据的收集,Candidate数据可以简单地理解成客户端的IP地址信息(本地IP地址、公网IP地址、Relay服务端分配的地址)。
-
当小徐收集到Candidate信息后,PeerConnection会通过OnIceCandidate接口给自己发送通知,小徐将收到的Candidate信息通过信令服务器发送给小徐,小罗通过PeerConnection的AddIceCandidate方法保存起来。同样的操作小罗对小徐再来一次。
-
小徐和小罗就已经建立了音视频传输的P2P通道,小罗接收到小徐传送过来的音视频流,会通过PeerConnection的OnAddStream回调接口返回一个标识小徐的音视频流的MediaStream对象,在小罗端渲染出来即可。同样操作也适应小罗到小徐的音视频流的传输。
代码实践(vue)
这里因为方便测试,省略了信令服务器,直接模拟本地端对端通道通信,通信原理流程还是一样的,属于精简版。
<template>
<div class="container">
<video id="localWebcam" class="local-video" autoplay="autoplay" />
<video id="webcam" class="video" autoplay="autoplay" />
</div>
</template>
<script>
export default {
name: 'WebRtcDemo',
mounted() {
this.init()
},
methods: {
init() {
// 获取本地流
navigator.mediaDevices
.getUserMedia({ video: { width: 720, height: 1200 }, audio: true })
.then(stream => {
document.querySelector('#localWebcam').srcObject = stream
this.startPeerConnection(stream)
})
.catch(err => {
console.log('The following error occurred: ' + err.name)
})
},
/**
* @author xuchen
* @date 2020-11-07 11:28:26
* @desc 建立通信
*/
startPeerConnection(stream) {
// stun服务器
const config = {
iceServers: [
{ url: 'stun:stun.services.mozilla.com' },
{ url: 'stun:stunserver.org' },
{ url: 'stun:stun.l.google.com:19302' }
]
}
// 创建通道
const selfConnection = new RTCPeerConnection(config)
const otherConnection = new RTCPeerConnection(config)
// 塞入本地流
selfConnection.addStream(stream)
// Candidate 信息
selfConnection.onicecandidate = e => {
if (e.candidate) {
otherConnection.addIceCandidate(new RTCIceCandidate(e.candidate))
}
}
otherConnection.onicecandidate = e => {
if (e.candidate) {
selfConnection.addIceCandidate(new RTCIceCandidate(e.candidate))
}
}
// 接收远端的媒体流
otherConnection.onaddstream = e => {
document.querySelector('#webcam').srcObject = e.stream
}
// 创建offer
selfConnection.createOffer().then(offer => {
selfConnection.setLocalDescription(offer) // 保存本地的sdp信息
otherConnection.setRemoteDescription(offer) // 设置远端的sdp信息
// 创建应答
otherConnection.createAnswer().then(answer => {
otherConnection.setLocalDescription(answer) // 保存本地的sdp信息
selfConnection.setRemoteDescription(answer) // 设置远端的sdp信息
})
})
}
}
}
</script>
<style lang="scss" scoped>
.container {
position: fixed;
left: 0;
right: 0;
top: 0;
bottom: 0;
margin: auto;
width: 300px;
height: 500px;
background-color: #1c4054;
.local-video,
.video {
width: 100%;
height: 250px;
}
}
</style>
浏览器支持
通过caniuse可以看出基本所有现代浏览器都支持 WebRTC:
不同浏览器使用对应的api时需要加上不同的前缀,可以使用webrtc-adapter来消除这些写法差异。