"Does WebRTC work in this browser?" stopped being an interesting question years ago. The core of WebRTC
1.0 is a W3C Recommendation, and the basic loop — getUserMedia, build an
RTCPeerConnection, exchange an offer and answer, watch media flow — runs the same way in every
major browser. If your app only does that, it will work in Chrome, Firefox, Safari, and Edge without
special handling.
The interesting questions are narrower and sharper. Which codecs can this browser actually encode? Does it do AV1 in hardware or fall back to a CPU-melting software path? Is that nice new encoded-transform API spelled the same here as in Chrome? Will Safari let your audio autoplay, or will it sit silent until the user taps something? The compatibility problems of 2026 live in codecs, hardware acceleration, autoplay, permissions, and a handful of newer extension APIs — not in whether peer connections exist at all. This article maps that terrain browser by browser, then shows how to detect features safely instead of guessing from the user agent.
Desktop browsers
Chrome and the Chromium family
Chrome is effectively the reference implementation. New WebRTC capabilities tend to land here first and in the most complete form: AV1, VP9 with spatial SVC, full simulcast, and the freshest extension APIs. Chromium-derived browsers inherit most of this. If a feature exists anywhere in WebRTC, it almost certainly exists in modern Chrome, which makes it the natural baseline to develop against — with the usual caveat that "works in Chrome" is the start of testing, not the end.
Firefox
Firefox has a genuinely independent, solid WebRTC stack. Core calling, the standard codecs, simulcast, and data channels all work well. Where it tends to trail is the newest extension surface — some recently standardized or Chrome-incubated APIs arrive later, and a few behave with subtle differences. For mainstream audio/video apps Firefox is a first-class target; for bleeding-edge media processing, test it explicitly rather than assuming parity.
Safari
Safari is the browser that earns its own test pass. Its WebRTC history is H.264-first, and while VP8 and
VP9 support has matured over the years, Apple's hardware and software choices still shape what is available
on a given machine — AV1 encode in particular depends on the underlying silicon and may be absent on older
hardware. Safari also enforces a stricter autoplay and permission model than Chrome, and
getUserMedia has historically had quirks around when and how device labels and streams become
available. None of this is fatal, but it means Safari behavior cannot be inferred from Chrome behavior.
Edge
Modern Edge is Chromium under the hood, so its WebRTC behavior mirrors Chrome closely. In practice you can treat current Edge and Chrome as the same target for compatibility purposes and spend your testing budget on Firefox and Safari instead.
Mobile browsers
Mobile is where compatibility gets physical — battery, backgrounding, and OS audio policies intrude on what the browser can do.
iOS and the WebKit rule
For most of WebRTC's life, every browser on iOS — Chrome, Firefox, Edge, all of them — was required to use Apple's WebKit engine under the hood. That meant iOS Chrome was not really Chrome for WebRTC purposes; it behaved like Safari. As of 2024 and later, EU regulation began allowing alternative browser engines on iOS in some regions, so this is no longer a universal rule, but you should still assume WebKit behavior on iOS unless you have confirmed otherwise for your users' region and browser.
Beyond the engine, iOS imposes lifecycle hazards. Sending the app to the background typically stops the camera, and audio sessions can be interrupted by a phone call, Siri, or another app grabbing the audio focus. Robust iOS apps listen for these interruptions and re-acquire media when the page returns to the foreground. The mobile devices article goes deeper on these lifecycle traps.
Android
Android Chrome tracks desktop Chrome closely, including codec support, though hardware encoder availability varies by device. The bigger variable is Android WebView, which many in-app browsers embed: its WebRTC behavior depends on the system WebView version, which users update independently, so an in-app browser can lag a standalone Chrome on the same phone.
Codec availability by browser
The table below sketches where each codec stands. Entries marked with a dagger vary by operating system or hardware — the same browser can differ between two machines depending on what the GPU and OS expose.
| Codec | Chrome / Edge | Firefox | Safari |
|---|---|---|---|
| Opus (audio) | Yes | Yes | Yes |
| G.711 (PCMU/PCMA) | Yes | Yes | Yes |
| VP8 | Yes | Yes | Yes |
| H.264 | Yes | Yes | Yes |
| VP9 | Yes | Yes | Yes† |
| AV1 decode | Yes | Yes | Yes† |
| AV1 encode | Yes (software/some hardware) | Yes† | Hardware-dependent† |
† Availability depends on OS version, GPU, and SoC. Always query
RTCRtpSender.getCapabilities('video') at runtime rather than trusting a static table —
including this one. For the deeper trade-offs between these codecs, see the
codec selection guide.
API differences that bite
-
mDNS
.localcandidates: to avoid leaking a user's private IP address, browsers replace host candidates with obfuscated*.localmDNS hostnames. This started in Chrome and is now common across browsers. It is usually transparent, but it can surprise server-side logging and any code that tries to parse raw IPs out of candidates. -
getDisplayMediaconstraints: screen-capture support exists everywhere, but the constraint set, the picker UI, and which surfaces (tab, window, full screen) are offered differ by browser and platform. Do not assume a constraint accepted by Chrome is honored identically by Safari. -
setCodecPreferences: available in modern browsers, but feature-detect it before calling — it is a method on the transceiver that older or unusual builds may not expose. -
Insertable Streams / Encoded Transform: the API for processing encoded frames (for
end-to-end encryption, for example) is exposed differently across engines — Chrome's
Insertable Streams versus the standardized
RTCRtpScriptTransformnaming used elsewhere. Check for the specific object before wiring it up. - WebCodecs: low-level encode/decode access is broadly available in Chromium and has been arriving elsewhere, but coverage is uneven. Treat it as progressive enhancement, not a baseline.
The things that mostly do behave consistently
It is worth naming the parts of WebRTC you can rely on across browsers, because over-defending against
phantom incompatibilities wastes time. Data channels (RTCDataChannel) are dependable
everywhere, including ordered and unordered, reliable and unreliable modes — they ride on SCTP over DTLS
and the implementations have long since converged. Perfect negotiation, the recommended pattern for
handling glare when both peers try to offer at once, works uniformly when implemented to the spec. DTLS-SRTP
media encryption is mandatory and identical in concept across browsers; there is no "insecure mode" to
account for.
The standardized statistics API, getStats(), returns the same report types
(inbound-rtp, outbound-rtp, candidate-pair, and friends) in every
browser, which makes monitoring portable. The caveat is that not every browser populates every optional
field, and some metrics are computed slightly differently, so build dashboards around the core fields and
treat the rest as best-effort. Trickle ICE — sending candidates as they are discovered rather than waiting
for gathering to finish — is universally supported and should always be on; disabling it only slows
connection setup.
Autoplay policies
Every modern browser blocks audible autoplay by default. Muted video generally plays automatically; anything with sound typically needs a user gesture first, and Safari is the strictest about it. The safe pattern is to attempt playback, and if it is rejected, start muted and surface an unmute control the user taps:
async function safePlay(videoEl, stream) {
videoEl.srcObject = stream;
try {
await videoEl.play(); // may reject due to autoplay policy
} catch (err) {
videoEl.muted = true; // muted playback is allowed
await videoEl.play();
showUnmuteButton(videoEl); // let a user gesture restore audio
}
}
function showUnmuteButton(videoEl) {
const btn = document.querySelector('#unmute');
btn.hidden = false;
btn.addEventListener('click', () => {
videoEl.muted = false; // runs inside a user gesture
btn.hidden = true;
}, { once: true });
}
Permissions and transport security
Camera and microphone permission models diverge. Chrome tends to remember a granted permission for an
origin, so a returning user is not re-prompted. Safari leans toward asking per session, meaning your users
may see the prompt more often than your Chrome testing suggests — design your UI so a fresh prompt is not
jarring. One rule is universal: getUserMedia, getDisplayMedia, and the secure
context they require only work over HTTPS (or localhost during development). There is no
browser where you can capture media from a plain http:// page.
Feature detection done right
The cardinal rule: never sniff the user agent string to decide what a browser can do. UA strings lie, get spoofed, and change without warning, and a feature you keyed off a version number may appear in an unrelated browser or vanish in an update. Test for the capability itself.
function detectWebRTC() {
const support = {
peerConnection: typeof RTCPeerConnection !== 'undefined',
getUserMedia:
!!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia),
getDisplayMedia:
!!(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia),
setCodecPreferences: false,
encodedTransform:
typeof RTCRtpScriptTransform !== 'undefined' ||
'createEncodedStreams' in RTCRtpSender.prototype,
};
if (support.peerConnection) {
const t = new RTCPeerConnection().addTransceiver('video');
support.setCodecPreferences =
typeof t.setCodecPreferences === 'function';
}
return support;
}
const caps = detectWebRTC();
if (!caps.peerConnection || !caps.getUserMedia) {
showUnsupportedMessage(); // degrade gracefully, do not crash
}
As for adapter.js, the shim that once papered over enormous cross-browser API differences: it does far less in 2026 than it used to, because the browsers converged on the standard. If you target only current evergreen browsers, you can often skip it entirely. It still earns its place if you must support older versions or want to insulate yourself from a few lingering naming and event-ordering edge cases — but reach for it deliberately, not by reflex.
Building a testing matrix
Compatibility testing for WebRTC cannot live entirely in CI. A few principles keep it honest:
- Test Safari and iOS on real devices. Simulators and remote rendering miss the camera, audio-session, and backgrounding behavior that actually breaks in the field.
- Cover the matrix of browser × OS × network, not just browser. The same browser behaves differently on a corporate firewall than on home Wi-Fi.
- Remember that ICE behavior varies by browser too. mDNS candidate handling, the order of candidate gathering, and candidate pruning differ between engines, which means connectivity itself — not just media — can succeed in one browser and stall in another. For the diagnostic toolkit, see debugging WebRTC connections.
When a call works in one browser and fails in another, the cause is often network reachability rather than the media code. Before you blame your application, confirm that your STUN and TURN servers are reachable and returning candidates from the browser in question — the ICE server tester lets you run that check per browser in seconds.
Check Your Own Servers
Browsers gather ICE candidates differently, so a TURN server that works in Chrome can still stall in Safari. Open the tester in each browser you support and confirm your STUN/TURN servers return reachable candidates everywhere.
Open the Free ICE Server Tester →