2、环境搭建与第一个Demo:浏览器兼容性检查、本地媒体流获取(getUserMedia)、第一个点对点连接示例
好,咱们正式开始动手了。
这一章的目标很明确:让你在浏览器里跑通第一个WebRTC Demo。说白了,就是先让摄像头亮起来,再让两个页面互相打个招呼。别小看这一步,我见过太多人卡在环境上,折腾半天连个视频都出不来。
2.1 浏览器兼容性检查
WebRTC 虽然已经是个成熟技术,但不同浏览器、不同版本之间还是有差异的。我个人习惯,写任何 Demo 之前,先做兼容性检查。
怎么查?最简单的方法:打开 chrome://version 或者 about:version,看看你的浏览器版本。但更靠谱的是用代码检测。
核心检测点:
navigator.mediaDevices.getUserMedia—— 摄像头/麦克风权限RTCPeerConnection—— 点对点连接核心RTCSessionDescription—— SDP 交换RTCIceCandidate—— ICE 候选者
嗯,这里要注意:WebRTC 在 HTTP 环境下是不工作的。必须用 HTTPS 或者 localhost。我刚开始学的时候,直接在本地文件系统打开 HTML,结果 getUserMedia 一直报错,折腾了半小时才发现是协议问题。
// 浏览器兼容性检查函数
function checkWebRTCSupport() {
const checks = {
getUserMedia: !!navigator.mediaDevices?.getUserMedia,
RTCPeerConnection: !!window.RTCPeerConnection || !!window.webkitRTCPeerConnection,
RTCSessionDescription: !!window.RTCSessionDescription || !!window.RTCSessionDescription,
RTCIceCandidate: !!window.RTCIceCandidate || window.RTCIceCandidate
};
console.table(checks);
const allPassed = Object.values(checks).every(v => v === true);
if (!allPassed) {
console.warn('⚠️ 当前浏览器不完全支持 WebRTC,建议使用最新版 Chrome 或 Firefox');
}
return allPassed;
}
// 调用
checkWebRTCSupport();
这段代码跑完,控制台会输出一个表格。如果全是 true,恭喜你,环境没问题。如果有 false,别慌——我曾经在 Safari 上遇到过 RTCPeerConnection 带前缀的情况,加个 webkit 前缀就好了。
我的小技巧: 写一个 adapter.js 垫片库(Google 官方出品),它能自动处理浏览器前缀差异。我所有项目都会引入它,省心。
2.2 本地媒体流获取(getUserMedia)
这是 WebRTC 的入口。你想想看,没有摄像头画面,后面点对点传什么呢?
getUserMedia 返回一个 Promise,成功后会拿到 MediaStream 对象。这个对象里包含视频轨道和音频轨道。
async function startLocalStream() {
try {
const constraints = {
video: {
width: { ideal: 1280 },
height: { ideal: 720 },
facingMode: 'user' // 前置摄像头
},
audio: true
};
const stream = await navigator.mediaDevices.getUserMedia(constraints);
const videoElement = document.getElementById('localVideo');
videoElement.srcObject = stream;
videoElement.play();
console.log('✅ 本地媒体流获取成功');
return stream;
} catch (err) {
console.error('❌ getUserMedia 失败:', err);
// 常见错误处理
if (err.name === 'NotAllowedError') {
alert('请允许摄像头和麦克风权限');
} else if (err.name === 'NotFoundError') {
alert('未检测到摄像头或麦克风设备');
} else if (err.name === 'NotReadableError') {
alert('摄像头被其他应用占用');
}
}
}
// 页面加载时自动调用
startLocalStream();
这里有个坑:constraints 里的参数不是所有浏览器都支持。比如 facingMode 在桌面浏览器上可能被忽略。我遇到过在 Windows 上,明明写了 facingMode: 'user',结果还是调用了外接摄像头。后来我加了个设备枚举,让用户自己选。
避坑指南: 我曾经在生产环境遇到过一个问题——用户第一次访问时,浏览器弹出了权限请求,但用户点了「阻止」。之后 getUserMedia 永远返回 NotAllowedError。解决方案:在 UI 上给一个「重新请求权限」的按钮,调用 navigator.mediaDevices.getUserMedia 会重新触发权限弹窗。
2.3 第一个点对点连接示例
好,现在摄像头画面有了。接下来我们要让两个浏览器页面互相看到对方。
WebRTC 的点对点连接,核心流程就三步:
- 创建 RTCPeerConnection 对象
- 交换 SDP(会话描述协议) —— 说白了就是告诉对方「我支持什么编解码器、我的网络情况如何」
- 交换 ICE 候选者 —— 找到双方都能通信的路径
为了演示,我假设你在同一台机器上打开两个标签页。一个作为「发起方」,一个作为「接收方」。实际生产环境需要通过信令服务器交换信息,但这里我们用 window.postMessage 模拟一下。
// 创建 RTCPeerConnection
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' } // Google 公共 STUN 服务器
]
});
// 当收到远程流时,显示到视频元素
pc.ontrack = (event) => {
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = event.streams[0];
};
// 添加本地流
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
// 发起方:创建 Offer
async function createOffer() {
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
// 这里通过信令通道发送 offer 给远端
sendToRemote(JSON.stringify({ type: 'offer', sdp: offer.sdp }));
}
// 接收方:收到 Offer 后创建 Answer
async function handleOffer(offerStr) {
const offer = JSON.parse(offerStr);
await pc.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
sendToRemote(JSON.stringify({ type: 'answer', sdp: answer.sdp }));
}
// ICE 候选者交换
pc.onicecandidate = (event) => {
if (event.candidate) {
sendToRemote(JSON.stringify({ type: 'candidate', candidate: event.candidate }));
}
};
这段代码跑起来后,你会看到两个视频窗口。左边是本地摄像头,右边是远端画面。如果一切正常,两个窗口应该显示同一张脸——因为你在本地测试,两个标签页用的是同一个摄像头。
关键点总结:
createOffer/createAnswer生成 SDPsetLocalDescription/setRemoteDescription设置本地和远端描述onicecandidate事件处理 ICE 候选者交换ontrack事件接收远端媒体流
为什么会这样?因为 WebRTC 的设计理念就是「尽可能直接通信」。它先尝试 P2P,如果不行(比如在对称 NAT 后面),才通过 TURN 服务器中转。我遇到过最极端的情况:两个用户在同一个局域网内,但因为防火墙策略,ICE 花了 5 秒才找到直连路径。
2.4 本章知识体系图
下面这张图,把本章的核心逻辑串起来了。你看一遍,应该能理解整个流程。
2.5 常见问题与调试技巧
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| getUserMedia 返回 undefined | 浏览器不支持或 HTTP 环境 | 使用 HTTPS 或 localhost |
| 摄像头黑屏 | 权限被拒绝或设备被占用 | 检查浏览器权限设置,关闭其他应用 |
| 点对点连接失败 | ICE 候选者未交换成功 | 检查 STUN 服务器地址,查看控制台 ICE 状态 |
| SDP 交换报错 | setRemoteDescription 顺序错误 | 确保先 setLocalDescription 再发送 |
调试利器: Chrome 的 chrome://webrtc-internals 页面。这里能看到所有 ICE 候选者、SDP 信息、连接状态。我每次排查问题,第一件事就是打开这个页面,比任何日志都好使。
好了,这一章的内容就到这里。你跟着代码敲一遍,应该能跑通第一个 Demo。如果遇到问题,别急——WebRTC 的调试本身就是学习过程。我当年第一个 Demo 跑了三天才通,现在回想起来,那些坑反而让我理解得更深。