3、浏览器兼容性检测:学习如何用 JavaScript 检测浏览器是否支持 WebRTC 及可用设备。

做 WebRTC 开发,第一件事不是写信令逻辑,也不是调媒体流。

而是先问一句:浏览器到底能不能跑?

我见过不少新手,上来就抄一段 getUserMedia 的代码,结果在 Safari 上直接报错。嗯,那种场面挺尴尬的。所以这一章,咱们就把兼容性检测这件事彻底聊透。

3.1 为什么需要兼容性检测?

说白了,WebRTC 虽然已经是个标准了,但各家浏览器的实现进度和 API 命名还是有差异的。你想想看,Chrome 和 Firefox 用的是 RTCPeerConnection,但老版本的 Safari 用的是 webkitRTCPeerConnection。如果不做检测,直接调用,页面就白屏了。

我个人习惯,在项目初始化阶段就做一次完整的兼容性扫描。这样能提前暴露问题,而不是等用户反馈了才去修。

核心原则:先检测,后使用。不要假设浏览器支持 WebRTC。

3.2 检测浏览器是否支持 WebRTC

检测方法其实很简单。WebRTC 的核心对象挂在全局的 window 上。我们只需要检查这些对象是否存在。

3.2.1 检测 RTCPeerConnection

RTCPeerConnection 是 WebRTC 的核心,负责点对点连接。不同浏览器有过不同的前缀:

浏览器 对象名称 状态
Chrome / Edge (Chromium) RTCPeerConnection 标准
Firefox RTCPeerConnection 标准
Safari (11+) RTCPeerConnection 标准
旧版 Safari / iOS webkitRTCPeerConnection 已废弃
旧版 Chrome webkitRTCPeerConnection 已废弃

兼容性检测函数可以这样写:

function isWebRTCSupported() {
  return !!(
    window.RTCPeerConnection ||
    window.webkitRTCPeerConnection ||
    window.mozRTCPeerConnection
  );
}

if (!isWebRTCSupported()) {
  alert('您的浏览器不支持 WebRTC,请使用最新版 Chrome 或 Firefox。');
}

我在项目中遇到过一个问题:用户用的是 IE11,直接弹了个不支持的提示。后来我们加了个降级方案,让用户手动输入房间号,走信令中转。虽然体验差了点,但至少能用。

3.2.2 检测 getUserMedia

getUserMedia 是获取摄像头和麦克风权限的 API。它的兼容性问题更多一些。

function isGetUserMediaSupported() {
  return !!(
    navigator.mediaDevices?.getUserMedia ||
    navigator.webkitGetUserMedia ||
    navigator.mozGetUserMedia ||
    navigator.msGetUserMedia
  );
}

小提示:在 HTTPS 环境下,navigator.mediaDevices 才可用。HTTP 下会返回 undefined。所以开发时记得用 localhost 或 HTTPS。

3.2.3 检测 RTCDataChannel

数据通道用于传输非媒体数据,比如文本聊天、文件传输。检测方法类似:

function isDataChannelSupported() {
  return typeof RTCPeerConnection !== 'undefined' &&
         typeof RTCPeerConnection.prototype.createDataChannel !== 'undefined';
}

3.3 检测可用设备

光知道浏览器支持还不够。你还需要知道用户有没有摄像头、麦克风,以及用户有没有授权。

3.3.1 使用 enumerateDevices

navigator.mediaDevices.enumerateDevices() 可以列出所有媒体设备。它会返回一个 Promise,结果是一个设备列表。

async function getAvailableDevices() {
  try {
    const devices = await navigator.mediaDevices.enumerateDevices();
    const result = {
      audioInput: [],
      audioOutput: [],
      videoInput: []
    };

    devices.forEach(device => {
      if (device.kind === 'audioinput') {
        result.audioInput.push(device);
      } else if (device.kind === 'audiooutput') {
        result.audioOutput.push(device);
      } else if (device.kind === 'videoinput') {
        result.videoInput.push(device);
      }
    });

    return result;
  } catch (err) {
    console.error('获取设备列表失败:', err);
    return null;
  }
}

每个设备对象包含以下字段:

字段 说明 示例
deviceId 设备唯一标识 "a1b2c3d4..."
kind 设备类型 "videoinput"
label 设备名称(需授权) "FaceTime HD Camera"
groupId 同一物理设备的组 ID "group123"

注意:在用户未授权摄像头/麦克风之前,label 字段是空的。只有调用过 getUserMedia 并授权后,才能拿到完整的设备名称。

3.3.2 检测设备是否可用

有时候设备列表里有摄像头,但实际已经被其他应用占用了。这时候需要尝试打开设备来验证。

async function checkCameraAvailability(deviceId) {
  try {
    const constraints = {
      video: deviceId ? { deviceId: { exact: deviceId } } : true
    };
    const stream = await navigator.mediaDevices.getUserMedia(constraints);
    // 获取成功,释放资源
    stream.getTracks().forEach(track => track.stop());
    return true;
  } catch (err) {
    console.warn('摄像头不可用:', err.message);
    return false;
  }
}

我曾经遇到一个 case:用户插了外接摄像头,但系统默认用的是内置摄像头。我们通过 enumerateDevices 列出了所有设备,让用户自己选。这个功能虽然简单,但用户体验提升很明显。

3.4 完整的兼容性检测流程

在实际项目中,我习惯把检测流程封装成一个函数,返回一个状态对象。这样上层逻辑可以统一处理。

async function checkWebRTCCompatibility() {
  const result = {
    supported: false,
    rtcPeerConnection: false,
    getUserMedia: false,
    dataChannel: false,
    devices: null,
    errors: []
  };

  // 1. 检测 RTCPeerConnection
  result.rtcPeerConnection = !!(
    window.RTCPeerConnection ||
    window.webkitRTCPeerConnection ||
    window.mozRTCPeerConnection
  );

  // 2. 检测 getUserMedia
  result.getUserMedia = !!(
    navigator.mediaDevices?.getUserMedia ||
    navigator.webkitGetUserMedia ||
    navigator.mozGetUserMedia ||
    navigator.msGetUserMedia
  );

  // 3. 检测 DataChannel
  if (result.rtcPeerConnection) {
    try {
      const pc = new (window.RTCPeerConnection ||
                       window.webkitRTCPeerConnection ||
                       window.mozRTCPeerConnection)();
      result.dataChannel = typeof pc.createDataChannel === 'function';
      pc.close();
    } catch (e) {
      result.errors.push('创建 RTCPeerConnection 失败:' + e.message);
    }
  }

  // 4. 检测设备
  if (result.getUserMedia) {
    try {
      result.devices = await getAvailableDevices();
    } catch (e) {
      result.errors.push('获取设备列表失败:' + e.message);
    }
  }

  result.supported = result.rtcPeerConnection &&
                     result.getUserMedia &&
                     result.dataChannel;

  return result;
}

3.5 知识体系总览

下面这张图,把本章的核心逻辑串起来了。你可以把它当作一个检测清单。

WebRTC 兼容性检测流程 WebRTC 兼容性检测流程 开始检测 检测 RTCPeerConnection 检测 getUserMedia 检测 DataChannel 检测可用设备 返回检测结果 所有检测通过 → 支持 任一失败 → 降级提示

3.6 避坑指南

最后,分享几个我踩过的坑:

  • 不要只检测一个对象。 有些浏览器支持 RTCPeerConnection 但不支持 getUserMedia(比如某些嵌入式浏览器)。三个核心对象都要查。
  • 设备列表可能为空。 即使用户有摄像头,如果没授权,enumerateDevices 返回的列表里可能只有 audioinput 和 videoinput 的占位,没有具体信息。
  • iOS Safari 的特殊情况。 iOS 上的 Safari 要求所有媒体调用必须在用户手势(点击、触摸)触发下才能进行。否则会直接 reject。
  • 曾经有一次,我在一个会议室项目里,发现用户的 Chrome 版本太旧,RTCPeerConnection 存在但 createDataChannel 返回 undefined。后来我加了个 try-catch 包裹,才把这个问题抓出来。

最佳实践:把兼容性检测放在应用启动时执行。如果检测不通过,直接展示友好的提示页面,而不是让用户看到一堆报错。