soli-sfu

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.

  1. 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.

  2. 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.

  3. 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.

  4. The group decides who hears whom

    When the conversation changes shape, the app sends the new peers list. 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 Content

Three 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 sizeUploads per person, meshThrough soli-sfu
2
1
1
4
3
1
6
5
1
8
7
1
10
9
1

What it leaves out on purpose

Each of these would multiply the code. None is needed for conversations of two to eight people.

Run it next to your proxy, open one UDP port, and your calls scale past the mesh.

Install soli-sfu