addIceCandidate必须在setLocalDescription和setRemoteDescription均成功执行、signalingState进入"stable"或协商中状态后调用,否则抛InvalidStateError;参数须为RTCIceCandidate实例而非原始字符串。

addIceCandidate 什么时候该调用?
addIceCandidate 不是“收集完就立刻加”,而是必须等本地 RTCPeerConnection 已完成 offer/answer 协商(即 setLocalDescription 和 setRemoteDescription 都已成功执行)之后,才能安全调用。提前调用会触发 InvalidStateError 错误,常见报错:DOMException: Failed to execute 'addIceCandidate' on 'RTCPeerConnection': Can't add ICE candidate in signaling state 'stable'。
典型时序是:
- 发起方调用
createOffer()→setLocalDescription()→ 发送 offer 给对方 - 接收方收到 offer →
setRemoteDescription(offer)→ 调用createAnswer()→setLocalDescription(answer)→ 发送 answer 回来 - 双方都完成
setLocalDescription和setRemoteDescription后,才进入signalingState === "stable"或"have-local-offer"/"have-remote-offer"的可添加状态
addIceCandidate 接收的 candidate 格式必须合法
传给 addIceCandidate 的参数不能是原始字符串(如 "candidate:0 1 UDP 2130706431 192.168.1.100 52334 typ host"),而必须是 RTCIceCandidate 实例,否则会静默失败或抛 TypeError。
正确做法:
立即学习“前端免费学习笔记(深入)”;
- 对方通过
onicecandidate事件拿到event.candidate,直接序列化为 JSON 发送(它本身已是合法对象) - 自己收到后,用
new RTCIceCandidate(candidateJSON)构造再传入 —— 注意不是new RTCIceCandidate({candidate: '...'}),那是旧版写法,已废弃 - 错误示例:
pc.addIceCandidate({candidate: 'candidate:...'})→ 会失败;正确示例:pc.addIceCandidate(new RTCIceCandidate({candidate: 'candidate:...'}))
addIceCandidate 失败但没报错?检查 candidate.type 和网络可达性
即使 addIceCandidate 调用不报错,也不代表该候选被真正启用。常见静默失效原因:
-
candidate.type === "host"且双方不在同一局域网:这种候选永远无法连通,ICE 检查阶段会自动跳过 -
candidate.type === "srflx"但 STUN 服务器不可达:比如配置了stun:stun.example.com,但域名解析失败或端口被防火墙拦截,此时虽能生成候选,但后续连通性检查全超时 -
candidate.type === "relay"但 TURN 凭证过期或带宽耗尽:TURN 分配的中继地址可能返回 401 或 403,但前端不会暴露,只表现为 candidate-pair 状态长期卡在checking
验证方式:监听 pc.oniceconnectionstatechange,并用 pc.getStats() 查看 candidate-pair 类型条目的 state 和 nominated 字段。只有 state === "succeeded" 且 nominated === true 的才是最终生效路径。
为什么 addIceCandidate 后 still no media?重点看 iceTransportPolicy 和防火墙
即使所有候选都成功添加、连接状态变成 connected,音视频仍无声无画,大概率是传输策略或底层协议被阻断:
-
iceTransportPolicy: "relay"强制只用中继,但没配 TURN → 所有 candidate 都是relay类型却无法建立分配,最终无可用路径 - 企业防火墙禁用了 UDP,但你没开启 TCP 候选:TURN 配置需显式加
urls: "turn:xxx?transport=tcp",否则默认只走 UDP - IPv6 候选被优先选中但链路不通:某些 NAT 设备对 IPv6 支持不完整,建议在
RTCPeerConnection构造时加bundlePolicy: "max-bundle"+rtcpMuxPolicy: "require"减少候选数量,加速收敛
最易忽略的一点:addIceCandidate 是单向操作,它只告诉本端“对方可能从这个地址连过来”,但不保证本端的对应候选也存在或可达。真正的连通性取决于两端各自收集的候选能否形成至少一对可通信的组合 —— 这就是为什么必须两端都正确配置 iceServers,且 STUN/TURN 服务本身稳定可用。



















