浏览器兼容矩阵
浏览器兼容矩阵
WebRTC 在 spec 上看起来一统江湖,落到具体浏览器版本就是另一回事。同一段代码在 Chrome 跑得好的,Safari 可能 codec 不通,Firefox 可能 stats 字段缺失,Android WebView 可能行为更接近上一代 Chromium。本篇给出 2026-06 的实际矩阵和处理策略。
1. 为什么要单独维护一份矩阵
- W3C spec 与浏览器实际实现存在 6~18 个月的滞后窗口。
- Safari 与 Chromium 的实现节奏不同,重大特性(VP9、AV1、WebCodecs、Insertable Streams)落地差异大。
- Android WebView 与 Chrome 看似同源,实际版本受厂商系统更新节奏制约,不能假设它等同于最新 Chrome。
- iOS WKWebView 不是 Safari,但共享 WebKit 内核,bug 表现与版本对齐策略不完全一致。
什么时候适用:在做能力探测、SDK 兼容、客户端版本下限决策时翻这份表。
什么时候不适用:业务代码里不要直接 if (isSafari) 这种 UA 判定,应该用特性检测(RTCRtpSender.getCapabilities / RTCPeerConnection.generateCertificate 等)。
2. 版本下限建议(生产)
| 浏览器 | 生产最低版本 | 理由 |
|---|---|---|
| Chrome / Edge(Chromium) | 100+ | TWCC、统一 Plan B 已彻底移除,WebCodecs 稳定 |
| Firefox | 115+(ESR) | Unified Plan、Simulcast 稳定 |
| Safari macOS | 16+ | VP9 解码、WebRTC 1.0 完整 |
| Safari iOS | 15.4+ | getDisplayMedia 部分支持,PiP 改善 |
| iOS WKWebView | iOS 14.3+ | WebRTC 才进入 WKWebView,14.3 之前必须原生 SDK |
| Android WebView | 90+ | 等同于 Chromium 90,许多老设备卡在 80 |
低于上面版本的,要么走降级(H.264-only、关闭 Simulcast),要么直接拒绝接入。
3. 全特性矩阵
下表是写业务前必看的速查表。✅ 表示生产可用,⚠️ 表示有坑或部分可用,❌ 表示不可用。
3.1 视频 Codec
| Codec | Chrome / Edge | Firefox | Safari macOS | Safari iOS | Android WebView | iOS WKWebView |
|---|---|---|---|---|---|---|
| H.264 (Constrained Baseline) | ✅ 软+硬 | ✅ 软+硬 | ✅ 硬解优先 | ✅ 硬 | ✅ 硬 | ✅ 硬 |
| H.264 High Profile | ⚠️ 编码器限制 | ⚠️ | ✅ | ✅ | ⚠️ | ✅ |
| VP8 | ✅ | ✅ | ✅ 14.3+ | ✅ 14.3+ | ✅ | ✅ |
| VP9 | ✅ | ✅ | ✅ 16+ 解码 | ✅ 16+ 解码(部分机型) | ✅ | ⚠️ 仅解码 |
| AV1 | ✅ 90+ 解码,113+ 硬编 | ✅ 解码,编码受限 | ⚠️ 17+ 解码 | ⚠️ 17+ 解码(A17 起硬解) | ⚠️ 部分机型 | ⚠️ |
| H.265 / HEVC | ⚠️ 107+ 桌面解码 | ❌ | ✅ | ✅ | ⚠️ 厂商相关 | ✅ |
最大公约数永远是 H.264 Constrained Baseline。要做跨 H5/iOS/Android/小程序 互通,SDP 里必须给 H.264 一个明确 payload。
3.2 音频 Codec
| Codec | Chrome | Firefox | Safari | iOS WKWebView |
|---|---|---|---|---|
| Opus | ✅ | ✅ | ✅ | ✅ |
| Opus DTX / FEC | ✅ | ✅ | ⚠️ FEC 默认关闭 | ⚠️ |
| Opus stereo | ✅ | ✅ | ✅ | ⚠️ |
| Opus RED (RFC 2198) | ✅ 105+ | ✅ | ❌ | ❌ |
| G.711 (PCMU/PCMA) | ✅ | ✅ | ✅ | ✅ |
| L16 / 16k 采样 | ✅ | ✅ | ⚠️ | ⚠️ |
3.3 PeerConnection 与协商
| 特性 | Chrome | Firefox | Safari | iOS WKWebView |
|---|---|---|---|---|
| Unified Plan(默认) | ✅ | ✅ | ✅ | ✅ |
| Plan B(已废弃) | ❌ 96+ 移除 | ❌ | ❌ | ❌ |
RTCRtpTransceiver | ✅ | ✅ | ✅ 14+ | ✅ 14.3+ |
addTransceiver direction | ✅ | ✅ | ✅ | ✅ |
| Trickle ICE | ✅ | ✅ | ✅ | ✅ |
| ICE Restart | ✅ | ✅ | ✅ | ✅ |
Bundle Policy max-bundle | ✅ | ✅ | ✅ | ✅ |
RTCDtlsTransport | ✅ | ✅ | ✅ 14+ | ✅ 14.3+ |
RTCSctpTransport | ✅ | ✅ | ✅ | ✅ |
RTCDataChannel | ✅ | ✅ | ✅ | ✅ |
3.4 Simulcast 与 SVC
| 特性 | Chrome | Firefox | Safari | iOS WKWebView |
|---|---|---|---|---|
| Simulcast (VP8/H.264) | ✅ | ✅ | ✅ 13+(H.264 only 早期) | ✅ |
| Simulcast (VP9) | ✅ | ⚠️ 实现差异 | ⚠️ 16+ | ⚠️ |
| SVC (VP9 / AV1) | ✅ | ⚠️ | ⚠️ 17+ | ⚠️ |
setParameters 动态调整 | ✅ | ✅ | ✅ | ✅ |
scaleResolutionDownBy | ✅ | ✅ | ✅ | ✅ |
maxBitrate | ✅ | ✅ | ✅ | ✅ |
3.5 拥塞与传输
| 特性 | Chrome | Firefox | Safari | iOS WKWebView |
|---|---|---|---|---|
TWCC transport-cc | ✅ | ✅ 110+ 稳定 | ✅ 14+ | ✅ |
| REMB(旧) | ⚠️ 仅作为兜底 | ⚠️ | ⚠️ | ⚠️ |
| ABS-Send-Time | ✅ | ✅ | ✅ | ✅ |
| RTX (重传) | ✅ | ✅ | ✅ | ✅ |
| FlexFEC | ✅(默认关) | ❌ | ❌ | ❌ |
| ULPFEC | ✅ | ✅ | ⚠️ | ⚠️ |
| Opus FEC / inband-FEC | ✅ | ✅ | ⚠️ 默认关 | ⚠️ |
| QUIC datagram (RTC over QUIC) | ⚠️ Origin Trial | ❌ | ❌ | ❌ |
3.6 采集与设备
| 特性 | Chrome | Firefox | Safari | iOS WKWebView |
|---|---|---|---|---|
getUserMedia | ✅ | ✅ | ✅ | ✅ |
getDisplayMedia | ✅ | ✅ | ✅ macOS | ❌ iOS |
| 屏幕系统声音采集 | ✅(用户勾选) | ❌ | ⚠️ | ❌ |
enumerateDevices 完整 label | 授权后 | 授权后 | 授权后 | 授权后 |
applyConstraints 切 facingMode | ✅ | ✅ | ⚠️ 部分版本失效 | ⚠️ 直接重新 getUserMedia |
selectAudioOutput | ✅ | ❌ | ❌ | ❌ |
setSinkId 选扬声器 | ✅ | ✅ 116+ | ❌ | ❌ |
| BackgroundBlur 原生 | ⚠️ 实验 | ❌ | ❌ | ❌ |
3.7 高阶 API
| 特性 | Chrome | Firefox | Safari | iOS WKWebView |
|---|---|---|---|---|
| Insertable Streams (E2EE) | ✅ 94+ | ⚠️ 116+ 部分 | ⚠️ 17+ | ⚠️ |
| WebCodecs | ✅ | ✅ 130+ | ✅ 16.4+ | ✅ |
| WebTransport | ✅ | ⚠️ 114+ | ❌ | ❌ |
| WHIP / WHEP(HTTP 标准化) | ✅ 应用层 | ✅ 应用层 | ✅ 应用层 | ✅ 应用层 |
RTCRtpScriptTransform | ⚠️ Origin Trial | ❌ | ✅ | ✅ |
RTCRtpSender.getStats 完整 | ✅ | ✅ | ⚠️ 字段少 | ⚠️ |
| Picture-in-Picture | ✅ | ✅ | ✅ | ⚠️ iOS 限制多 |
requestVideoFrameCallback | ✅ | ✅ 122+ | ✅ 15.4+ | ✅ |
4. 关键差异详解
4.1 Safari 的 codec 解锁节奏
- iOS 14.3 之前 WebRTC 完全不可用,包括 WKWebView。
- iOS 14.3 ~ 15:H.264 + VP8 编解码。
- iOS 16+:VP9 解码,但编码仍然只有 H.264 / VP8。
- iOS 17+:AV1 解码(A17 Pro 起硬解,老机型软解)。
- 任何时候 Safari 主动发布的视频都优先 H.264。
结论:要让 Safari 既能发又能收,SDP 里必须有 H.264。offerToReceiveVideo 包含 VP9/AV1 没问题,但 Safari 这一端发的还是 H.264。
4.2 Firefox 与 Chrome 的 stats 字段差异
RTCStatsReport 在两家实现的 type 名一致,但字段细节差很多:
interface RtcStatsCommon {
bytesSent?: number;
bytesReceived?: number;
packetsLost?: number;
jitter?: number;
// Firefox 没有 totalRoundTripTime,只有 roundTripTime
// Chrome 105+ 才有 jitterBufferDelay
}
写监控时要包一层适配:
function readRtt(stats: RTCStatsReport): number | undefined {
for (const report of stats.values()) {
if (report.type === 'remote-inbound-rtp') {
return report.roundTripTime; // Chrome / Firefox 都有
}
if (report.type === 'candidate-pair' && report.state === 'succeeded') {
return report.currentRoundTripTime; // Chrome 主用这个
}
}
return undefined;
}
4.3 Android WebView 的版本碎片
Android WebView 由系统组件 com.google.android.webview 提供,理论上随 Chrome 同步,但:
- 国产 ROM(MIUI / EMUI / ColorOS)可能锁定某个旧版本不更新。
- 鸿蒙 NEXT 不带 GMS,WebView 由厂商自己提供,行为偏离 Chromium。
- 企业 MDM 设备会锁版本到上线时的 baseline。
生产应对:
async function detectWebRTCSupport(): Promise<{
supported: boolean;
reason?: string;
chromiumVersion?: number;
}> {
if (!window.RTCPeerConnection) {
return { supported: false, reason: 'no RTCPeerConnection' };
}
const ua = navigator.userAgent;
const chrome = ua.match(/Chrome\/(\d+)/);
const version = chrome ? parseInt(chrome[1], 10) : 0;
if (version > 0 && version < 90) {
return { supported: false, reason: 'chromium too old', chromiumVersion: version };
}
// 真正能跑 simulcast 还要进一步看
const caps = RTCRtpSender.getCapabilities?.('video');
if (!caps) return { supported: false, reason: 'no getCapabilities' };
return { supported: true, chromiumVersion: version };
}
4.4 WKWebView 与 Safari 的细微差异
虽然共享 WebKit,但有以下不同:
- 自动播放策略更严:必须
playsinline+muted,再加上用户手势触发,远端音频才能自动出声。 - 后台挂起更激进:App 切到后台 ~30s 后页面进入 frozen 状态,PeerConnection 通常断。
- getUserMedia 权限:需要在 Info.plist 加
NSCameraUsageDescription与NSMicrophoneUsageDescription,否则直接拒绝。 RTCAudioSession冲突:原生侧若启用了 RTCAudioSession,再让 WKWebView 拉getUserMedia会争抢音频通道。
4.5 Edge 的"Chromium 化"残留
新 Edge 自 2020 起完全基于 Chromium,行为和 Chrome 一致;旧 EdgeHTML 早就退役,不必再考虑。 注意:企业 IE Mode(Internet Explorer 模式)的页面不能用 WebRTC——业务页面进 IE Mode 一律拒绝接入。
5. 能力探测代码
5.1 通用 codec 探测
function getSupportedVideoCodecs(): string[] {
const caps = RTCRtpSender.getCapabilities('video');
if (!caps) return [];
return caps.codecs
.filter(c => !c.mimeType.includes('rtx') && !c.mimeType.includes('red'))
.map(c => c.mimeType.toLowerCase().replace('video/', ''));
}
function preferCodec(transceiver: RTCRtpTransceiver, codec: 'h264' | 'vp9' | 'av1') {
const caps = RTCRtpSender.getCapabilities('video');
if (!caps) return;
const ordered = [
...caps.codecs.filter(c => c.mimeType.toLowerCase().includes(codec)),
...caps.codecs.filter(c => !c.mimeType.toLowerCase().includes(codec)),
];
transceiver.setCodecPreferences(ordered);
}
5.2 浏览器 + WebView 真值矩阵
type Runtime =
| 'chrome' | 'edge' | 'firefox'
| 'safari' | 'wkwebview'
| 'android-webview' | 'unknown';
export function detectRuntime(): Runtime {
const ua = navigator.userAgent;
// iOS WKWebView: 没有 Safari/ 标识但是 AppleWebKit
if (/iPhone|iPad|iPod/.test(ua) && !/Safari\//.test(ua)) return 'wkwebview';
if (/Edg\//.test(ua)) return 'edge';
if (/Chrome\//.test(ua) && /; wv\)/.test(ua)) return 'android-webview';
if (/Chrome\//.test(ua)) return 'chrome';
if (/Firefox\//.test(ua)) return 'firefox';
if (/Safari\//.test(ua)) return 'safari';
return 'unknown';
}
UA 不是用来做能力判断的——它只用来打日志、做兼容白名单的辅助。
6. 失败时的样子
| 现象 | 大概原因 | 排查 |
|---|---|---|
| Safari 收得到 Chrome,反向无图像 | Chrome 端 SDP 没列 H.264 | setCodecPreferences 强制把 H.264 提到第一 |
| Firefox 互通后 simulcast 只剩一层 | Firefox 早期版本对 a=simulcast 解析问题 | 升级 Firefox 或退化为单层 |
| iOS Safari 黑屏,但 Android Chrome 正常 | <video> 缺 playsinline | <video playsinline muted autoplay> |
| Android WebView 卡顿 | 软件渲染兜底 | 加 <meta name="theme-color">、确认硬解、降分辨率 |
| Edge 公司账号下白屏 | 落到 IE Mode | 把域名加入站点策略黑名单(避免 IE Mode) |
| iOS 14.3 以下完全不工作 | 没有 WebRTC | 强制升级或走原生 SDK |
7. 反模式
| 反模式 | 后果 |
|---|---|
| 用 UA 判定来开关能力 | 国产浏览器 UA 各种伪装,结果误判 |
| 假设所有 Chromium 行为一致 | Android WebView 旧版本会爆出 bug |
| SDP 里只放 VP9/AV1 不放 H.264 | Safari、iOS、Android 互通失败 |
写死 RTCRtpSender.getCapabilities 不存在分支抛错 | 老 WebView 直接白屏 |
applyConstraints 切 facingMode 不写降级 | iOS Safari 部分版本无效,黑屏 |
| 只测最新 Chrome 上线 | iOS 用户进来基本必坏 |
| 通过硬编码版本号判断特性 | Safari 17.4 修了某 bug,业务还在拒绝 17.x |
8. 跨浏览器最小可行配置
const pc = new RTCPeerConnection({
iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
bundlePolicy: 'max-bundle',
rtcpMuxPolicy: 'require',
iceTransportPolicy: 'all',
});
const sender = pc.addTransceiver('video', {
direction: 'sendrecv',
sendEncodings: [
{ rid: 'h', maxBitrate: 1_500_000 },
{ rid: 'm', maxBitrate: 600_000, scaleResolutionDownBy: 2 },
{ rid: 'l', maxBitrate: 200_000, scaleResolutionDownBy: 4 },
],
});
// 强制 H.264 优先(保证跨浏览器)
const caps = RTCRtpSender.getCapabilities('video');
if (caps) {
const ordered = [
...caps.codecs.filter(c => c.mimeType === 'video/H264'),
...caps.codecs.filter(c => c.mimeType !== 'video/H264'),
];
sender.setCodecPreferences(ordered);
}
这套配置在 Chrome / Edge / Firefox / Safari 16+ / iOS 14.3+ 都能稳定建连。
9. 权威资料
- W3C WebRTC 1.0: https://www.w3.org/TR/webrtc/
- WebRTC samples 兼容测试: https://webrtc.github.io/samples/
- caniuse WebRTC: https://caniuse.com/?search=webrtc
- WebKit Release Notes: https://webkit.org/blog/category/standards/
- Chrome Platform Status: https://chromestatus.com/features#webrtc
- Mozilla WebRTC docs: https://developer.mozilla.org/docs/Web/API/WebRTC_API
- 核对日期:2026-06-22