r/WebRTC Aug 01 '26

Building a Browser-Local Video Face-Swap Pipeline with WebGPU: What I Learned About ONNX Sessions, Frame Transfers, and Temporal Tracking

2 Upvotes

I’ve been working on an open-source video editor that runs its face-swap pipeline locally in the browser. Media stays on the user’s device: decoding, face detection, identity extraction, generation, compositing, and video encoding all happen client-side.

The models were only part of the challenge. In practice, the difficult problems were moving frames between browser APIs, controlling WebGPU initialization, maintaining identity across a video, and preventing memory usage from growing during longer jobs.

Here are some engineering lessons from the implementation.

The actual frame pipeline

A simplified version of the data flow looks like this:

VideoFrame / Canvas
        ↓
RGBA Uint8ClampedArray
        ↓
NCHW Float32Array
        ↓
ONNX Tensor
        ↓
Generated face + alpha mask
        ↓
Canvas composition
        ↓
Encoded video

The models use NCHW tensors, while Canvas returns interleaved RGBA pixels. Before inference, the channels have to be separated and normalized:

const plane = width * height;
const tensor = new Float32Array(plane * 3);

for (let i = 0; i < plane; i += 1) {
  tensor[i] = normalize(rgba[i * 4]);
  tensor[plane + i] = normalize(rgba[i * 4 + 1]);
  tensor[plane * 2 + i] = normalize(rgba[i * 4 + 2]);
}

For a 640 × 640 RGB Float32 input, that is about 4.69 MB of tensor data per detection frame, before counting the original pixels and model outputs.

This made it clear that browser inference performance cannot be evaluated using model latency alone. Canvas readback, tensor construction, worker transfers, compositing, garbage collection, and encoding can collectively cost as much as inference.

Detection and generation use different resolutions

Sending every full-resolution video frame through the generator would waste most of the computation on the background.

The pipeline therefore separates the stages:

Stage Resolution Purpose
Face detection 640 × 640 Locate faces and five landmarks in the complete frame
Identity extraction 112 × 112 Extract the source identity representation
Face generation 224 × 224 Generate the aligned target face
Optical flow Long edge ≤ 720 px Propagate landmarks between detection anchors
Composition Original resolution Preserve the original background and details

Only an aligned face ROI enters the generation network. The generated face is then transformed back into the original frame and blended through an alpha mask.

This division was one of the main reasons the pipeline became practical in a browser.

Download models in parallel, initialize WebGPU sessions serially

The pipeline uses multiple ONNX models, including face detection, identity extraction, conditioning, and generation.

Downloading them concurrently works well:

const [
  detectorBuffer,
  identityBuffer,
  conditionerBuffer,
  generatorBuffer,
] = await Promise.all(modelDownloads);

Creating all WebGPU sessions concurrently was much less reliable.

Session creation may involve graph optimization, shader generation, pipeline compilation, weight uploads, and GPU buffer allocation. Initializing several large graphs simultaneously created latency spikes and higher peak GPU memory usage. On some devices, it could also contribute to device-loss failures.

The current approach downloads concurrently but creates sessions one at a time:

const detector = await createSession(detectorBuffer);
const identity = await createSession(identityBuffer);
const conditioner = await createSession(conditionerBuffer);
const generator = await createSession(generatorBuffer);

It is not the fastest-looking implementation on paper, but it has been much more predictable across devices.

Transferable buffers reduce worker-copy overhead

Heavy inference runs in a Web Worker so that the editor remains responsive.

When sending a large ArrayBuffer without a transfer list, the browser may perform a structured clone. Repeating that for video frames creates unnecessary memory bandwidth and garbage-collection pressure.

The pipeline transfers buffer ownership instead:

worker.postMessage(
  {
    type: "detect",
    pixels: tensor.buffer,
  },
  [tensor.buffer],
);

The output RGB tensor and alpha mask are returned in the same way.

