How it works
Two peers join the same room through a small WebSocket signaling server. The server only relays control messages: the SDP offer and answer, ICE candidates and the E2EE key. Audio, video and chat go directly between the peers over WebRTC. Google's public STUN server helps each peer find its public address.
That is the default mode: a room holds two people. Group calls are an optional mode where everyone connects to a small SFU server instead.
The pieces
| Piece | Tech | Start reading at |
|---|---|---|
| Signaling server | Node.js, ws | server.js |
| SFU server (optional) | Go, Pion | sfu-server/ |
| Web | Vue 3, TypeScript, Vite | useCall.ts |
| Android | Kotlin, Jetpack Compose, webrtc-sdk | PeerConnectionClient.kt |
| iOS | Swift, SwiftUI, webrtc-sdk | PeerConnectionClient.swift |
| macOS | Swift, SwiftUI + AppKit, the iOS engine | WebRTCDemoMacApp.swift |
| Windows | C#, WinUI 3, libwebrtc through a C shim | CallViewModel.cs |
- UI
- Vue 3, Tailwind CSS, shadcn-vue
- WebRTC
- The browser's own RTCPeerConnection
- Signaling socket
- WebSocket
- Camera
- getUserMedia()
- Screen sharing
- getDisplayMedia()
- Switching sources
- RTCRtpSender.replaceTrack()
- E2EE
- Insertable Streams or RTCRtpScriptTransform, in a worker
- Backgrounds and stickers
- MediaPipe ImageSegmenter + FaceLandmarker, canvas 2D
- Picture-in-picture
- Document Picture-in-Picture API, video PiP fallback
Rules every client follows
These few decisions are what let five independent apps call each other. Each one has its own page.
- The peer already in the room makes the offer. The one who joins second answers. Nobody ever offers at the same time. Signaling
- The chat channel is created before the first offer, so opening the chat never renegotiates. Chat
- Switching between camera, screen and file never renegotiates. The video sender stays, only what feeds it changes. Switching video sources
- Camera state travels as a message. A disabled track still sends black frames, so each side tells the other with
media state, 300 ms before or after the track changes. Camera and mic state - E2EE uses one frame format and one set of key options, and VP8 goes first when it is on. End-to-end encryption
- No reconnect. If the signaling socket closes, the call ends. Signaling
The full repository layout, with every file and what it does, is in ARCHITECTURE.md.
