1.3 一通通话的全景与状态机 本节摘要:别以为「视频通话」是一个动作——它是采集、协商、打洞、加密四段流程的接力,任何一段掉链子都表现为同一句用户抱怨「连不上」。本节把一次 1v1 通话拆成完整的建立流水线,并讲清 connectionState 与 iceConnectionState 两套状态机如何联动。它是第 2 到第 5 章的预告片,也是你日后看日志定位故障阶段的总索引。 目标清单 阅读完本节,你应当能够: 按顺序说出一次通话建立的完整步骤与每步的产物; 解释 offer 与 answer 各自回答了什么问题、为什么必须一先一后; 区分 candidate 交换与 SDP 交换这两条不同的时间线; 根据 connectionState 的取值判断通话建立进行到哪个阶段;
本节摘要:别以为「视频通话」是一个动作——它是采集、协商、打洞、加密四段流程的接力,任何一段掉链子都表现为同一句用户抱怨「连不上」。本节把一次 1v1 通话拆成完整的建立流水线,并讲清 connectionState 与 iceConnectionState 两套状态机如何联动。它是第 2 到第 5 章的预告片,也是你日后看日志定位故障阶段的总索引。
阅读完本节,你应当能够:
用户眼里,「发起视频通话」是一个按钮;工程师眼里,按钮按下之后是一场四棒接力。少了任何一棒,用户看到的现象都一样——黑屏。这就是为什么入门项目最常出现的排查困境是「没有报错,但画面不出来」:你盯着的那段代码没问题,问题在接力赛的前几棒。
把接力棒列出来。第一棒,采集:本端打开摄像头麦克风,拿到轨道。第二棒,协商:两端通过信令交换 SDP,谈好用什么编码、什么参数、谁发谁收。第三棒,接线:两端各自收集自己的网络地址(候选),互相试探哪条路能通。第四棒,开讲:协商出的加密密钥生效,DTLS 握手完成后,媒体数据开始流动。第二棒属于第 3 章,第三棒属于第 4 章,第四棒属于第 5 章——本节只需要你记住接力顺序和每棒的交接物。
完整的建立流程画成时序是这样的:
两个容易误解的点要先掰正。其一,SDP 交换和候选交换是两条时间线。SDP 是「谈判纪要」,候选地址是「名片」,名片不需要等谈判结束才递——现代实现用 Trickle ICE,边收集边发,谁先到就用谁排队试探,不必等对端把所有地址收齐。其二,协商没有中心裁判。offer 与 answer 是对等的两个提议与确认,谁先发起谁掌握初始议程,但每一项参数都要对端点头才算数。
流程在代码里映照为两套状态机。RTCPeerConnection.connectionState 描述整条连接的宏观阶段:
而 iceConnectionState 是其中「打洞」那一棒自己的仪表盘,取值包括 new、checking、connected、completed、failed、disconnected。两套状态机的联动关系值得单独强调:ice 先走到 connected,connectionState 才可能变成 connected;ice 变 failed,connectionState 随之 failed。监听错误时盯 connectionState,定位细节时看 iceConnectionState——一个给你结论,一个给你线索。
用一段骨架代码把事件接起来:
const pc = new RTCPeerConnection(config); pc.onconnectionstatechange = () => { console.log('连接宏观状态:', pc.connectionState); if (pc.connectionState === 'failed') { // 通话彻底建立失败:提示用户检查网络或重启通话 } }; pc.oniceconnectionstatechange = () => { console.log('打洞子状态:', pc.iceConnectionState); }; pc.ontrack = (event) => { // 对端媒体到达:第四棒交接完成的信号 remoteVideo.srcObject = event.streams[0]; };
有了接力赛地图,「黑屏但无报错」就能按阶段归因。给出一张排查用的对照表:
| 现象组合 | 最可能的故障阶段 | 去哪一章 |
|---|---|---|
| 本端预览就黑屏 | 采集关:权限、设备占用、非安全上下文 | 第 2 章 |
| 本端正常,对端无事件到达 | 协商关:信令没通、SDP 没送达 | 第 3 章 |
| 有 offer/answer 但连接停在 checking | 接线关:候选太少、防火墙拦 UDP | 第 4 章 |
| ice 变 failed 或一直 checking | 接线关:缺 TURN 中继、对称 NAT | 第 4 章 |
| ice 已 connected 但画面黑 | 开讲关:解码能力协商错位、密钥问题 | 第 5 章 |
| 通了但几十秒后卡死花屏 | 质量关:丢包恢复与带宽估计失灵 | 第 6 章 |
实践里还有两个高频坑值得预记。坑一:忘设 iceServers。不加 STUN 服务器时只有本机地址可用,跨网段必然连不通,症状正是永远停在 checking。坑二:只在一端 addTrack。协商是双向的,A 端发了视频而 B 端一个轨道都不加,B 收不到画面是正常的——方向性由轨道决定,不是 bug。这两个坑在第 4、7 章还会以更完整的形态出现,先混个脸熟。
⚠️ 常见坑:把 connectionState === 'connected' 当作「可以发送数据」的信号是不精确的——数据通道要等它自己的 open 事件,媒体要等 ontrack。状态机到 connected 只说明「路通了」,不说明「货到了」。