WebRTC Browser Compatibility: What Works Where in 2026

"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.

WebRTC feature support across major browsers An at-a-glance grid of WebRTC feature support. H.264 and Opus work everywhere. VP9 and AV1 are full in Chrome and Firefox and partial in Safari. Screen capture is full in Chrome and Firefox and partial on Safari. Insertable Streams for end-to-end encryption is full in Chrome, partial in Firefox, and unavailable in Safari. Chrome / Edge Firefox Safari VP9 video AV1 video H.264 · Opus Screen capture (getDisplayMedia) Insertable Streams (E2EE) ~ ~ ~ ~ ✓ full · ~ partial / behind a flag · ✗ unavailable — illustrative for 2026; verify current status on caniuse.com
Figure 1 — The rough support picture. The safe cross-browser baseline is H.264 + Opus; anything marked ~ or ✗ needs feature detection and a fallback.

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 .local candidates: to avoid leaking a user's private IP address, browsers replace host candidates with obfuscated *.local mDNS 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.
  • getDisplayMedia constraints: 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 RTCRtpScriptTransform naming 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 →