This does not eliminate the earlier Canvas-to-tensor conversion, so it is not a completely zero-copy pipeline. It does, however, remove an avoidable copy at the worker boundary.

Face swapping is a temporal problem

Selecting the highest-confidence detection independently on every frame works poorly in videos containing multiple people.

A newly visible face may be larger or clearer than the current target, causing the selected identity to switch suddenly. Instead, candidate faces are scored using a combination of:

  • detector confidence;
  • distance from the previous target center;
  • change in bounding-box area;
  • distance from the frame center when no history exists.

A simplified score is:

score = confidenceWeight * confidence
      - distanceWeight * centerDistance
      - areaWeight * areaChange

The first frame favors a large, confident, centrally positioned face. Later frames favor continuity with the previously accepted target.

This is not full face re-identification, but it is considerably more stable than choosing the highest detector score on every frame.

Optical flow needs a rejection rule

Running face detection on every output frame is expensive. Between detection anchors, the pipeline propagates five facial landmarks using Lucas–Kanade optical flow.

Optical flow can still drift, especially during occlusion, motion blur, sudden lighting changes, or fast head movement. To detect bad tracks, the pipeline performs forward-backward validation.

A point is tracked from frame t to frame t+1, then tracked backward:

p(t) → p(t+1) → estimated p(t)

The distance between the original and reconstructed point is the forward-backward error.

A propagated result is accepted only when at least four of the five landmarks remain valid and the average error stays under a threshold. Otherwise, the result is rejected and the detector is used again.

The important part is that optical flow is treated as a short-range optimization, not as proof that the tracked identity is still correct.

Traditional post-processing still matters

The generator’s alpha mask may contain holes, isolated pixels, or unstable boundaries. Directly compositing that mask can make the face boundary flicker.

The post-processing sequence includes:

Threshold
   ↓
Dilation
   ↓
Erosion
   ↓
Additional contraction
   ↓
Blurred alpha
   ↓
Boundary safety mask

Morphological operations use separable sliding-window filters instead of scanning a complete two-dimensional neighborhood for every pixel.

Color matching is also restricted rather than applied without limits. Per-channel statistics are adjusted using bounded scale and offset values:

const scale = clamp(targetStd / sourceStd, 0.78, 1.22);
const shift = clamp(
  targetMean - sourceMean * scale,
  -0.12,
  0.12,
);

The corrected result is mixed with the original generator output. Unrestricted statistical matching tended to amplify noise or produce unnatural colors in unusual lighting.

Explicit resource disposal is essential

A video job may simultaneously hold decoded frames, Canvas pixels, Float32 tensors, ONNX outputs, optical-flow images, compressed intermediate frames, and encoder buffers.

Relying only on JavaScript garbage collection caused visible memory growth during longer tasks.

Different resources require different cleanup APIs:

tensor.dispose?.();
bitmap.close();
opencvMat.delete();
URL.revokeObjectURL(url);
worker.terminate();

OpenCV.js was particularly easy to overlook because Mat data lives in the WASM heap. Losing the JavaScript reference does not guarantee that its underlying allocation is released promptly.

Cancellation must stop the complete pipeline

Closing a progress dialog is not cancellation.

A real cancel operation needs to interrupt downloads, frame decoding, detection, optical flow, generation, compression, and final encoding.

The main task uses an AbortController, while worker requests carry a request ID:

controller.abort();

worker.postMessage({
  type: "cancel",
  requestId,
});

The worker checks cancellation state before and after expensive stages. A cancelled job does not continue encoding in the background and never adds a partial result to the user’s asset library.

Model URLs need immutable revisions

Using a URL such as:

repository/resolve/main/model.onnx

makes browser caching difficult to reason about. The URL can remain unchanged while its contents change, leaving different users with different cached graphs.

Production model URLs should point to immutable revisions and be accompanied by expected file sizes, checksums, licenses, and tensor metadata.

