能力中的 API
WebRTC API
发布于 更新于
WebRTC 在两个浏览器之间(或浏览器与媒体服务器之间)通过加密 UDP 直接传输音频、视频和任意数据:RTCPeerConnection 经 SDP 的 offer/answer 交换协商编解码器与传输,ICE 寻找网络路径,addTrack() 挂上 getUserMedia() 采集的 MediaStreamTrack,RTCDataChannel 经 SCTP 传递应用消息。offer、answer 与 ICE candidate 的交换(信令)留给应用自己完成,页面已有的任何通道都行,通常是 WebSocket。
无前缀的 RTCPeerConnection 构造函数自 Chrome 56、Edge 15、Firefox 44、Safari 11、Samsung Internet 6 和 Android WebView 56 起可用,因此 Baseline 把它列为 2017-09 起广泛可用。getUserMedia() 带 [SecureContext],采集需要 https:// 或 localhost;对等连接本身不要求,但采集不了的页面通常也没什么可发送。
new RTCPeerConnection()new RTCPeerConnection(configuration)
pc.createOffer()pc.createOffer(options)pc.createAnswer()pc.setLocalDescription()pc.setLocalDescription(description)pc.setRemoteDescription(description)pc.addIceCandidate(candidate)
pc.addTrack(track)pc.addTrack(track, ...streams)pc.createDataChannel(label)pc.createDataChannel(label, options)pc.close()
navigator.mediaDevices.getUserMedia(constraints)createOffer() 与 createAnswer() 以 RTCSessionDescriptionInit({ type, sdp })兑现;两个设置描述的方法和 addIceCandidate() 以 undefined 兑现,并在连接的操作链上按调用顺序执行。不带参数调用 setLocalDescription() 会按当前 signalingState 创建所需的 offer 或 answer。addTrack() 同步返回 RTCRtpSender,createDataChannel() 同步返回 RTCDataChannel,getUserMedia() 以 MediaStream 兑现。
构造函数的 RTCConfiguration、createDataChannel() 的 RTCDataChannelInit 和 getUserMedia() 的 MediaStreamConstraints 是一次典型调用会碰到的字典(见 RTCConfiguration 成员(w3.org))。
| 成员 | 类型 | 必填 | 说明 |
|---|---|---|---|
configuration.iceServers |
sequence<RTCIceServer> |
否 | 默认 []。每项含 urls(一个或多个 stun:/turn:/turns: URL)以及 username 和 credential,turn: 与 turns: 条目必须携带后两者。没有 TURN 服务器时,对称 NAT 后的对等方无法连通。 |
configuration.iceTransportPolicy |
RTCIceTransportPolicy |
否 | "all"(默认)或 "relay";"relay" 只使用 TURN candidate,从而隐藏用户 IP。 |
configuration.bundlePolicy |
RTCBundlePolicy |
否 | "balanced"(默认)、"max-compat" 或 "max-bundle";控制多个媒体段协商多少条传输。 |
configuration.rtcpMuxPolicy |
RTCRtcpMuxPolicy |
否 | 规范中只剩 "require";不复用 RTCP 的远端描述会被拒绝。 |
configuration.certificates |
sequence<RTCCertificate> |
否 | 默认 []。来自 RTCPeerConnection.generateCertificate() 的证书,使 DTLS 指纹跨会话稳定。 |
configuration.iceCandidatePoolSize |
octet |
否 | 默认 0。在 setLocalDescription() 之前预先收集的 candidate 数。 |
options.ordered(数据通道) |
boolean |
否 | 默认 true。false 时消息按到达顺序交付。 |
options.maxPacketLifeTime / options.maxRetransmits |
unsigned short |
否 | 互斥;任一项都把通道变为部分可靠。 |
options.negotiated / options.id |
boolean / unsigned short |
否 | negotiated: true 配合显式 id(0 到 65534)跳过带内通道通告;两端都必须创建该通道。 |
constraints.audio / constraints.video |
boolean 或 MediaTrackConstraints |
否 | 至少一项存在且为真值。对象形式接受 width、height、frameRate、facingMode、deviceId,每项可写裸值(偏好)或 { exact } / { ideal }。 |
下列名称来自 webrtc-pc 与 mediacapture-main;RTCPeerConnection 的方法以拒绝 Promise 的方式报错,标注「抛出」的除外(带 errorDetail 的 SDP 与传输失败见 RTCError(w3.org))。
| 异常 | 条件 |
|---|---|
InvalidStateError |
signalingState 为 "closed" 后调用 createOffer()、createAnswer()、两个设置描述的方法、addIceCandidate()、addTrack()(抛出)和 createDataChannel()(抛出);remoteDescription 为 null 时调用 addIceCandidate();状态不是 "have-remote-offer" 或 "have-local-pranswer" 时调用 createAnswer()。 |
InvalidModificationError |
传给 setLocalDescription() 的 offer 或 answer,其 sdp 与 createOffer()/createAnswer() 最近生成的不一致。 |
InvalidAccessError |
构造函数收到过期或跨源证书,或没有 username 和 credential 的 turn: 服务器(抛出);描述未通过 RFC 9429 语义校验、在 rtcpMuxPolicy: "require" 下不复用 RTCP,或重新协商 RID;对同一个 track 调用两次 addTrack()(抛出)。 |
SyntaxError |
iceServers 的某个 URL 无法解析、带路径,或 query 不是 transport=udp 或 transport=tcp(构造函数与 setConfiguration() 抛出)。 |
NotSupportedError |
iceServers 的某个 URL 使用浏览器未实现的协议(抛出)。 |
TypeError |
addIceCandidate() 收到非空 candidate 但 sdpMid 与 sdpMLineIndex 都为 null;createDataChannel() 同时设置 maxPacketLifeTime 和 maxRetransmits、用 negotiated: true 却不给 id,或使用 id: 65535(抛出)。 |
OperationError |
addIceCandidate() 的 sdpMid 匹配不到任何媒体段、sdpMLineIndex 越界,或 usernameFragment 与已应用的远端描述不匹配;createDataChannel() 发现 id 已被占用或耗尽(抛出);应用描述因上述以外的原因失败;createOffer() 无法检查系统的编解码器与资源。 |
RTCError |
DOMException 的子类,errorDetail 指明失败类型:设置描述的方法给出 "sdp-syntax-error"(附 sdpLineNumber),传输层给出 "dtls-failure"、"fingerprint-failure"、"sctp-failure" 或 "data-channel-failure",经 RTCDataChannel 的 error 事件送达。 |
NotAllowedError(getUserMedia) |
用户拒绝授权、页面被 camera/microphone Permissions Policy 禁止,或调用缺少浏览器要求的激活。 |
NotFoundError(getUserMedia) |
不存在所请求类型的设备。 |
NotReadableError(getUserMedia) |
操作系统或其他应用占用着设备。 |
OverconstrainedError(getUserMedia) |
某个 exact 约束无法满足;error.constraint 给出其名称。 |
TypeError / SecurityError(getUserMedia) |
constraints 既没有 audio 也没有 video;文档不是 fully active 或上下文不安全。 |
三个示例构成一次完整通话:采集、协商、交换数据,每个都给出缺少支持或权限时所走的分支。
采集本地媒体并在约束不满足时回退
Section titled “采集本地媒体并在约束不满足时回退”把 RTCPeerConnection 和 navigator.mediaDevices 一起检测;http:// 页面的 navigator.mediaDevices 为 undefined。先请求理想分辨率,摄像头满足不了 exact 约束时改用最简单的 video: true 重试。
async function capture() { if (!("RTCPeerConnection" in window) || !navigator.mediaDevices?.getUserMedia) { return null; // 改为显示上传表单,而不是实时通话 } try { return await navigator.mediaDevices.getUserMedia({ audio: true, video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: { exact: "user" } }, }); } catch (err) { if (err.name === "OverconstrainedError") { console.warn(`约束 ${err.constraint} 无法满足,去掉后重试`); return navigator.mediaDevices.getUserMedia({ audio: true, video: true }); } if (err.name === "NotAllowedError") return null; // 权限被拒:走无音视频的回退 throw err; }}桌面端最常触发它的是 facingMode: { exact: "user" },因为网络摄像头根本不报告朝向。
由浏览器事件驱动 offer、answer 与 ICE
Section titled “由浏览器事件驱动 offer、answer 与 ICE”让 negotiationneeded 来创建 offer,这样之后加入的 track 会自动重新协商;把每个 icecandidate 经信令通道转发;只在 setRemoteDescription() 执行之后才把对端的 candidate 交给 addIceCandidate()。
function connect(stream, signaling, iceServers) { const pc = new RTCPeerConnection({ iceServers }); const controller = new AbortController(); const { signal } = controller; for (const track of stream.getTracks()) pc.addTrack(track, stream);
pc.addEventListener("negotiationneeded", async () => { await pc.setLocalDescription(); // 按当前状态创建 offer signaling.send({ description: pc.localDescription }); }, { signal }); pc.addEventListener("icecandidate", ({ candidate }) => { if (candidate) signaling.send({ candidate }); }, { signal }); pc.addEventListener("track", ({ streams }) => { document.querySelector("#remote").srcObject = streams[0]; }, { signal });
signaling.onmessage = async ({ description, candidate }) => { if (description) { await pc.setRemoteDescription(description); if (description.type === "offer") { await pc.setLocalDescription(); signaling.send({ description: pc.localDescription }); } } else if (candidate && pc.remoteDescription) { await pc.addIceCandidate(candidate); } };
return () => { controller.abort(); for (const track of stream.getTracks()) track.stop(); pc.close(); };}返回的函数就是挂断:中止 signal 一次性移除全部监听器,停止 track 会熄灭摄像头指示灯,close() 结束 ICE。addEventListener() 的 signal 选项需要 Chrome 90、Firefox 86 或 Safari 15;低于这些版本时保留具名处理函数并调用 removeEventListener()。
通过数据通道收发消息并回退到 WebSocket
Section titled “通过数据通道收发消息并回退到 WebSocket”数据通道不需要任何媒体。在第一个 offer 之前创建通道,使其在带内被通告;每次 send() 前读取 readyState,通道只在 "open" 时可用。
function openChat(pc, signalingSocket, onMessage) { if (!pc || typeof pc.createDataChannel !== "function") { signalingSocket.addEventListener("message", (e) => onMessage(e.data)); return (text) => signalingSocket.send(text); // 经服务器中转 } const channel = pc.createDataChannel("chat", { ordered: false, maxRetransmits: 0 }); channel.binaryType = "arraybuffer"; channel.addEventListener("message", (e) => onMessage(e.data)); channel.addEventListener("error", (e) => console.error(e.error.errorDetail, e.error.message));
pc.addEventListener("datachannel", ({ channel: incoming }) => { incoming.addEventListener("message", (e) => onMessage(e.data)); });
return (text) => { if (channel.readyState === "open") channel.send(text); else signalingSocket.send(text); };}ordered: false 配合 maxRetransmits: 0 给在线状态或光标位置更新提供类似 UDP 的交付;必须完整、有序到达的聊天文本则两个选项都不要设。
- Screen Capture API,屏幕共享 track 的来源
getDisplayMedia() - WebCodecs,帧进入对等连接前的自定义编码
- WebTransport API,
RTCDataChannel的客户端到服务器替代方案 - Document Picture-in-Picture,把远端视频保持在置顶窗口中
- WebRTC: RTCPeerConnection constructor(w3.org)
- WebRTC: addIceCandidate()(w3.org)
- Media Capture and Streams: getUserMedia()(w3.org)
- RFC 8445: Interactive Connectivity Establishment (ICE)(rfc-editor.org)
- WebRTC samples(webrtc.github.io)
规范
- 图例
- 支持
- 部分支持
- 需开启标志
- 不支持
- 未知
| 浏览器 / 平台 | 支持 | 版本 | 置信度 | 来源 | 备注 |
|---|---|---|---|---|---|
| Chrome (Desktop) | 支持 | 56 | 高 | 来源 | — |
| Chrome (Android) | 支持 | 56 | 高 | 来源 | 1 |
| Edge (Desktop) | 支持 | 15 | 高 | 来源 | — |
| Firefox (Desktop) | 支持 | 44 | 高 | 来源 | — |
| Firefox (Android) | 支持 | 44 | 高 | 来源 | — |
| Safari (macOS) | 支持 | 11 | 高 | 来源 | — |
| Safari (iOS) | 支持 | 11 | 高 | 来源 | 2 |
| Samsung Internet | 支持 | 6.0 | 高 | 来源 | 3 |
| WebView (Android) | 支持 | 56 | 高 | 来源 | 4 |
- 由 browser-compat-data 镜像自 Chrome 的数据推导。
- 由 browser-compat-data 镜像自 Safari 的数据推导。
- 由 browser-compat-data 镜像自 Chrome Android 的数据推导。
- 由 browser-compat-data 镜像自 Chrome Android 的数据推导。