WebRTC支持P2P传输,但需满足三条件:正确addTrack()、配置有效iceServers(含STUN/TURN)、完整交换ICE候选;任一缺失均导致黑屏或iceConnectionState卡住。

Html2md
使用 Readability + Turndown 将 HTML 页面转换为干净的、Agent 友好的 Markdown。去除导航栏、广告、页脚、Cookie 横幅、社交按钮等干扰元素。支持 URL...
下载
WebRTC 本身完全支持点对点(P2P)媒体流传输,**问题不在于“不支持”,而在于你没走通 P2P 的必要条件链**。常见现象是:本地视频能播、信令通了、offer/answer 也交换成功,但远程视频黑屏、
ontrack 不触发、
iceConnectionState 卡在
"checking" 或直接变成
"failed" —— 这不是 WebRTC 不行,是连接根本没打穿。
为什么你的 RTCPeerConnection 没建立真正的 P2P?
WebRTC 的“点对点”只针对媒体流,但建立这条路径需要三件事同时成立:
- 双方都正确调用
addTrack()(或旧式 addStream(),已废弃),且流里有有效轨道(stream.getTracks().length > 0)
-
RTCPeerConnection 初始化时传入了可用的 iceServers,至少含一个 STUN;纯局域网可省略,但一旦跨 NAT 就必须有
- ICE 候选地址真正交换完成:
onicecandidate 触发后,你得把 e.candidate 发给对方,并确保对方调用了 addIceCandidate();漏掉任意一个候选,尤其 host candidate 之后的 srflx 或 relay,就可能卡住
典型错误是:前端只发了 offer,但没监听
onicecandidate,或后端信令服务丢弃了 type="candidate" 消息;又或者对方收到 candidate 后没做
JSON.parse() 就直接传给
addIceCandidate(),导致抛错静默失败。
ontrack 死活不触发?先查这三处
ontrack 是远端流到达的唯一可靠信号,不触发基本等于媒体通道没通:
- 确认远端是否真的调用了
addTrack():很多 demo 只处理本地流,忘了把 stream 加进 peerConnection
- 检查
pc.remoteDescription 是否已设置:必须在收到 offer/answer 后调用 setRemoteDescription(),否则即使 candidate 全部到位,也不会触发 track
- 浏览器策略限制:非 HTTPS 或非 localhost 环境下,
getUserMedia() 会拒绝,导致本地无流 → 远端无 track 可发;Chrome 123+ 对 http://127.0.0.1 也开始收紧,建议统一用 https://localhost
临时验证方法:在远端
setRemoteDescription() 成功后,手动执行
pc.getReceivers().forEach(r => console.log(r.track?.kind)),如果有 track 输出,说明接收器已就位,问题出在 video 元素赋值或 mute 属性上。
STUN/TURN 配置写错,P2P 就是空谈
iceServers 不是摆设,写错格式或地址不可达,ICE 就永远卡在 gathering:
- STUN 地址必须是
urls 字段,不是 url(常见拼写错误):{ urls: 'stun:stun.l.google.com:19302' } ✅,{ url: '...' } ❌
- 多个服务器要写成数组:
iceServers: [{ urls: 'stun:...' }, { urls: 'turn:...', username: 'u', credential: 'p' }]</li>
<li>TURN 必须带认证信息,且 <code>credential 类型是 string;若用短期凭证,需动态更新,不能硬编码过期值
- 开发阶段可用公共 STUN,但上线必须换自有或商用服务;
stun:stun1.l.google.com:19302 等地址近年响应不稳定,建议备用至少两个
用 Chrome 访问
chrome://webrtc-internals,筛选你的
RTCPeerConnection,点开 “Stats” 标签页,看
iceCandidatePair 的
state 是否为
succeeded,
nominated 是否为 true —— 这才是 P2P 真正跑起来的铁证。
真正卡住的地方,往往不是协议多复杂,而是 candidate 漏发了一条、
setRemoteDescription() 忘加
await 导致 race condition、或者信令消息被 JSON 序列化时丢了
candidate 字段。这些细节不打印日志根本看不见。