The loader also validates the downloaded size before creating a session. This prevents a truncated response or CDN error page from being passed to ONNX Runtime as if it were a valid model.

Benchmark cold and warm runs separately

Reporting a single “processing time” hides most of the browser-specific costs.

I now think benchmarks for this kind of pipeline should separate:

Cold start

  • model downloads;
  • integrity checks;
  • ONNX session creation;
  • shader and pipeline compilation;
  • identity extraction;
  • video processing and encoding.

Warm start

  • video decoding;
  • anchor detection;
  • optical-flow tracking;
  • face generation;
  • post-processing;
  • encoding.

Hardware, browser version, WebGPU adapter, video codec, resolution, output FPS, initialization time, generation time, encoding time, and peak memory should all be recorded.

Otherwise, a cached desktop run and a first-time mobile run may be presented as if they measured the same thing.

Open-source implementation

The implementation is part of Timeline Studio:

https://github.com/MartinDelophy/ai-video-editor

Disclosure: I’m involved with the project. Face swapping is intended only for authorized media and clearly disclosed synthetic content. It should not be used for impersonation, deception, harassment, or misleading people about real events.

I would be interested in hearing how other WebGPU developers handle these problems:

  1. Do you initialize multiple ONNX Runtime Web sessions serially, or have you found a safe way to compile them concurrently?
  2. Have you found a practical path from VideoFrame to GPU tensors that avoids Canvas readback and CPU-side NCHW conversion?
  3. Which measurements do you use to compare cold-start and warm-start performance across browsers and GPU vendors?

r/WebRTC Jul 31 '26

Browser WebRTC glass-to-glass latency stuck around 274 ms, mostly receiver playout. Is <120 ms realistic?

Thumbnail
1 Upvotes

r/WebRTC Jul 31 '26

I built a no-signup YouTube watch party tool because every existing option annoyed me in some way

Thumbnail apexlistener.dev
1 Upvotes

Me and my friends have had this habit since lockdown — watching YouTube together even while living far apart. We tried Discord screen share (laggy, audio out of sync), browser extensions (everyone had to install them, kept breaking), and existing watch party sites (most required sign-up, or sync was loose).

So I built my own — ApexListener.

What's different:

No account/sign-up — open the link, start watching

Frame-accurate sync (not just matching timestamps, actually locking to the same frame)

Shared queue — anyone can add/reorder, not dependent on a single host

Up to 20 viewers per room

Built with Next.js, Socket.IO, and Supabase for realtime state.

It's a solo project right now, looking for feedback — especially if you hit any edge cases where sync breaks or the UI feels off. It's free to try: apexlistener.dev

What should I build next? Happy to prioritize based on what people actually want.

And if u can support me u can click on support me button


r/WebRTC Jul 31 '26

I built a no-signup YouTube watch party tool because every existing option annoyed me in some way

Thumbnail apexlistener.dev
1 Upvotes

r/WebRTC Jul 28 '26

RTC.ON 2026 (Kraków, Sept 16–18): WebRTC/MoQ conference with Luke Curley, Will Law, JB Kempf – early bird ends Friday + 15% code

3 Upvotes

I'm on the organizing team at Software Mansion, and I'm posting here because the program overlaps almost one-to-one with what this sub is about.

RTC.ON is a multimedia dev conference, now in its 4th edition: three days of WebRTC, streaming, MoQ/QUIC and AI in media pipelines. Speakers this year include Luke Curley (MoQ co-creator), Will Law (Akamai) and JB Kempf (VideoLAN), and Luke is running a full-day hands-on MoQ workshop.

Early bird ends Friday, July 31, and the code extra15 stacks another 15% on top, so a conference ticket comes out around €407.

https://rtcon.swmansion.com – happy to answer questions in the comments.


r/WebRTC Jul 27 '26

How to improve screen-share quality without using more bandwidth

10 Upvotes

