Case-study template — prose is true, ⟨bracketed⟩ numbers are placeholders · preview home

← All work

Group video calling on an SFU

Turning one-to-one WebRTC into group calls that actually show everyone.

VMeeta · Jamtech Technology · 2026 · React Native · mediasoup · Socket.io · Node

Overview

VMeeta is a social app — feed, reels, chat, and calling — built with React Native against a Node and MongoDB backend. I own the media layer and the signalling that sits under calling, plus the feed ranking.

The problem

One-to-one calls worked. They used plain peer-to-peer WebRTC: two devices, one connection, no server in the media path. Group calls were built later and reused a shared-room SFU that had been designed for broadcasting — one publisher, many watchers.

That assumption is invisible with two people and fatal with three. The room accepted a single producer, so the first participant to publish was the only one anybody could see. Everyone else got a tile that stayed black. The call connected, the audio sometimes worked, and the product looked broken.

Constraints

Mobile clients on Indian mobile data, where a connection drops for a few seconds and comes back several times in a call. One VPS, no budget for a TURN fleet or a second media node. And a hard rule: one-to-one calling was working in production and could not regress while I changed the group path.

What I did

Moved the room from one shared producer to per-user producer streams. Every participant publishes their own audio and video track to the SFU, and the SFU forwards each track to every other participant in the room. Tiles stop competing for a single slot, because there isn’t one.

That change surfaced a second problem. With per-user streams, a participant reconnecting needs their transports rebuilt, not just their socket restored. I added transport recovery and rejoin-on-reconnect: when a client comes back, it re-announces itself, the roster is reconciled, and consumers are recreated for whoever is still in the room. Call lifecycle is driven off the roster rather than off media state, so a brief media stall no longer ends a call.

client A client B node api mediasoup sfu peers ×N ws roster rtp forward
solid = media over RTP · dashed = signalling and roster state

The tradeoff

Per-user producers multiply server egress roughly N× per room — every track has to be forwarded to every other participant. I accepted that because rooms are capped at a small size and one VPS can carry it. Past that cap it needs simulcast so the SFU can drop to a lower layer per consumer instead of shipping full quality to everyone.

Screens

Group audio and video calling screen
Calling. Tiles are ordered off the roster, and the local user is pinned so a black remote tile is diagnosable.
Real-time chat with attachments
Chat. Attachments upload over HTTP, not the socket — binaries would block the signalling channel.
Personalised ranked feed
Feed. Ranked, not chronological; classification runs out of band so the read path stays fast.
Short video reels
Reels, with watch-time recorded as a ranking signal.
User profile
Profile.
Settings menu
Settings.

Outcome

Group calls show every participant, and they survive reconnects instead of quietly ending. Black-tile reports went to ⟨n⟩ over ⟨n⟨ weeks of device testing, with rooms of up to ⟨n⟩ people.

What I’d do differently

Two things. Simulcast from the start, so bandwidth adapts per consumer rather than being capped by room size. And structured logging on day one — diagnosing transport failures across a phone, a signalling server and a media server without correlated logs cost me days I didn’t need to spend.


Resume ayushm185@gmail.com More work

Open to full-stack / backend roles — remote or relocation