title: WebRTC之实现1v1音视频通话
date: 2019-12-05 21:06:38
tags:
- WebRTC
- 音视频
categories:
- WebRTC
原文: https://ahoj.cc/
打通一下 1v1 音视频通话的流程。
基本知识
端到端连接基本流程:

客户端信令消息设计:
- join 加入房间
- leave 离开房间
- message 端到端消息
- offer 消息
- answer 消息
- candidate 消息
服务端信令消息设计:
- joined 已成功加入房间
- otherjoin 其他用户加入此房间
- full 要加入的房间已满
- leave 已成功离开房间
- bye 对方离开房间
消息处理流程:

客户端状态机:
客户端加入相关流程:

客户端离开流程图:

代码逻辑
连接
- 网页加载完毕,用户初始状态为
init
。

-
点击连接按钮,进入 start() 函数,获取权限并开启本地音视频,然后进入 conn() 函数和信令服务器建立连接,并向信令服务器发送一个 join 信号,当收到服务器返回的 joined 后,客户端的状态变为 joined 。然后执行 createPeerConnection() 函数,创建 pc 并绑定媒体流,等待第二个人加入。注意:addTrack 将采集的音视频轨道添加并发送给远端。
-
当有第二个人点击连接按钮,执行完上述步骤后,第一个人的客户端会收到信令服务器发来的一个 otherjoin 信号,这时会触发 call() 函数。在 call() 中执行 createOffer() ,在创建 offer 成功后 setLocalDescription,并将创建好的 desc 信息发给信令 message 给信令服务器。
setLocalDescription 调用后底层会悄悄地向 sturn/turn 服务器发送一个 bind request,这个时候就开始收集所有能和对方连接的候选者了。
当服务器收到 message 后,会将其原封不动的转发给房间内除发送者外的其他成员:
socket.to(room).emit('message', room, data); // 给房间出自己外所有人回消息
-
然后第二个人会收到信令服务器发来的 message 信息,信息中的
data.type==='offer'
。下来第二个人的客户端会执行 setRemoteDescription,然后 createAnswer,并 setLocalDescription,最后讲创建的 answer 以 message 信息。同样,信令服务器收到这个 answer 后还是原封不动的转发给了第一个连接的人。
-
第一个人在收到 answer 后 setRemoteDescription,这样两个人的 SDP 就交换好了。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-P2JB3Ezk-1575561809056)(https://i.loli.net/2019/12/05/gnqZImlSVCbB4HY.png)]
-
已经根据SDP信息创建好本地的相关 Channel 后会开启Candidate数据的收集,接收由 TURN 服务器收集好的 candidate 信息。
-
当 candidate 到达第一个连接的人那后会触发 onicecandidate,第一个人的客户端会将这个 candidate 发送给信令服务器,信令服务器发给第二个进来的人,第二个进来的人会 addIceCandidate,然后会触发自己的 onicecandidate,再给信令服务器,信令服务器再给第一个进来的人。
-
这样两个人就建立了音视频传输的P2P通道,接收对方传送过来的 MediaStream 对象并渲染出来。
-
onTrack 监听音视频数据的到达,到达后执行 getRemoteStream。
-
当其他人再加入时,信令服务器发现此房间已满,会发送一个 full 信号,提示当前房间已满,并关闭 pc 和本地音视频流的 Track。
离开
离开的逻辑就简单一些了。
先向服务器发送 leave 信令,收到 leaved 后变更状态然后关闭pc,关闭媒体流。
注意
网络连接要在音视频流数据获取之后,否则有可能绑定音视频流失败。
当一端退出房间后,另一端的 PeerConnection 要关闭重建,否则与新用户互通的时候媒体协商失败。
异步事件处理。
完整代码
Client.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Room</title>
<link href="main.css" rel="stylesheet">
</head>
<body>
<div>
<div id="preview">
<table align="center">
<tr>
<td>
<h2>Local:</h2>
<video id="localvideo" autoplay playsinline muted></video>
<label>Offer SDP:
<textarea id="textarea_offer"></textarea>
</label>
</td>
<td>
<button id="connserver">连接</button><br>
<button id="leave" disabled>离开</button>
</td>
<td>
<h2>Remote:</h2>
<video id="remotevideo" autoplay playsinline></video>
<label>Answer SDP:
<textarea id="textarea_answer"></textarea>
</label>
</td>
</tr>
</table>
</div>
</div>
<script src="https://cdn.bootcss.com/socket.io/2.3.0/socket.io.js"></script>
<script src="https://cdn.bootcss.com/webrtc-adapter/zv4.1.1/adapter.min.js"></script>
<script src="room.js"></script>
</body>