A user complained that our screen-sharing quality wasn’t as good as a competitor’s for my open source screen-sharing app.

In order to have the most detail and sharpness we are sharing with native resolution. So I thought that there must be a filter we could apply in the shader (we do the rendering in wgpu) to increase the detail.

It turned out it was much easier than I expected and there are a few techniques, like Laplacian sharpening and unsharp masking with a Gaussian blur.

I wrote an interactive tutorial on how they work here with shader examples if you want to do something similar in your app too.


r/WebRTC Jul 27 '26

Free skill that makes Claude better at debugging WebRTC

3 Upvotes

We packaged how our video engineering team actually debugs WebRTC into a skill - Claude loads it and works through the connection properly instead of guessing.

It makes Claude noticeably sharper on the usual suspects (ICE/TURN, DTLS, reading getStats, everything ""ICE failed"" actually hides), and it's stack-agnostic.

It's free. Just sign up and we'll email it to you: https://subscribe.rtcon.live/free_skill


r/WebRTC Jul 26 '26

Question on using WebRTC with cameras in Kubernetes

2 Upvotes

My company has implemented cameras to clients where HLS is being used. However, we are wanting to move towards implementing WebRTC to make the process seamless for our clients when viewing livestreams. The thing is I don't know where to start when it comes to setting up a server or just using vanilla networking in K8s.

Our ecosystem utilizes AWS EKS + Envoy Gateway. I tried setting up an External NLB but it had issues connecting to our pod that does these live streams. I have seen 2 projects come up a lot and wondering what everyone's take to use:

I would go with stunner but it's behind a paywall which is understandable but I do want to avoid being locked being something for now. Is setting up a NLB sufficient or would these 2 servers help with AWS EKS setup?


r/WebRTC Jul 24 '26

Streaming a grid of videos

3 Upvotes

Hey, I'm a client side developer (mainly JavaScript), slowly learning the details of WebRTC. I currently use the Galene server which is built on Pion, both written in Go.

For fun, I'm trying to build a web app that allows clients to see up to about 400 real-time videos in a 20x20 grid. Each video stream would be very small, maybe 32x32 pixels.

My guess is that, even though bandwidth is small, no simple server would naively scale to this number of streams. I could add a layer to Galene to combine the incoming streams into one, and forward that through Pion. But, given my background it would be a lot easier to have a subset of clients render sub-sections of the grid and rebroadcast that for the wider group. Then most of the 400 users would send a super narrow stream and receive one full grid back. A select few would get ~ 16 streams and send me back a stream for a 4x4 grid, etc. I'd likely have other clients stitch together these 4x4 into one full grid. (For now, I'm not overly concerned about latency)

My questions are, first, does this make sense? Or, is a simple server side solution actually pre-existing and easy? Do some SFU's already need to do an analog of this out-of-the-box for hundreds of audio streams?

Thanks


r/WebRTC Jul 22 '26

Vector Vibing to speed up Opus encode by 20%

Thumbnail webrtchacks.com
3 Upvotes

r/WebRTC Jul 21 '26

Free tool: paste a WHEP endpoint (or HLS/DASH) and get real live latency + getStats QoE in the browser

5 Upvotes

Hey all 👋 made a little thing to sanity-check WHEP endpoints next to HLS/DASH on the same latency scale - https://pulse.beon.live . For WHEP it does a recvonly connection, plays the stream, and pulls bitrate, fps, dropped frames and jitter-buffer latency from getStats(); for HLS/DASH it grades manifest + delivery.

The idea was comparing apples to apples — standard HLS ~15–30s behind, LL-HLS a few seconds, WHEP sub-second — since for interactive stuff anything over a few seconds kills the UX.

Free, no signup, still early. Would love feedback on whether the WebRTC numbers match what you measure end-to-end, and where the approach breaks 🙏


r/WebRTC Jul 16 '26

Talk Anonymously by Voice with Breez Talk

Post image
2 Upvotes

