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 的点对点连接,核心流程就三步:

  1. 创建 RTCPeerConnection 对象
  2. 交换 SDP(会话描述协议) —— 说白了就是告诉对方「我支持什么编解码器、我的网络情况如何」
  3. 交换 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 生成 SDP
  • setLocalDescription / setRemoteDescription 设置本地和远端描述
  • onicecandidate 事件处理 ICE 候选者交换
  • ontrack 事件接收远端媒体流

为什么会这样?因为 WebRTC 的设计理念就是「尽可能直接通信」。它先尝试 P2P,如果不行(比如在对称 NAT 后面),才通过 TURN 服务器中转。我遇到过最极端的情况:两个用户在同一个局域网内,但因为防火墙策略,ICE 花了 5 秒才找到直连路径。

2.4 本章知识体系图

下面这张图,把本章的核心逻辑串起来了。你看一遍,应该能理解整个流程。

WebRTC 点对点连接流程 1. 兼容性检查 navigator.mediaDevices 2. 获取本地媒体流 getUserMedia() 3. 创建连接 RTCPeerConnection 4. SDP 交换 createOffer → setLocalDescription → 信令通道 → setRemoteDescription 5. ICE 候选者交换 onicecandidate 事件 STUN/TURN 服务器 ✅ 点对点连接建立 ontrack 接收远端流

2.5 常见问题与调试技巧

问题现象 可能原因 解决方案
getUserMedia 返回 undefined 浏览器不支持或 HTTP 环境 使用 HTTPS 或 localhost
摄像头黑屏 权限被拒绝或设备被占用 检查浏览器权限设置,关闭其他应用
点对点连接失败 ICE 候选者未交换成功 检查 STUN 服务器地址,查看控制台 ICE 状态
SDP 交换报错 setRemoteDescription 顺序错误 确保先 setLocalDescription 再发送

调试利器: Chrome 的 chrome://webrtc-internals 页面。这里能看到所有 ICE 候选者、SDP 信息、连接状态。我每次排查问题,第一件事就是打开这个页面,比任何日志都好使。

好了,这一章的内容就到这里。你跟着代码敲一遍,应该能跑通第一个 Demo。如果遇到问题,别急——WebRTC 的调试本身就是学习过程。我当年第一个 Demo 跑了三天才通,现在回想起来,那些坑反而让我理解得更深。


公众号:蓝海资料掘金营,微信deep3321