10、RTCPeerConnection:创建、配置与状态监听
好,咱们进入 WebRTC 最核心的部分——RTCPeerConnection。
你可以把它理解成一条「音视频管道」。没有它,两个浏览器之间就没法传数据。我刚开始接触 WebRTC 时,以为只要拿到摄像头流就能直接发出去,结果折腾半天发现——嗯,少了这个关键对象。
10.1 创建 RTCPeerConnection 对象
创建方式很简单,一行代码就行:
const pc = new RTCPeerConnection();
但别高兴太早。实际项目中,你几乎不会这样裸写。为什么呢?因为缺了配置参数。
我个人习惯在创建时就把 STUN/TURN 服务器传进去。这样后续不用再补配置,逻辑更清晰。
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:your-turn-server.com:3478',
username: 'user',
credential: 'pass'
}
]
};
const pc = new RTCPeerConnection(config);
你想想看,如果只传空配置,WebRTC 就只能走局域网。一旦遇到 NAT 穿透,立马歇菜。
10.2 配置 STUN/TURN 服务器
STUN 和 TURN 是干什么的?说白了就是帮两个浏览器「找到对方」。
- STUN:帮你拿到自己的公网 IP 和端口。适合大部分场景。
- TURN:当 P2P 连不上时,作为中继转发数据。兜底方案。
我在项目中遇到过一个问题:用户在内网环境,STUN 能拿到公网 IP,但就是连不上。后来发现是防火墙把 UDP 封了。这时候只能走 TURN。
配置时要注意:
const config = {
iceServers: [
{ urls: 'stun:stun.voiparound.com' },
{
urls: [
'turn:turn.voiparound.com:3478',
'turns:turn.voiparound.com:5349'
],
username: 'test',
credential: 'test123'
}
],
iceCandidatePoolSize: 10
};
这里有个细节:iceCandidatePoolSize 可以预生成候选者。我建议设成 10 左右,能加快连接速度。
10.3 连接状态监听
创建完对象,配置好服务器,接下来就是等连接建立。但你不能干等,得监听状态变化。
WebRTC 提供了几个关键事件:
| 事件名 | 触发时机 | 常用场景 |
|---|---|---|
iceconnectionstatechange |
ICE 连接状态变化 | 判断是否连通 |
connectionstatechange |
整体连接状态变化 | 显示连接质量 |
icecandidate |
发现新的候选者 | 收集候选者 |
icegatheringstatechange |
候选者收集状态变化 | 判断收集是否完成 |
我最常用的是 iceconnectionstatechange:
pc.oniceconnectionstatechange = () => {
console.log('ICE 状态:', pc.iceConnectionState);
switch (pc.iceConnectionState) {
case 'checking':
// 正在尝试连接
break;
case 'connected':
// 连接成功
break;
case 'disconnected':
// 短暂断开,可能还能恢复
break;
case 'failed':
// 彻底失败,需要重建
break;
case 'closed':
// 连接关闭
break;
}
};
嗯,这里要注意:disconnected 和 failed 的区别。前者是临时断开,WebRTC 会尝试重连;后者是彻底没戏了。
我曾经踩过一个坑:用户网络波动导致 disconnected,我直接关闭了连接。结果用户骂娘了。后来改成等 5 秒再判断,如果还是 disconnected 再关。
10.4 完整的创建与监听示例
把上面这些串起来,就是一个完整的初始化流程:
async function createPeerConnection() {
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:your-turn.com:3478',
username: 'dynamic_user',
credential: 'dynamic_pass'
}
],
iceCandidatePoolSize: 10
};
const pc = new RTCPeerConnection(config);
// 监听 ICE 候选者
pc.onicecandidate = (event) => {
if (event.candidate) {
// 发送给远端
sendCandidate(event.candidate);
}
};
// 监听连接状态
pc.oniceconnectionstatechange = () => {
updateUI(pc.iceConnectionState);
};
// 监听整体连接状态
pc.onconnectionstatechange = () => {
console.log('连接状态:', pc.connectionState);
};
return pc;
}
你想想看,这个流程其实就三步:创建对象、配置服务器、监听状态。但每一步都有坑。
10.5 避坑指南
最后分享几个我踩过的坑:
- STUN 服务器不可用:Google 的 STUN 偶尔会被墙。我建议备选 2-3 个 STUN 服务器。
- TURN 带宽超限:TURN 流量走服务器,带宽很贵。我曾经一个月烧掉 200 刀。后来加了流量限制。
- 状态监听遗漏:只监听
iceconnectionstatechange不够。还要监听connectionstatechange,后者更全面。 - 候选者收集超时:有些网络环境收集候选者很慢。我建议加个 10 秒超时,超时后强制开始连接。
10.6 核心逻辑流程图
下面这张图帮你理清整个流程:
这张图把整个流程串起来了。从创建对象开始,到配置服务器,再到监听状态,最后根据状态做不同处理。你写代码时,就按这个顺序来。
公众号:蓝海资料掘金营,微信deep3321