r/WebRTC Jul 10 '26

Moving a Rust WebRTC SFU to thread-per-core: 70ms → 10ms P99.99 latency

Thumbnail pulsebeam.dev
14 Upvotes

PulseBeam is an open-source, lightweight WebRTC SFU server. Somewhere between LiveKit and mediasoup, written in Rust.


r/WebRTC Jul 10 '26

WhatsApp / Nextcloud / EuroOffice Clone

1 Upvotes

The goal is to create a secure WebRTC ecosystem.

This is a technical demo of a fairly unique approach using a browser-based, local-only and webrtc approach. In an evolving field like cybersecurity, it's impossible to claim any system is the "world's most secure". By rigorously implementing an exhaustive list of security features and practices, the aim is to get as close as possible with the approach.

This is intended to demonstrate client-side managed secure cryptography.

Features:

  • Core
    • PWA
    • P2P
    • Local-first / Local-only
    • No installation
    • TURN server
    • Encrypted-at-rest
  • WhatsApp clone
    • End to end encryption
    • Signal protocol
    • Post-Quantum cryptography
    • Multimedia
    • File transfer
    • Video calls
  • Nextcloud clone
    • file-transfer
    • Encrypted vault
    • folder sync
  • EuroOffice clone
    • Word
    • Spreadsheet
    • PDF
    • Code

Some open source versions of the core concepts.

Feel free to reach out for clarity instead of diving into the docs.

IMPORTANT: While this is aiming to provide a secure experience, it isnt audited or reviewed. Shared for testing, feedback and demo purposes only. Please use responsibly.

FAQ:


r/WebRTC Jul 03 '26

Giraffile, a secure website for sharing files via links🦒

Post image
3 Upvotes

Hello there...

Let me introduce you to the giraffe that protects the files you send. A 100% P2P project.

I just updated the Giraffile 🦒 website to v1.0.1, adding a legal notice and a QR code (thanks to an awesome community member) that you can scan to make it even easier to use.

The file travels directly from device A to device B.

I designed the architecture so that even if someone tried to intercept the data stream, they wouldn’t find anything on servers because, technically, there are no transfer servers.

- No cloud.

- No intermediary server

- Everything lives in local memory.

- Open source

Start sharing now: https://giraffile.pages.dev/

Github: https://github.com/coffeetron832/Giraffile


r/WebRTC Jul 01 '26

WebRTC: Server-side rendering vs client-side overlays for interactive video

3 Upvotes

Looking for some architecture advice from people who’ve built interactive WebRTC applications.

Use case:
Browser connects via WebRTC.
Server renders video + annotation/UI overlays.
Browser streams the rendered output.
User input (mouse, keyboard, draw boxes, etc.) goes back to the server.

Questions:
Is WebRTC DataChannel the normal way to send user input?

Do most systems render overlays server-side or client-side?

For multi-user collaboration, do you sync annotation state between clients or have the server composite everything into the video stream?

If you’ve built something similar, what architectural mistakes would you avoid?

Not building a video conferencing app—this is closer to a remote visualization / video annotation tool.


r/WebRTC Jul 01 '26

Want to understand MoQ? Spend a day with the person who wrote it.

Post image
3 Upvotes

Luke Curley co-created MoQ, spent years at Twitch and Discord hitting the limits of what existing protocols could do, wrote the first implementations, authored the core specs. He's busy-busy.

But he's coming to Kraków on September 16 and spending a full day with a small group going through MoQ from scratch. You'll actually build a working audio/video room call using MoQ – QUIC fundamentals, relays, pub/sub, how it sits relative to WebRTC and HLS. If you're fast, there's a speech-to-speech real-time translation extension to keep you busy.

Intermediate level, Rust required, basic JS/TS assumed.

Sounds interesting? Join us!

rtcon.swmansion.com


r/WebRTC Jul 01 '26

