Group calls that stay in the group.
soli-sfu forwards WebRTC audio and video between the people in a conversation, and nobody else. One Rust binary, one UDP port, no transcoding.
curl -sSL https://raw.githubusercontent.com/solisoft/sfu/main/install.sh | sh
Each colour is one conversation. A speaker uploads once, and the SFU sends the packet back out only to that speaker's circle. When Noor walks into the meeting room, the app updates her peers and she starts hearing Jo and Elif.
What happens when someone joins
Four steps, one HTTPS round trip, and the browser never renegotiates afterwards.
-
Your app signs a token
The Soli app mints a short-lived token naming the user and the room, with the HMAC builtins it already has. The SFU keeps no user database.
-
The browser sends one offer
One peer connection: the microphone, a video sender kept for later, and a fixed set of receive slots, posted to
/v1/sessions. -
Media goes straight to the port
The answer names a single address. The browser reaches it over UDP directly, with no STUN or TURN servers to run.
-
The group decides who hears whom
When the conversation changes shape, the app sends the new
peerslist. It applies to the next packet, without a media gap.
const pc = new RTCPeerConnection();
pc.addTransceiver(mic, { direction: 'sendonly' });
const cam = pc.addTransceiver('video', { direction: 'sendonly' });
for (let i = 0; i < 8; i++) pc.addTransceiver('audio', { direction: 'recvonly' });
for (let i = 0; i < 4; i++) pc.addTransceiver('video', { direction: 'recvonly' });
await pc.setLocalDescription(await pc.createOffer());
const res = await fetch(SFU + '/v1/sessions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, sdp_offer: pc.localDescription.sdp, peers }),
});
const { session_id, sdp_answer } = await res.json();
await pc.setRemoteDescription({ type: 'answer', sdp: sdp_answer });PATCH /v1/sessions/3
{ "token": "sfu1.…", "peers": ["jo", "elif", "noor"] }
204 No ContentThree decisions that keep it small
soli-sfu is about two thousand lines of Rust because each of these removes a whole category of machinery.
- One port for everyone
- ICE-lite on a single UDP socket. You open one firewall rule; peers are told apart by their STUN username and source address.
- Slots instead of renegotiation
- Each browser reserves 8 audio and 4 video receive slots up front. The SFU rebinds them as people start and stop talking.
- Groups, not rooms
- Media reaches only the user ids in each listener's
peers. One office of forty can hold ten separate conversations.
Why not a peer-to-peer mesh
In a mesh, everyone uploads their stream once per listener, and a home connection gives up at four or five people. Through an SFU, everyone uploads exactly once.
| Group size | Uploads per person, mesh | Through soli-sfu |
|---|---|---|
| 2 | ||
| 4 | ||
| 6 | ||
| 8 | ||
| 10 |
What it leaves out on purpose
Each of these would multiply the code. None is needed for conversations of two to eight people.
- No simulcast or congestion control. Cap bitrates in the browser, around 64 kbps for audio and 600 kbps for video.
- No TCP or TURN fallback. Networks that block UDP outright will not connect.
- Slots cap what one person receives. 8 voices and 4 cameras by default: right for conversations, wrong for an all-hands broadcast.
- State lives in memory. A restart drops live calls, and clients rejoin on their own.
Run it next to your proxy, open one UDP port, and your calls scale past the mesh.
Install soli-sfu