Prompt
How do I make WebRTC calls work across browsers and mobile?
Latest observation
To make WebRTC calls work reliably across browsers and mobile, focus on compatibility, signaling, NAT traversal, media permissions, and mobile-specific behavior.
1) Use the right browser APIs
Most modern browsers support the core WebRTC APIs:
navigator.mediaDevices.getUserMedia()RTCPeerConnectionRTCDataChannel
But there are browser differences, especially on mobile Safari and older browsers.
Recommendations
- Prefer the latest browser versions.
- Use feature detection instead of user-agent checks.
- Bundle a WebRTC adapter/shim, like
webrtc-adapter, to normalize browser differences.
2) Implement signaling correctly
WebRTC does not define signaling. You need your own signaling channel to exchange:
- SDP offer/answer
- ICE candidates
Common choices:
- WebSocket
- Socket.IO
- Firebase / Firestore
- Custom HTTP signaling
Important
Make sure your signaling messages are:
- delivered in order
- tied to the correct call/session
- able to handle reconnects
3) Handle NAT traversal with STUN and TURN
This is one of the biggest reasons calls fail across networks.
STUN
Used so peers can discover their public-facing address.
Example:
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }
]
};
TURN
A relay server used when direct peer-to-peer connection fails, which is common on:
- mobile networks
- restrictive corporate Wi‑Fi
- symmetric NATs
- some carrier-grade NATs
For production, use a TURN server:
coturnis a common open-source choice- include both UDP and TCP
- ideally support TLS on port 443 for restrictive networks
Example:
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:turn.example.com:3478',
username: 'user',
credential: 'pass'
}
]
};
Tip
If calls only work on the same Wi‑Fi or fail on mobile data, TURN is usually the missing piece.
4) Always create the peer connection with good ICE settings
A robust baseline:
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:turn.example.com:3478',
username: 'user',
credential: 'pass'
}
],
iceCandidatePoolSize: 10
});
Also listen for:
iceconnectionstatechangeconnectionstatechangesignalingstatechange
These help you detect failures and recover.
5) Use unified-plan and transceivers where possible
Modern browsers use Unified Plan. Older code sometimes assumes Plan B, which can break interoperability.
Better approach
- Use
addTrack()rather than oldaddStream() - Use transceivers if you need advanced control
stream.getTracks().forEach(track => pc.addTrack(track, stream));
If you support older browsers, webrtc-adapter helps bridge differences.
6) Handle autoplay and audio policies on mobile
Mobile browsers often block autoplay, especially with audio.
Best practices
- Start audio/video only after a user gesture
- Mute local preview by default
- Call
video.play()in response to a tap/click - On iOS Safari, ensure the video element has:
playsInlinemutedfor local previewautoplayif appropriate
Example:
<video id="localVideo" autoplay playsinline muted></video>
<video id="remoteVideo" autoplay playsinline></video>
7) Request permissions carefully
Camera/mic permissions vary by browser and platform.
Tips
- Request
getUserMedia()only after user interaction on mobile when possible - Explain why permissions are needed
- Handle denial gracefully
Example:
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true
});
8) Secure your app with HTTPS
WebRTC requires a secure context in most browsers.
Must have
- HTTPS in production
localhostis okay for development
Without HTTPS, browsers may block:
- camera/mic access
- some WebRTC features
9) Test on real networks and devices
WebRTC often works in one environment and fails in another.
Test across:
- Chrome desktop
- Firefox desktop
- Safari macOS
- iOS Safari
- Android Chrome
- mobile data
- home Wi‑Fi
- corporate Wi‑Fi
- VPNs
Common failure case
A connection works on LAN but fails externally because there is no TURN server or TURN is misconfigured.
10) Build reconnection and fallback logic
WebRTC connections can drop due to:
- network handoff on mobile
- app backgrounding
- screen lock
- browser suspension
Add logic for:
- retrying ICE
- renegotiation
- re-acquiring media if needed
- rejoining after app resume
On mobile, expect the app to be paused when backgrounded.
11) Monitor ICE candidate types
To debug connectivity:
- host candidates = local network
- srflx candidates = STUN-discovered public IP
- relay candidates = TURN
If you only see relay candidates, your TURN server is doing the heavy lifting. If you see no usable candidates, the network path may be blocked.
12) Handle codec compatibility
Modern browsers mostly agree on common codecs, but mismatches can still happen.
Generally safe choices
- Audio: Opus
- Video: VP8 / H.264 depending on platform support
Safari has historically been more sensitive to codec choices than Chrome/Firefox.
If you need maximum compatibility with Safari/mobile, test with H.264 support in mind.
A practical compatibility checklist
- Use HTTPS
- Use
webrtc-adapter - Use
getUserMedia,RTCPeerConnection,addTrack - Support Unified Plan
- Implement WebSocket-based signaling
- Add STUN and TURN
- Make TURN available over TCP/TLS
- Use
playsInlineon iOS - Handle autoplay restrictions
- Test on iOS Safari and Android Chrome
- Add reconnect/renegotiation logic
Minimal example config
const pc = new RTCPeerConnection({
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: ['turn:turn.example.com:3478?transport=udp', 'turns:turn.example.com:5349?transport=tcp'],
username: 'user',
credential: 'pass'
}
]
});
If you want the shortest answer
To make WebRTC work across browsers and mobile:
- Use HTTPS
- Use webrtc-adapter
- Implement signaling
- Add STUN + TURN
- Support mobile autoplay/permission rules
- Test on Safari iOS and real mobile networks
If you want, I can also give you:
- a cross-browser WebRTC starter template
- a TURN server setup guide
- or a debug checklist for calls that fail on mobile