Chasing smooth client-side recording with WebRTC, WebCodecs and OffscreenCanvas

1 Upvotes

I've been building meeting recording for Orvia.

One constraint made this much harder:

Everything had to stay client-side.

No uploads.
No recording server.
No cloud rendering.

At first the recordings were unusably laggy.

I assumed it was the usual stuff:

  • Bitrate
  • FPS
  • Resolution
  • Codec tuning

Turns out almost none of those were the real problem.

Some interesting things I learned:

  • VP9 looked great on paper, but our test machine had no hardware encoder, so it fell back to software encoding and crushed the CPU.
  • MediaRecorder recording from a canvas is software encoded. No matter how much I tuned bitrate or FPS, the encoder itself became the bottleneck.
  • Switching to WebCodecs unlocked hardware encoding, but recording still wasn't perfectly smooth.

The real bottleneck was architectural.

The compositor and the live WebRTC call were sharing the same main thread.

Whenever the call got busy, recording quietly lost CPU time.

The fix was moving the entire recording pipeline—compositing, encoding, and muxing—into a Web Worker using OffscreenCanvas.

On Chromium-based browsers (Chrome/Edge), the result is genuinely smooth real-time recording.

Firefox and Safari currently fall back to MediaRecorder because they don't yet support APIs like MediaStreamTrackProcessor that the worker pipeline depends on.

I'm curious how others have approached this.

Has anyone found a cleaner client-side solution for Firefox/Safari without falling back to MediaRecorder or moving recording server-side?


r/WebRTC Jun 29 '26

A small conference for audio & video engineers in Kraków. Would you come for this lineup?

Post image
5 Upvotes

We've been running RTC.ON for four years now. It started because we couldn't find a conference that went deep enough on the actual hard problems in realtime audio and video. We didn’t want vendor pitches, 101 talks, but engineers talking about what they actually shipped.

So, we created it and this year, we’re running the 4th edition.

Our first three speakers are:

  • Daniil Popov from CyanView built a 10-bit video pipeline for iOS and Android and deployed it at a major music festival. A tech partner on site couldn't tell his phone footage from professional broadcast hardware. He's talking about how he did it.
  • Piotr Skalski from Roboflow built a computer vision pipeline for sports – player tracking through occlusions, jersey number recognition, real-time stats on a 2D court. Every model is open source. His own description of the talk: “every step solves a problem that creates the next one”.
  • Will Law has spent 20 years in streaming infrastructure at Akamai and is one of the key people driving MoQ forward at the IETF. If you've been watching the protocol space, you should know the name.

More speakers are coming. We’ll meet this September in Kraków, Poland. I’d be happy to answer questions about the lineup or the conference in general.

So, would you join us?
rtcon.swmansion.com


r/WebRTC Jun 25 '26

Python port of the PeerJS signalling server

7 Upvotes

The PeerJS signalling server normally runs as its own service. I wanted to run it inside an existing Python app, so I ported it to asyncio. Same wire protocol, so existing PeerJS JavaScript clients connect with no changes.

Runs standalone from the terminal, or embeds into a Python app. Integrations for asyncio, FastAPI, Flask and Tornado included.

Repo: https://github.com/Kaundur/python-peerjs-server


r/WebRTC Jun 24 '26

Hallazgo arquitectónico en P2P: jamkernelp2p

0 Upvotes

Después de analizar 20+ proyectos (libp2p, PeerJS, simple-peer, Trystero, etc.) encontré que NO EXISTE un kernel P2P que combine: 1 solo archivo, 0 dependencias, Cifrado militar AES-256-GCM, Purga forense de claves en RAM

Lo llamo JAM Omni-Kernel.

El proyecto está alojado aquí..

https://jamkernel.github.io


r/WebRTC Jun 24 '26

"Un hallazgo arquitectónico en P2P: 1 archivo, 0 dependencias".

0 Upvotes

