3. 媒体捕获与流处理
做 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 知识体系总览
下面这张图,把本章的核心逻辑串起来了。你可以把它当作一个检测清单。
3.6 避坑指南
最后,分享几个我踩过的坑:
- 不要只检测一个对象。 有些浏览器支持
RTCPeerConnection但不支持getUserMedia(比如某些嵌入式浏览器)。三个核心对象都要查。 - 设备列表可能为空。 即使用户有摄像头,如果没授权,
enumerateDevices返回的列表里可能只有audioinput和videoinput的占位,没有具体信息。 - iOS Safari 的特殊情况。 iOS 上的 Safari 要求所有媒体调用必须在用户手势(点击、触摸)触发下才能进行。否则会直接 reject。
- 曾经有一次,我在一个会议室项目里,发现用户的 Chrome 版本太旧,
RTCPeerConnection存在但createDataChannel返回 undefined。后来我加了个 try-catch 包裹,才把这个问题抓出来。
最佳实践:把兼容性检测放在应用启动时执行。如果检测不通过,直接展示友好的提示页面,而不是让用户看到一堆报错。