“它(WebRTC)容许网络应用或者站点,在不借助中间媒介的状况下,创建浏览器之间点对点(Peer-to-Peer)的链接,实现视频流和(或)音频流或者其余任意数据的传输”。
这是 MDN 上对 WebRTC 的描述,初次接触时没法理解 WebRTC 为何要和 WebSocket 搭配,明明说的很清楚 不借助中间媒介 ,那 WebSocket 充当的是什么角色?整个 WebRTC 通话创建的流程又是怎样的?git
先看一下最终效果:github
本示例主要使用了 WebRTC
和 WebSocket
:web
WebRTC
(Web Real-Time Communication)即网页即时通讯,是一个支持网页浏览器进行实时语音对话或视频对话的API。WebSocket
是一种在单个TCP链接上进行全双工通讯的协议。在 WebSocket 中,浏览器和服务器只须要完成一次握手,二者之间就直接能够建立持久性的链接,并进行双向数据传输。简单说一下流程,如浏览器 A 想和浏览器 B 进行音视频通话:浏览器
offer sdp
)信息;offer sdp
经过 ws 发送给 B;answer sdp
)信息;answer sdp
经过 ws 发送给 A;引用网上的有关WebRTC
创建的时序图,可能更加直观:安全
从上述流程,能够发现通讯双方在创建链接前须要交换信息,这也就是开头提到的 WebSocket
充当的角色:信令服务器,用于转发信息。而 WebRTC 不借助中间媒介 的意思是,在创建对等链接后,不须要借助第三方服务器中转,而是直接在两个实体(浏览器)间进行传输。服务器
获取视频标签,链接信令服务器,建立 RTCPeerConnection
对象。其中 RTCPeerConnection 的做用是在两个对等端之间创建链接,其构造函数支持传一个配置对象,包含ICE“打洞”(因为本示例在本机进行测试,故不须要)。网络
const localVideo = document.querySelector('#local-video'); const remoteVideo = document.querySelector('#remote-video'); const socket = new WebSocket('ws://localhost:8080'); const peer = new RTCPeerConnection(); socket.onmessage = () => { // todo } peer.ontrack = () => { // todo } peer.onicecandidate = () => { // todo }
获取本地摄像头/麦克风(须要容许使用权限),拿到本地媒体流(MediaStream)后,须要将其中全部媒体轨道(MediaStreamTrack)添加到轨道集,这些轨道将被发送到另外一对等方。并发
navigator.mediaDevices.getUserMedia({ video: true, audio: true }) .then(stream => { localVideo.srcObject = stream; stream.getTracks().forEach(track => { peer.addTrack(track, stream); }); });
建立发起方会话描述对象(createOffer),设置本地SDP(setLocalDescription),并经过信令服务器发送到对等端,以启动与远程对等端的新WebRTC链接。socket
peer.createOffer().then(offer => { peer.setLocalDescription(offer); socket.send(JSON.stringify(offer)); });
当调用 setLocalDescription 方法,PeerConnection 开始收集候选人(ice信息),并发送offer_ice到对等方。这边补充第一步中的peer.onicecandidate
和socket.onmessage
ide
对等方收到ice信息后,经过调用 addIceCandidate 将接收的候选者信息传递给浏览器的ICE代理。
peer.onicecandidate = e => { if (e.candidate) { socket.send(JSON.stringify({ type: 'offer_ice', iceCandidate: e.candidate })); } }; socket.onmessage = e => { const { type, sdp, iceCandidate } = JSON.parse(e.data); if (type === 'offer_ice') { peer.addIceCandidate(iceCandidate); } }
接收方收到了offer
信令后,开始获取摄像头/麦克风,与发起方操做一致。同时将收到offer SDP
指定为链接的远程对等方属性(setRemoteDescription),并建立应答SDP(createAnswer),发送到对等端。这边补充第一步中的socket.onmessage
。
socket.onmessage = e => { const { type, sdp, iceCandidate } = JSON.parse(e.data); if (type === 'offer') { navigator.mediaDevices.getUserMedia(); // 与发起方一致,省略 const offerSdp = new RTCSessionDescription({ type, sdp }); peer.setRemoteDescription(offerSdp).then(() => { peer.createAnswer(answer => { socket.send(JSON.stringify(answer)); peer.setLocalDescription(answer) }); }); } }
注意:当 setLocalDescription 方法调用后,开始收集候选人信息,并发送 answer_ice 到对等方。与发送方同理,不赘述。
经过不断收集ICE信息(onicecandidate),发起方和应答方最终将创建一条最优的链接方式,此时会触发 ontrack 回调,便可获取到对等方的媒体流。
peer.ontrack = e => { if (e && e.streams) { remoteVideo.srcObject = e.streams[0]; } };
整合发起方和应答方的代码,差很少50行,不算标题党!哈哈哈哈哈哈...
完整示例相关代码已上传 github.com/shushushv/webrtc-p2p ,⭐🦆~