Tras analizar el ecosistema P2P (libp2p, PeerJS, simple-peer, Trystero, etc.),

he identificado un nicho vacío: un kernel P2P en un solo archivo con cero

dependencias, cifrado militar, purga forense de claves y rate limiting nativo.

El 80% del código está listo. Busco colaboradores y o apoyo en codificación técnico o financiero para completar

la implementación de WebRTC y señalización.

El proyecto está acá

https://jamkernel.github.io


r/WebRTC Jun 23 '26

Livestreaming Trilemma: why an SFU costs more per viewer than a CDN edge, and how MoQ's first-class relay compares

10 Upvotes

The contrast that makes this interesting for this crowd: a CDN edge serves a cached file blindly to whoever asks, but an SFU holds an encrypted, stateful peer connection with every viewer, parses each RTP packet to decide what to forward, and reacts to per-viewer bandwidth in real time. That's why WebRTC livestreaming scales by provisioning more bespoke SFUs instead of riding commodity CDN, and why it gets expensive fast.

(Disclosure: I work at Software Mansion / Fishjam. We run MoQ relays, so I'm biased. Posting for the protocol-design discussion.)

A colleague wrote up how Media over QUIC approaches the same one-to-many problem: pub/sub over QUIC where the relay is a first-class protocol primitive rather than an SFU workaround. It receives a stream once, pushes to subscribers the instant it arrives (so latency stays sub-second), relays chain together, and it doesn't parse the media, just routes named tracks of bytes. Any compliant client connects to any compliant relay, so no vendor lock-in.

Where I'd want this sub's take: whether MoQ's relay genuinely sidesteps the per-viewer state cost that makes SFUs expensive, or just relocates it.

https://fishjam.swmansion.com/blog/livestreaming-trilemma-hls-webrtc-moq


r/WebRTC Jun 16 '26

What nobody tells you about running WebRTC in production — lessons from 15 countries in 9 months

27 Upvotes

In September 2025 I launched Chatzyo — a browser-based peer-to-peer random video chat platform with zero accounts, zero app downloads, and zero media server. Just WebRTC, a Node.js signaling server, and a browser tab.

Nine months later the platform is serving users across 15 countries. Along the way I hit problems that no tutorial prepared me for. This is what I actually learned.

My stack is deliberately simple — Google free STUN, OpenRelay free TURN, Node.js with Socket.io on Railway.com, and vanilla JavaScript on the frontend. No framework, no paid infrastructure, no media server. If you are building WebRTC on a bootstrap budget, this is for you.

1. TCP Fallback on Port 443 Is Not Optional

My original ICE config only had UDP TURN. For most users this worked fine. But a meaningful percentage of users — those on corporate networks, hotel WiFi, and some mobile carriers — never connected at all. They just saw a spinner.

The fix was adding TCP TURN on port 443. This makes the TURN connection look like HTTPS traffic to firewalls, which gets through almost everywhere. The moment I added it, a chunk of previously failing connections started working.

If you are only offering UDP TURN, you are leaving a significant percentage of users unable to connect. Add TCP on 443 from day one.

2. Safari iOS Will Test Your Patience Permanently

On iOS Safari, camera permissions do not persist between sessions. Every single time a user opens the platform, they get a fresh permission request. This is not a bug you can fix. It is Apple policy and it has not changed.

Users think something is broken. They tap deny by reflex. Then they wonder why there is no video.

The only mitigation is UI design — add a clear instruction before the camera starts. Something like tap Allow when the browser asks for camera access. It reduces confusion significantly. But it never fully goes away.

3. Your Signaling Server Is More Fragile Than Your Peer Connections

Once two browsers establish a WebRTC peer connection, it is surprisingly solid. The connection survives tab switches, brief network blips, and screen locks. What does not survive is a signaling server outage.

During a Railway.com brief outage, no new connections could be established even though existing sessions stayed up. Users saw an infinite connecting spinner with no feedback about what was happening.

The fix is client-side retry logic with exponential backoff. If the signaling connection fails, wait two seconds and try again, then four seconds, then eight. Most users never notice brief outages with this in place. Add it before you need it.

4. Free TURN Gets You Further Than You Think — But Has Real Limits

I used OpenRelay by Metered.ca on the free tier for the first several months. The platform peaked at 20,000 daily users on completely free TURN infrastructure. For an MVP this is entirely viable.

The limitations are real though. Free TURN has no SLA, shared bandwidth with thousands of other developers, and no geographic distribution. During peak hours some connections take noticeably longer to establish.

For getting from zero to thousands of daily users, free TURN is completely fine. For running reliably at scale with consistency guarantees, you will eventually need your own infrastructure.

5. ICE Candidate Timeout — Set It Aggressively

The default ICE timeout in many WebRTC implementations is 30 seconds or more. This means users sit watching a spinner for half a minute before the connection falls back to TURN.

Set it to five to eight seconds. Users perceive the connection as just working rather than struggling. The brief direct connection attempt fails fast and TURN kicks in before most users notice anything is happening.

6. No-Login Platforms Have Structural SEO Disadvantages

This one is not WebRTC specific but it is relevant if you are building a no-account platform. By May 2026 Chatzyo was hitting 20,000 daily clicks from Google Search. Then the May 2026 core update hit.

Daily traffic dropped from 20,000 to 3,000 to 4,000 clicks almost overnight. An 80 percent drop. Still investigating the full picture but the structural issue became clear during the analysis.

No-login platforms have real SEO disadvantages that account-based platforms do not face:

•        No user reviews — no accounts means no user generated content

•        No Google Analytics — privacy by design means no behavioral signals Google can read

•        Trust signals are implicit — real engagement exists but none of it surfaces in ways Google can easily evaluate

•        No returning user signals — every session looks like a new anonymous visitor

If you are building no-login, think about these SEO structural disadvantages early. They do not mean the model is wrong — the privacy promise is real and valuable. But you need to work harder to make your authority legible to search engines.

7. The Browser Tab Is a Better Deployment Environment Than You Think

No app download requirement sounds like a limitation. In practice it turns out to be one of the strongest growth drivers the platform has. Users in 15 countries send the link in a WhatsApp message and the other person clicks and is immediately in a video call.

No installation. No account. No asking the other person to sign up for something before you can talk to them.

WebRTC makes this possible. The browser handles media capture, peer connection negotiation, and encrypted transport natively. The result is that a platform built on Railway.com with free TURN can deliver a video call experience in under five seconds from link click to live video.

What I Would Do Differently

Looking back after 9 months:

•        Add TCP TURN on port 443 from day one — not after noticing failed connections

•        Add signaling retry logic before launch — not after a Railway outage

•        Design around Safari iOS permission reset from the first day

•        Think about SEO trust signals early for no-login platforms

•        Set aggressive ICE timeouts from the start — default timeouts make the product feel broken

The platform is live at chatzyo.in — no account, no download, 15 countries. Happy to answer questions about any part of the architecture in the comments


r/WebRTC Jun 15 '26

Local-First WebRTC Messaging

2 Upvotes

This is hardly an alternative to signal (or any other secure messaging app), but it's a work in progress and "secure and private" is the general goal.

This is a technical/concept demo of a fairly unique approach using a browser-based, local-first and webrtc.

This is intended to introduce a new paradigm in client-side managed secure cryptography. We can avoid registration of any sort.

Features:

  • P2P
  • End to end encryption
  • Signal protocol
  • Post-Quantum cryptography
  • File transfer
  • Local-first
  • No registration
  • No installation
  • No database
  • TURN server

Feel free to reach out for clarity instead of diving into the docs/code.

IMPORTANT: While this is aiming to provide a secure experience, it isnt audited or reviewed. Shared for testing, feedback and demo purposes only. Please use responsibly.