Prompt

How do I fix WebRTC calls that fail on some browsers

Technology / API Platforms1 observationLast seen Nov 2, 2026

Latest observation

Nov 2, 2026OpenAI APIWeb search: off

WebRTC failures on some browsers but not others are usually caused by one of a few common issues: codec mismatch, autoplay policy, missing STUN/TURN setup, CORS/permissions, or browser-specific quirks. Here’s a practical way to debug and fix them.

1) Check the browser console and WebRTC internals first

Different browsers expose useful diagnostics:

  • Chrome / Edge: chrome://webrtc-internals
  • Firefox: about:webrtc
  • Safari: Web Inspector → Console + Media logs

Look for:

  • ICE state stuck on new, checking, or failed
  • getUserMedia permission errors
  • setLocalDescription / setRemoteDescription failures
  • addIceCandidate errors
  • codec negotiation issues
  • DTLS/SRTP handshake failures

2) Make sure your signaling is correct

WebRTC itself does not connect peers automatically; your signaling server must reliably exchange:

  • offer
  • answer
  • ICE candidates

Common mistakes:

  • sending ICE candidates before remote description is set
  • not handling glare / simultaneous offers
  • dropping candidates during reconnects
  • mismatched SDP due to race conditions

A safe pattern:

  • set remote description first
  • queue incoming ICE candidates until remote description is set
  • only then call addIceCandidate

3) Use STUN and TURN properly

A lot of “works on my browser” issues are actually NAT traversal problems.

  • STUN helps peers discover public-facing addresses.
  • TURN relays media when direct connection fails.

If some browsers work only on the same network or only in certain environments, TURN is often missing or misconfigured.

Checklist:

  • include at least one STUN server
  • deploy a TURN server for production
  • ensure TURN supports UDP and TCP
  • ideally support TLS on 443 for restrictive networks

Example:

const pc = new RTCPeerConnection({
  iceServers: [
    { urls: "stun:stun.l.google.com:19302" },
    {
      urls: [
        "turn:turn.example.com:3478?transport=udp",
        "turn:turn.example.com:3478?transport=tcp",
        "turns:turn.example.com:443?transport=tcp"
      ],
      username: "user",
      credential: "pass"
    }
  ]
});

4) Verify codec compatibility

Some browsers prefer or require different codecs.

Typical issues:

  • Safari support differs from Chrome/Firefox
  • H.264 vs VP8/VP9/AV1 negotiation issues
  • audio codec mismatch (Opus usually safest)

To reduce problems:

  • prefer Opus for audio
  • support H.264 if Safari/iOS is involved
  • don’t assume VP8/VP9 will work everywhere
  • avoid SDP munging unless necessary

If you modify SDP manually, that can break interoperability.

5) Watch out for autoplay restrictions

A common “call connects but no sound/video plays” issue.

Browsers may block autoplay unless:

  • user has interacted with the page
  • media is muted initially
  • video.play() is called in a user gesture context

Fix:

  • start remote video muted if needed
  • call element.play() after user action
  • handle promise rejections from play()

Example:

remoteVideo.play().catch(err => {
  console.warn("Autoplay blocked:", err);
});

6) Make sure you handle tracks correctly

Modern WebRTC uses addTrack and ontrack, not older addStream APIs.

Use:

stream.getTracks().forEach(track => pc.addTrack(track, stream));

pc.ontrack = (event) => {
  remoteVideo.srcObject = event.streams[0];
};

Browser differences often appear if one side still uses legacy APIs.

7) Check secure context requirements

Most WebRTC features require HTTPS, except localhost.

If some browsers fail in production:

  • ensure the site is served over HTTPS
  • avoid mixed content
  • make sure permissions are granted on a secure origin

8) Fix ICE candidate handling across browsers

Browsers may emit candidates at different times, and some send an empty candidate to signal end-of-candidates.

Make sure you:

  • handle null / empty ICE candidates
  • don’t treat them as errors
  • serialize and forward candidates correctly

Example:

pc.onicecandidate = (event) => {
  if (event.candidate) {
    sendToPeer({ type: "ice-candidate", candidate: event.candidate });
  }
};

9) Handle Safari-specific quirks

Safari is often the browser that “fails on some calls.”

Common Safari issues:

  • stricter autoplay rules
  • different handling of codecs
  • issues with bundled ICE candidates in some versions
  • camera/mic permission behavior
  • speaker selection limitations

Try:

  • updating Safari/iOS
  • including H.264 support
  • testing with TURN over TCP/TLS
  • avoiding assumptions about getStats() fields being identical across browsers

10) Check transceiver and SDP direction mismatches

If you use transceivers, make sure directions align:

  • sendrecv
  • sendonly
  • recvonly
  • inactive

A mismatch can cause one browser to think it should send while the other expects receive-only.

11) Inspect getUserMedia constraints

Some browsers are pickier about constraints.

If this fails:

navigator.mediaDevices.getUserMedia({
  video: { width: { ideal: 1920 }, height: { ideal: 1080 } },
  audio: true
});

Try loosening constraints:

navigator.mediaDevices.getUserMedia({
  video: true,
  audio: true
});

Then gradually add constraints back.

12) Use stats to narrow down the failure

getStats() can tell you whether the issue is:

  • network connectivity
  • packet loss
  • codec mismatch
  • RTP not flowing
  • DTLS not established

Look for:

  • candidate-pair selected
  • bytesSent / bytesReceived
  • framesDecoded
  • packetsLost
  • currentRoundTripTime

If selectedCandidatePair exists but media counters stay at zero, it’s often signaling/SDP/track wiring rather than network.

13) Test with a minimal example

Reduce your app to:

  • one offer
  • one answer
  • one audio track
  • one video track
  • basic STUN/TURN
  • no custom SDP munging

If the minimal version works, reintroduce features one by one.

Quick checklist

  • HTTPS enabled
  • STUN + TURN configured
  • Remote description set before ICE candidates
  • ontrack used correctly
  • Browser autoplay handled
  • Codec support includes Safari/iOS needs
  • No legacy API mismatch
  • Permissions granted
  • Check webrtc-internals / about:webrtc

If you want, I can help you debug a specific browser issue if you paste:

  1. the browser(s) that fail,
  2. the error from console, and
  3. your RTCPeerConnection config / signaling flow.

Observation history

How did Obsurfable measure this prompt?

Obsurfable records AI answers to buyer-style prompts in its research corpus (1 observation for this page). Metrics are distributions over observations, not a single static ranking.

Which AI systems does Obsurfable collect answers from?

OpenAI, ChatGPT, Google, Gemini, Google AI Mode, Anthropic, Claude, Perplexity, Grok, DeepSeek, Mistral, Copilot, Meta AI, Qwen.

How can brands improve their AEO?

Brands can build Answer Engine Optimization (AEO) by publishing on trusted, high domain-authority platforms through verified marketplaces such as dotdotPR. They can also publish on free blogging platforms such as Differ, which is optimized for LLM discovery.

Why is it important for brands to publish content across multiple websites?

Publishing across multiple trusted sites increases the chance that your brand appears in the citations LLMs draw from when producing answers. Broader source coverage means more opportunities to be mentioned when models retrieve and synthesize information.

Want this interpreted for your brand?

Explorer is the free public corpus. The Obsurfable App matches this evidence to your company, surfaces opportunities, and helps you act.