WebRTC - RTCPeerConnection API
WebRTC - RTCPeerConnection API
Section titled “WebRTC - RTCPeerConnection API”RTCPeerConnection API 是 WebRTC 的核心,它使浏览器(或其他 WebRTC 兼容的端点)之间能够建立直接的点对点连接(peer-to-peer connection),用于交换音频、视频和任意数据。
要创建一个 RTCPeerConnection 对象,您需要使用一个可选的配置对象来实例化它:
const configuration = { iceServers: [ { urls: 'stun:stun.l.google.com:19302' }, // 另一个 STUN 服务器示例 // { urls: 'stun:stun1.l.google.com:19302' }, // TURN 服务器配置示例 // { // urls: 'turn:your.turn.server.com:3478', // username: 'yourUsername', // credential: 'yourPassword' // } ], // 可选:控制预获取的 ICE 候选者数量 // iceCandidatePoolSize: 10, // 在现代浏览器中是默认值,'plan-b' 已废弃 // sdpSemantics: 'unified-plan'};
const peerConnection = new RTCPeerConnection(configuration);configuration 对象通常包含一个 iceServers 数组。此数组列出了 STUN(Session Traversal Utilities for NAT,NAT 会话穿越工具)和/或 TURN(Traversal Using Relays around NAT,使用中继进行 NAT 穿越)服务器。STUN 服务器帮助发现对等方的公网 IP 地址和端口,而 TURN 服务器在由于复杂的 NAT 或防火墙导致直接 P2P 连接失败时充当中继。有许多可用的公共 STUN 服务器(例如,Google 的 stun:stun.l.google.com:19302)。对于健壮(robust)的应用,通常需要部署您自己的 TURN 服务器。
RTCPeerConnection 的使用方式因对等方是发起呼叫方(caller)还是接收呼叫方(callee)而略有不同,这主要体现在 offer/answer 交换过程中。
建立连接的典型流程包括:
- 设置本地媒体:使用 navigator.mediaDevices.getUserMedia() 获取本地音频/视频流(streams)。
- 创建 RTCPeerConnection:实例化 new RTCPeerConnection(configuration)。
- 添加本地轨道:使用 peerConnection.addTrack(track, stream) 将本地媒体流中的轨道(track)添加到 RTCPeerConnection 中。这使得这些轨道可以发送到远程对等方。
- 注册 onicecandidate 处理器:当本地 ICE 代理(ICE agent)发现 ICE 候选者(ICE candidate)时,会触发此事件。这些候选者必须通过信令通道(signaling channel)发送给远程对等方。
- 注册 ontrack 处理器:当收到远程媒体轨道(MediaStreamTrack)时,会触发此事件。处理器通常应将此轨道添加到远程的
- 注册 ondatachannel 处理器(如果使用数据通道(data channels)):对于接收方对等方,这用于处理传入的数据通道请求。
- 信令逻辑(Signaling Logic):实现用于处理从信令服务器接收到的消息的处理器。这些消息将包含来自远程对等方的 offer、answer 或 ICE 候选者。
- 如果收到 SDP offer,调用 peerConnection.setRemoteDescription(offer),然后调用 peerConnection.createAnswer(),接着调用 peerConnection.setLocalDescription(answer),并将 answer 发送回去。
- 如果收到 SDP answer,调用 peerConnection.setRemoteDescription(answer)。
- 如果收到 ICE 候选者,调用 peerConnection.addIceCandidate(candidate)。
- 发起 Offer(呼叫方侧):呼叫方通过调用 peerConnection.createOffer() 启动协商,然后调用 peerConnection.setLocalDescription(offer),并通过信令通道将此 offer 发送给被呼叫方(callee)。
- 需要协商(Negotiation Needed):RTCPeerConnection 上的 negotiationneeded 事件可以指示何时需要新的 offer/answer 协商,例如在添加轨道或创建数据通道之后。应用通常应通过创建 offer 来响应。
RTCPeerConnection API 详情
Section titled “RTCPeerConnection API 详情”属性(除非另有说明,否则为只读)
Section titled “属性(除非另有说明,否则为只读)”- iceConnectionState:一个枚举(enum)(RTCIceConnectionState),指示 ICE 连接的当前状态。值包括:“new”(新建)、“checking”(检查中)、“connected”(已连接)、“completed”(已完成)、“failed”(失败)、“disconnected”(已断开)、“closed”(已关闭)。iceconnectionstatechange 事件在状态变化时触发。
- iceGatheringState:一个枚举(RTCIceGatheringState),指示 ICE 候选者收集状态:“new”(新建)、“gathering”(收集中)、“complete”(完成)。icegatheringstatechange 事件在状态变化时触发。
- localDescription:一个 RTCSessionDescription 对象,描述连接的本地端点(其 SDP)。如果尚未设置,则为 Null。
- remoteDescription:一个 RTCSessionDescription 对象,描述连接的远程端点。如果尚未设置,则为 Null。
- signalingState:一个枚举(RTCSignalingState),描述信令过程(offer/answer 交换)的状态:“stable”(稳定)、“have-local-offer”(有本地 offer)、“have-remote-offer”(有远程 offer)、“have-local-pranswer”(有本地 provisional answer,在基本 WebRTC 中较少见)、“have-remote-pranswer”(有远程 provisional answer)、“closed”(已关闭)。signalingstatechange 事件在状态变化时触发。
- canTrickleIceCandidates:一个布尔值,指示远程对等方是否可以接受 trickle ICE 候选者(candidates)(即在发现时逐个发送的候选者)。现代 WebRTC 实现通常支持此特性。
- currentLocalDescription:类似于 localDescription,但表示最近成功应用的描述。
- currentRemoteDescription:类似于 remoteDescription。
- pendingLocalDescription:一个 RTCSessionDescription,正在等待在本地应用(例如,刚刚创建的 offer 或 answer)。
- pendingRemoteDescription:一个 RTCSessionDescription,正在等待从远程端应用。
事件处理器(可赋值属性,例如 peerConnection.onevent = handlerFunc;)
Section titled “事件处理器(可赋值属性,例如 peerConnection.onevent = handlerFunc;)”- onicecandidate:当本地 ICE 代理生成 RTCIceCandidate 时触发。事件对象的 candidate 属性应发送给远程对等方。
- ontrack:当远程 MediaStreamTrack 添加到连接时触发。事件对象具有 track 属性,并且通常包含一个 streams 数组(通常包含轨道所属的单个流)。这是 onaddstream 的现代替代方案。
- ondatachannel:当远程对等方创建 RTCDataChannel 并向此对等方发送信令时触发。事件对象的 channel 属性是新的 RTCDataChannel 实例。
- onnegotiationneeded:当需要新的 offer/answer 协商时触发,例如在初始设置后添加轨道或创建数据通道之后。应用通常应通过创建 offer 来响应。
- oniceconnectionstatechange:当 iceConnectionState 属性变化时触发。
- onicegatheringstatechange:当 iceGatheringState 属性变化时触发。
- onsignalingstatechange:当 signalingState 属性变化时触发。
- onconnectionstatechange(较新的综合状态):当整体连接状态变化时触发,该状态派生自 ICE 和 DTLS 传输状态。值包括:“new”(新建)、“connecting”(连接中)、“connected”(已连接)、“disconnected”(已断开)、“failed”(失败)、“closed”(已关闭)。与单独监控 iceConnectionState 相比,这通常是一个更方便的状态。
方法(许多方法返回 Promise)
Section titled “方法(许多方法返回 Promise)”- constructor (RTCPeerConnection(configuration?)):创建一个新的 RTCPeerConnection 对象。
- createOffer(options?):异步创建一个 SDP offer。返回一个 Promise,该 Promise 会解析为一个 RTCSessionDescriptionInit 对象(即 offer)。options 可以指定是否应 offer 音频/视频,或者是否应重新启动 ICE。
- createAnswer(options?):异步为收到的 offer 创建一个 SDP answer。返回一个 Promise,该 Promise 会解析为一个 RTCSessionDescriptionInit 对象(即 answer)。
- setLocalDescription(description):设置本地 SDP 描述(offer 或 answer)。返回一个 Promise。必须在 createOffer 或 createAnswer 之后调用。
- setRemoteDescription(description):设置从对等方接收到的远程 SDP 描述。返回一个 Promise。
- addIceCandidate(candidate):添加从远程对等方接收到的 ICE 候选者。返回一个 Promise。
- getConfiguration():返回连接当前使用的 RTCConfiguration 对象。
- setConfiguration(configuration):更新连接的配置,例如添加新的 ICE 服务器。(注意:并非所有参数都可以动态更改)。
- addTrack(track, stream…?):添加一个 MediaStreamTrack,以便发送到远程对等方。可以选择将其与一个或多个 MediaStream 对象关联(对于远程端的 ontrack 事件很有用)。返回一个 RTCRtpSender 对象。
- removeTrack(sender):停止发送之前使用 addTrack() 添加的轨道。接受一个 RTCRtpSender(由 addTrack 返回)作为参数。
- getSenders():返回一个 RTCRtpSender 对象数组,每个对象代表一个负责编码和传输轨道的 RTP(Real-time Transport Protocol)发送方。
- getReceivers():返回一个 RTCRtpReceiver 对象数组,每个对象代表一个负责解码传入轨道的 RTP 接收方。
- getTransceivers():返回一个 RTCRtpTransceiver 对象数组。Transceiver 包含一个发送方和一个接收方,并管理媒体轨道的协商。这是统一计划(Unified Plan)SDP 的一部分。
- addTransceiver(trackOrKind, init?):创建一个新的 RTCRtpTransceiver 并将其添加到 transceiver 集合中。这是控制媒体协商的更高级方法。trackOrKind 可以是 MediaStreamTrack 或字符串(‘audio’ 或 ‘video’)。
- close():关闭对等连接,终止媒体,并释放资源。
- createDataChannel(label, options?):创建一个新的 RTCDataChannel,用于发送任意数据。label 是一个字符串名称,options 可以配置可靠性、排序等。
- getStats(selector?):异步收集连接或特定轨道/发送方/接收方的统计信息。返回一个 Promise,该 Promise 会解析为一个 RTCStatsReport。
示例:建立连接(概念性客户端代码)
Section titled “示例:建立连接(概念性客户端代码)”下面是建立连接的客户端 JavaScript 概念性概述。假设信令服务器(signaling server)(signalingChannel.send(message))可用并负责消息转发。
// --- 共享变量 ---let peerConnection;let localStream;let remoteStream;const signalingChannel = new WebSocket('ws://your-signaling-server.com'); // 示例
const configuration = { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }] };
// --- 信令消息处理器 ---signalingChannel.onmessage = async (event) => { const message = JSON.parse(event.data); console.log('Received signaling message:', message);
if (message.offer) { if (!peerConnection) await createPeerConnection(); // 被呼叫方在收到 offer 时创建 PC await peerConnection.setRemoteDescription(new RTCSessionDescription(message.offer)); const answer = await peerConnection.createAnswer(); await peerConnection.setLocalDescription(answer); signalingChannel.send(JSON.stringify({ answer: answer })); } else if (message.answer) { await peerConnection.setRemoteDescription(new RTCSessionDescription(message.answer)); } else if (message.candidate) { try { if (message.candidate) { // 确保 candidate 非空 await peerConnection.addIceCandidate(new RTCIceCandidate(message.candidate)); } } catch (e) { console.error('Error adding received ICE candidate', e); } } else if (message.type === 'user-left') { // 处理对等方断开连接 closePeerConnection(); }};
// --- PeerConnection 设置 ---async function createPeerConnection() { peerConnection = new RTCPeerConnection(configuration);
peerConnection.onicecandidate = event => { if (event.candidate) { signalingChannel.send(JSON.stringify({ candidate: event.candidate })); } };
peerConnection.ontrack = event => { console.log('Remote track received'); // const remoteVideo = document.getElementById('remoteVideo'); if (!remoteStream) remoteStream = new MediaStream(); remoteStream.addTrack(event.track); // remoteVideo.srcObject = remoteStream; };
// 如果 localStream 可用,则添加本地轨道 if (localStream) { localStream.getTracks().forEach(track => peerConnection.addTrack(track, localStream)); }}
// --- 呼叫方操作 ---async function startCall() { if (!peerConnection) await createPeerConnection(); const offer = await peerConnection.createOffer(); await peerConnection.setLocalDescription(offer); signalingChannel.send(JSON.stringify({ offer: offer }));}
// --- 工具函数 ---async function setupLocalMedia() { try { localStream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true }); // const localVideo = document.getElementById('localVideo'); // localVideo.srcObject = localStream; } catch (e) { console.error('Error getting user media', e); }}
function closePeerConnection() { if (peerConnection) { peerConnection.close(); peerConnection = null; console.log('PeerConnection closed.'); } // 根据需要同时清理 UI、本地/远程流}
// --- 初始化示例 ---// (async () => {// await setupLocalMedia();// // 如果此客户端是呼叫方:// // document.getElementById('callButton').onclick = startCall;// // 如果此客户端是被呼叫方,它将通过信令等待 offer。// // 在真实应用中,您将有 UI 来决定是发起呼叫还是仅准备接收。// })();原始教程示例包含了一个控制台截图,显示了成功登录后 RTCPeerConnection 对象已创建及其属性。另一个截图显示了控制台中 SDP offer 和 ICE 候选者正在交换。此更新示例侧重于 API 结构和概念流程;具体的控制台输出将取决于完整的演示实现。
本概述涵盖了 RTCPeerConnection 的基本方面。高级主题包括同播(Simulcast)、可伸缩视频编码(SVC - Scalable Video Coding)、用于更改媒体的重新协商(renegotiation)以及详细的统计数据分析。后续教程将通过实际演示这些 API 来构建语音、视频和数据聊天应用。