WebRTC 入门指南:实时通信完全解析
🚀 简介
WebRTC(Web 实时通信)是一项强大的技术,支持浏览器和移动应用实时交换音视频与数据------无需中间服务器中转。它是现代视频通话、屏幕共享工具及实时协作平台的核心底层技术。
本文将完整覆盖 WebRTC 技术流程:从获取用户媒体到建立安全的点对点(P2P)连接,并提供基于 TypeScript 风格的 JavaScript 实战示例。
🎥 捕获媒体流
什么是媒体流(Media Stream)?
流(Stream)是连续的数据传输流------在 WebRTC 中,特指实时传输的音频或视频数据。
使用 getUserMedia 捕获音视频
通过 navigator.mediaDevices.getUserMedia() 方法可请求访问用户的麦克风和摄像头,示例如下:
JavaScript
const constraints = { audio: true, video: true }; // 配置:同时捕获音频和视频
navigator.mediaDevices
.getUserMedia(constraints)
.then((mediaStream) => {
console.log('成功获取媒体流:', mediaStream);
})
.catch((err) => {
console.error('获取媒体流失败:', err);
});
💡 注意:浏览器会先弹出权限请求,仅在用户允许后才会提供音视频访问权限。
在 <video> 元素中显示视频
首先在 HTML 中定义用于显示视频的元素:
HTML
<video autoplay playsinline id="local-video"></video>
<!-- autoplay:自动播放;playsinline:在页面内播放(避免全屏) -->
再通过 JavaScript 将捕获到的媒体流绑定到视频元素:
TypeScript
const videoElement = document.getElementById('local-video') as HTMLVideoElement;
navigator.mediaDevices.getUserMedia({ video: true }) // 仅捕获视频
.then((stream) => {
videoElement.srcObject = stream; // 将媒体流赋值给视频元素
});
🎛 枚举与选择设备
枚举所有设备
通过以下方法可列出设备上所有可用的音视频输入/输出设备(如麦克风、摄像头、扬声器):
TypeScript
navigator.mediaDevices.enumerateDevices()
.then((devices) => {
devices.forEach((device) => {
console.log(`${device.kind}:${device.label}`);
// 示例输出:videoinput:USB 摄像头、audioinput:内置麦克风
});
});
监听设备变化
当有新设备(如外接摄像头)连接或设备断开时,可通过事件监听实时更新设备列表:
TypeScript
navigator.mediaDevices.addEventListener('devicechange', () => {
console.log('设备列表已更新!');
// 可在此处重新调用 enumerateDevices() 刷新设备列表
});
🧪 使用媒体约束(Media Constraints)
媒体约束允许精细化配置音视频参数,例如指定使用某台摄像头、设置视频分辨率或帧率:
TypeScript
const preferredDeviceId = 'abc123'; // 从 enumerateDevices() 获取的目标设备 ID
const constraints = {
video: {
deviceId: { exact: preferredDeviceId }, // 精确指定使用某台设备
width: { ideal: 1280 }, // 理想宽度:1280px
height: { ideal: 720 }, // 理想高度:720px(720P)
frameRate: { ideal: 30 }, // 理想帧率:30fps
},
audio: {
echoCancellation: true, // 开启回声消除
},
};
// 按约束条件获取媒体流
navigator.mediaDevices.getUserMedia(constraints);
🖥 捕获屏幕
通过 getDisplayMedia() 方法可捕获屏幕内容(如整个屏幕、特定窗口或应用),示例如下:
TypeScript
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: true, // 屏幕捕获仅支持视频(无音频)
});
// 将屏幕流绑定到视频元素显示
document.querySelector('video').srcObject = screenStream;
📌 注意:浏览器会弹出选择窗口,要求用户指定要捕获的屏幕、窗口或应用。
🎚 管理媒体轨道(Media Tracks)
每个媒体流(Stream)包含一个或多个轨道(Track),分别对应音频轨道或视频轨道。可对轨道进行单独禁用、停止等操作:
TypeScript
// 获取仅含视频的媒体流
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
// 提取流中的所有轨道
const tracks = stream.getTracks();
// 1. 禁用轨道(临时关闭,可重新启用)
tracks[0].enabled = false; // 禁用第一个轨道(此处为视频轨道)
// 2. 完全停止轨道(释放设备资源,无法恢复)
tracks.forEach((track) => track.stop());
🌐 建立对等连接(Peer Connection)
WebRTC 的核心是 RTCPeerConnection 接口,它负责在两个对等端(如两台设备)之间建立直接连接:
TypeScript
// 配置 ICE 服务器(用于穿透 NAT,建立 P2P 连接)
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' } // 谷歌公共 STUN 服务器
],
};
// 创建对等连接实例
const peerConnection = new RTCPeerConnection(config);
📡 ICE、STUN、TURN 概念解析
WebRTC 依赖以下三种技术实现对等端之间的网络连接:
- ICE(Interactive Connectivity Establishment,交互式连接建立):核心框架,负责寻找对等端之间可用的网络路径。
- STUN(Session Traversal Utilities for NAT,NAT 会话穿越工具):帮助设备发现自身在公网中的 IP 地址和端口(解决 NAT 遮挡问题)。
- TURN(Traversal Using Relays around NAT,通过中继穿越 NAT):当 P2P 直接连接失败时,作为中继服务器转发音视频数据(确保通信不中断)。
含 TURN 服务器的配置示例
TypeScript
const config = {
iceServers: [
// 优先使用 STUN 尝试直接连接
{ urls: ['stun:stun.l.google.com:19302'] },
// 备用 TURN 服务器(需自行部署或使用商业服务)
{
urls: 'turn:turn.example.com', // TURN 服务器地址
username: 'user', // 认证用户名
credential: 'pass' // 认证密码
},
],
};
🔄 ICE 候选者交换
ICE 候选者(ICE Candidate)是包含设备网络信息(如 IP、端口、传输协议)的数据,对等端需交换候选者才能找到可通信的路径。交换需通过信令服务器(如 WebSocket)完成:
发送 ICE 候选者(本地 → 远程)
TypeScript
// 监听本地 ICE 候选者生成事件
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
// 通过信令服务器将候选者发送给远程对等端
signalingServer.send('ice-candidate', event.candidate);
}
};
接收 ICE 候选者(远程 → 本地)
TypeScript
// 监听信令服务器的 ICE 候选者消息
signalingServer.on('ice-candidate', async (candidate) => {
// 将远程候选者添加到本地对等连接
await peerConnection.addIceCandidate(candidate);
});
🤝 提议/应答交换(SDP)
对等连接建立前,需交换 SDP(Session Description Protocol,会话描述协议)信息,用于协商媒体格式、编码方式等参数。交换过程分为"提议(Offer)"和"应答(Answer)"两步:
1. 发起方创建并发送提议
TypeScript
// 1. 创建提议(包含本地媒体配置)
const offer = await peerConnection.createOffer();
// 2. 将提议设置为本地描述(保存本地配置)
await peerConnection.setLocalDescription(offer);
// 3. 通过信令服务器发送提议给接收方
signalingServer.send('offer', offer);
2. 接收方处理提议并发送应答
TypeScript
// 1. 接收并设置远程描述(保存发起方的配置)
peerConnection.setRemoteDescription(offer).then(async () => {
// 2. 创建应答(包含接收方的媒体配置)
const answer = await peerConnection.createAnswer();
// 3. 将应答设置为本地描述(保存接收方配置)
await peerConnection.setLocalDescription(answer);
// 4. 通过信令服务器发送应答给发起方
signalingServer.send('answer', answer);
});
🎤 向连接添加本地媒体
将本地捕获的音视频流添加到对等连接,才能向远程对等端传输媒体:
TypeScript
// 1. 获取本地音视频流
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
// 2. 将流中的所有轨道添加到对等连接
stream.getTracks().forEach((track) => {
peerConnection.addTrack(track, stream);
});
接收远程媒体
监听 track 事件,获取远程对等端发送的媒体流并显示:
TypeScript
peerConnection.addEventListener('track', (event) => {
// 从事件中提取远程媒体流
const remoteStream = event.streams[0];
// 将远程流绑定到视频元素(显示对方画面)
document.getElementById('remote-video').srcObject = remoteStream;
});
🔁 动态管理轨道
连接建立后,可动态调整媒体轨道(如关闭麦克风、切换摄像头):
切换麦克风(禁用/启用音频轨道)
TypeScript
// 1. 找到音频轨道的发送器(Sender)
const audioSender = peerConnection
.getSenders()
.find((sender) => sender.track?.kind === 'audio');
// 2. 禁用/启用音频轨道
if (audioSender) audioSender.track.enabled = false; // 禁用(静音)
// if (audioSender) audioSender.track.enabled = true; // 启用(取消静音)
连接后添加新轨道
TypeScript
// 假设已获取新的视频轨道(如切换摄像头后的轨道)
const newVideoTrack = stream.getVideoTracks()[0];
// 将新轨道添加到对等连接
peerConnection.addTrack(newVideoTrack, stream);
❌ 关闭 WebRTC 连接
结束通信时,需停止所有媒体轨道并关闭对等连接,释放资源:
TypeScript
// 1. 停止所有发送器的轨道
peerConnection.getSenders().forEach((sender) => sender.track.stop());
// 2. 关闭对等连接
peerConnection.close();
👥 群组通话:Mesh、SFU 与 MCU 对比
当通话参与者超过 2 人时,需选择合适的架构方案。以下是三种主流群组通话架构的对比:
Mesh 架构
- 原理:每个参与者直接与其他所有参与者建立 P2P 连接(如 3 人通话需建立 3 条连接)。
- 优点:架构简单,无需专用媒体服务器,延迟低。
- 缺点:参与者数量越多,CPU 占用和网络带宽消耗呈指数级增长(仅适合 4 人以下小规模通话)。
SFU(选择性转发单元)
- 原理:所有参与者将媒体流发送到 SFU 服务器,服务器不解码媒体,仅根据需求将流转发给其他参与者(如只转发说话者的流)。
- 优点:客户端负载低(仅需处理 1 条上传流和 N 条下载流),支持中等规模群组(20 人以内)。
- 主流方案:LiveKit、mediasoup、Mirotalk。
MCU(多点控制单元)
- 原理:所有参与者将媒体流发送到 MCU 服务器,服务器解码并混合所有流(如将多个人的画面合成一个分屏画面),再将混合后的单一流转发给所有参与者。
- 优点:客户端负载极低(仅需处理 1 条上传流和 1 条下载流)。
- 缺点:服务器解码/混合需大量计算资源,延迟较高,服务器成本高(适合大规模但对延迟不敏感的场景)。
🧩 总结
WebRTC 仅需几行 JavaScript 代码,就能实现实时音视频与数据传输。虽然入门简单,但它涉及 ICE、SDP、信令等深层技术概念。
无论你是想开发类似 Zoom 的视频会议工具,还是实时代码面试平台------理解如何捕获、发送和接收点对点媒体流,都是迈出的第一步。