WebRTC 入门指南:实时通信完全解析
WebRTC 入门指南:实时通信完全解析
简介
WebRTC(Web 实时通信)是一项强大的技术,支持浏览器和移动应用实时交换音视频与数据——无需中间服务器中转。它是现代视频通话、屏幕共享工具及实时协作平台的核心底层技术。
本文将完整覆盖 WebRTC 技术流程:从获取用户媒体到建立安全的点对点(P2P)连接,并提供基于 TypeScript 风格的 JavaScript 实战示例。
捕获媒体流
什么是媒体流(Media Stream)?
流(Stream)是连续的数据传输流——在 WebRTC 中,特指实时传输的音频或视频数据。
使用 getUserMedia 捕获音视频
通过 navigator.mediaDevices.getUserMedia() 方法可请求访问用户的麦克风和摄像头,示例如下:
const constraints = { audio: true, video: true }; // 配置:同时捕获音频和视频
navigator.mediaDevices
.getUserMedia(constraints)
.then((mediaStream) => {
console.log('成功获取媒体流:', mediaStream);
})
.catch((err) => {
console.error('获取媒体流失败:', err);
});
注意:浏览器会先弹出权限请求,仅在用户允许后才会提供音视频访问权限。
在 <video> 元素中显示视频
首先在 HTML 中定义用于显示视频的元素:
<video autoplay playsinline id="local-video"></video>
<!-- autoplay:自动播放;playsinline:在页面内播放(避免全屏) -->
再通过 JavaScript 将捕获到的媒体流绑定到视频元素:
const videoElement = document.getElementById('local-video') as HTMLVideoElement;
navigator.mediaDevices.getUserMedia({ video: true }) // 仅捕获视频
.then((stream) => {
videoElement.srcObject = stream; // 将媒体流赋值给视频元素
});
枚举与选择设备
枚举所有设备
通过以下方法可列出设备上所有可用的音视频输入/输出设备(如麦克风、摄像头、扬声器):
navigator.mediaDevices.enumerateDevices()
.then((devices) => {
devices.forEach((device) => {
console.log(`${device.kind}:${device.label}`);
// 示例输出:videoinput:USB 摄像头、audioinput:内置麦克风
});
});
监听设备变化
当有新设备(如外接摄像头)连接或设备断开时,可通过事件监听实时更新设备列表:
navigator.mediaDevices.addEventListener('devicechange', () => {
console.log('设备列表已更新!');
// 可在此处重新调用 enumerateDevices() 刷新设备列表
});
使用媒体约束(Media Constraints)
媒体约束允许精细化配置音视频参数,例如指定使用某台摄像头、设置视频分辨率或帧率:
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() 方法可捕获屏幕内容(如整个屏幕、特定窗口或应用),示例如下:
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: true, // 屏幕捕获仅支持视频(无音频)
});
// 将屏幕流绑定到视频元素显示
document.querySelector('video').srcObject = screenStream;
注意:浏览器会弹出选择窗口,要求用户指定要捕获的屏幕、窗口或应用。
管理媒体轨道(Media Tracks)
每个媒体流(Stream)包含一个或多个轨道(Track),分别对应音频轨道或视频轨道。可对轨道进行单独禁用、停止等操作:
// 获取仅含视频的媒体流
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 接口,它负责在两个对等端(如两台设备)之间建立直接连接:
// 配置 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 服务器的配置示例
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 候选者(本地 → 远程)
// 监听本地 ICE 候选者生成事件
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
// 通过信令服务器将候选者发送给远程对等端
signalingServer.send('ice-candidate', event.candidate);
}
};
接收 ICE 候选者(远程 → 本地)
// 监听信令服务器的 ICE 候选者消息
signalingServer.on('ice-candidate', async (candidate) => {
// 将远程候选者添加到本地对等连接
await peerConnection.addIceCandidate(candidate);
});
提议/应答交换(SDP)
对等连接建立前,需交换 SDP(Session Description Protocol,会话描述协议)信息,用于协商媒体格式、编码方式等参数。交换过程分为“提议(Offer)”和“应答(Answer)”两步:
1. 发起方创建并发送提议
// 1. 创建提议(包含本地媒体配置)
const offer = await peerConnection.createOffer();
// 2. 将提议设置为本地描述(保存本地配置)
await peerConnection.setLocalDescription(offer);
// 3. 通过信令服务器发送提议给接收方
signalingServer.send('offer', offer);
2. 接收方处理提议并发送应答
// 1. 接收并设置远程描述(保存发起方的配置)
peerConnection.setRemoteDescription(offer).then(async () => {
// 2. 创建应答(包含接收方的媒体配置)
const answer = await peerConnection.createAnswer();
// 3. 将应答设置为本地描述(保存接收方配置)
await peerConnection.setLocalDescription(answer);
// 4. 通过信令服务器发送应答给发起方
signalingServer.send('answer', answer);
});
向连接添加本地媒体
将本地捕获的音视频流添加到对等连接,才能向远程对等端传输媒体:
// 1. 获取本地音视频流
const stream = await navigator.mediaDevices.getUserMedia({ audio: true, video: true });
// 2. 将流中的所有轨道添加到对等连接
stream.getTracks().forEach((track) => {
peerConnection.addTrack(track, stream);
});
接收远程媒体
监听 track 事件,获取远程对等端发送的媒体流并显示:
peerConnection.addEventListener('track', (event) => {
// 从事件中提取远程媒体流
const remoteStream = event.streams[0];
// 将远程流绑定到视频元素(显示对方画面)
document.getElementById('remote-video').srcObject = remoteStream;
});
动态管理轨道
连接建立后,可动态调整媒体轨道(如关闭麦克风、切换摄像头):
切换麦克风(禁用/启用音频轨道)
// 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; // 启用(取消静音)
连接后添加新轨道
// 假设已获取新的视频轨道(如切换摄像头后的轨道)
const newVideoTrack = stream.getVideoTracks()[0];
// 将新轨道添加到对等连接
peerConnection.addTrack(newVideoTrack, stream);
关闭 WebRTC 连接
结束通信时,需停止所有媒体轨道并关闭对等连接,释放资源:
// 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 的视频会议工具,还是实时代码面试平台——理解如何捕获、发送和接收点对点媒体流,都是迈出的第一步。
扩展链接
SpreadJS+GCExcel全栈解决方案--协同编辑的框架搭建(三)
WebRTC 入门指南:实时通信完全解析的更多相关文章
- WebRTC 学习资源 电子书 WebRTC权威指南 Learning WebRTC
webRTC源码下载地址:https://pan.baidu.com/s/18CjClvAuz3B9oF33ngbJIw 提取码:wl1e 1.<WebRTC权威指南>第三版 中文版 本书 ...
- 【翻译】Fluent NHibernate介绍和入门指南
英文原文地址:https://github.com/jagregory/fluent-nhibernate/wiki/Getting-started 翻译原文地址:http://www.cnblogs ...
- 一起学微软Power BI系列-官方文档-入门指南(5)探索数据奥秘
我们几篇系列文章中,我们介绍了官方入门文档与获取数据等基本知识.今天继续给大家另外一个重点,探索数据奥秘.有了数据源,有了模型,下一步就是如何解析数据了.解析数据的过程需要很多综合技能,不仅仅是需要掌 ...
- Vue 入门指南 JS
Vue 入门指南 章节导航 英文:http://vuejs.org/guide/index.html 介绍 vue.js 是用来构建web应用接口的一个库 技术上,Vue.js 重点集中在MVVM模式 ...
- 【OpenCV入门指南】第一篇 安装OpenCV
http://blog.csdn.net/morewindows/article/details/8225783/ win10下vs2015配置Opencv3.1.0过程详解(转) http://ww ...
- Sublime Text 2入门指南
Sublime Text 2入门指南 一天在iteye上看到范凯介绍一个开发工具(TextMate ),看下面的评论时看到Sublime Text 2.其实我一直喜欢editplus.百度了一番才 ...
- 分布式消息系统Jafka入门指南之二
分布式消息系统Jafka入门指南之二 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs 三.Jafka的文件夹结构 1.安装tree命令 $ sudo yu ...
- 【翻译Autofac的帮助文档】1.入门指南
[写在前面]尝试做完一件工作之外自我觉得有意义的一件事,那就从翻译Autofac的帮助文档吧. 入门指南 将Autofac集成你的应用程序的步骤通常很简单,一般是: 时刻以IOC(控制反转)的思想来规 ...
- OpenCASCADE入门指南
OpenCASCADE入门指南 eryar@163.com 一.概述 荀子说“君子性非异也,善假于物也”.当你会用英语,就可以与世界各国的人交流:当你会用编程语言,就可以与计算机交流:当你会用数学语言 ...
- dogse入门指南
dogse入门指南 Dogse作为游戏服务端引擎,目前只包含游戏服务端的核心部分,但这也是最核心的部分.它全部使用.net c#开发,充分兼顾了程序性能与代码编写的准确性与易用性,再加上以vs作为开发 ...
随机推荐
- Dify实战案例:MySQL查询助手!嘎嘎好用
有了 AI 之后,我们在查询数据库的时候就不需要使用数据库客户端或程序(如 Java.Python)来查询了,我们可以直接使用 AI 来查询数据库,并且查询语句都不用你来写了,AI 会自动帮你生成. ...
- 《刚刚问世》系列初窥篇-Java+Playwright自动化测试-17- 如何优雅地切换浏览器多窗口(详细教程)
1.简介 有时候我们在网页上点击一些按钮或超链接时,有时会打开一个新的网页窗口.这个时候如果下一步操作是在新的网页窗口上,那么就需要切换网页窗口,切换到新的网页窗口后再执行元素定位等操作.Playwr ...
- USB Gadget设备枚举失败的处理方法
技术背景 我们的板子作为 USB Gadget 设备通过 USB 线接入 USB 主机使用,我们的板子被主机识别为一个 Compsite Device,这个 Compsite Device 是由我们板 ...
- ARC158(A~D)
Tasks - AtCoder Regular Contest 158 实际上是114514年前做的来着,非常好的数学(跟数论无关)/思维题集(\(A\)~\(D\)) A - +3 +5 +7 (a ...
- java 如何根据经纬度查询出一千米内的资源数据
使用地理信息系统(GIS)的相关技术和算法来实现根据经纬度查询一定范围内的资源数据.以下是一种基本的实现思路: 获取资源数据:首先,您需要有一组资源数据,每个数据都包含了对应资源的经纬度信息. 计算距 ...
- pg 锁表 取消或者中断正在执行的命令
查询出锁表的pid 进行中断或者取消 --取消后台操作,回滚未提交事物 select pg_cancel_backend(pid) --中断session,回滚未提交事物 select pg_term ...
- sql交并差运算
-- 取并集 select count(distinct user_id) from ( select user_id from hive_table where {some condition} u ...
- Copy 'xxx' to effectively final temp variable 问题解决
问题出现:如图下图所示,我们想动态的把参数i传到线程内部执行,出现语法错误,提示lambda表达式应该是final 解决方案:借助map传参数 注意:map放在for里面,放外面会存在线程安全的问题
- mac下Homebrew使用国内源
背景MacOS系统使用Homebrew官方地址时,报错: /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homeb ...
- JuiceFS 社区版 V1.3 正式发布:支持 Python SDK、亿级备份加速、SQL 和 Windows 全面优化
JuiceFS 社区版 v1.3 今日正式发布,是自 2021 年开源以来的第四个重要版本.四年多的开源历程中,JuiceFS 在 GitHub 上已获得超 11.8K star,数据用量超过 800 ...