Backend Communication
Current:
WebRTC
What is WebRTC?
WebRTC (Web Real-Time Communication) enables peer-to-peer audio, video, and data sharing directly between browsers and native applications. Unlike traditional client-server models, WebRTC allows clients to communicate directly with each other after establishing a connection through a signaling server.
Key characteristics:
- Peer-to-peer - Direct communication between clients
- Low latency - Minimal delay for real-time media
- NAT traversal - STUN/TURN servers handle firewall traversal
- Encryption - Built-in DTLS/SRTP security
- Multiple data types - Audio, video, and arbitrary data channels
Architecture Overview
sequenceDiagram
participant A as Peer A
participant S as Signaling Server
participant STUN as STUN Server
participant TURN as TURN Server
participant B as Peer B
Note over A,B: Discovery & Signaling
A->>S: Create Offer (SDP)
S->>B: Forward Offer
B->>S: Create Answer (SDP)
S->>A: Forward Answer
Note over A,B: ICE Candidate Exchange
A->>STUN: Discover public IP
STUN-->>A: Your public address
A->>S: ICE Candidate
S->>B: Forward Candidate
B->>STUN: Discover public IP
STUN-->>B: Your public address
B->>S: ICE Candidate
S->>A: Forward Candidate
rect rgb(200, 220, 240)
Note over A,B: Direct P2P Connection
A<-->B: Media/Data (DTLS/SRTP)
end
rect rgb(240, 220, 200)
Note over A,TURN,B: Fallback via TURN
A<-->TURN: Relay
TURN<-->B: Relay
end
WebRTC Connection Lifecycle
- Signaling - Exchange connection metadata (SDP offers/answers)
- ICE Gathering - Discover network candidates
- ICE Checking - Test connectivity between candidates
- Connection - Establish peer-to-peer connection
- Communication - Exchange media and data
- Disconnection - Clean up resources
Real-World Use Cases
- Video Conferencing - Zoom, Google Meet, Microsoft Teams
- Live Streaming - Twitch-style broadcasts with low latency
- File Sharing - P2P file transfer (ShareDrop, Snapdrop)
- Screen Sharing - Remote collaboration tools
- Online Gaming - Low-latency multiplayer games
- IoT Communication - Direct device-to-device communication
- Telemedicine - Doctor-patient video consultations
Implementation in Go with Pion
Project Structure
webrtc-server/
├── main.go
├── signaling/
│ ├── server.go
│ └── room.go
├── webrtc/
│ ├── peer.go
│ └── sfu.go
├── static/
│ └── index.html
└── go.mod
Dependencies
go.mod
module webrtc-server
go 1.21
require (
github.com/gorilla/websocket v1.5.1
github.com/pion/webrtc/v3 v3.2.40
github.com/pion/ice/v2 v2.3.11
)
1. WebRTC Peer Connection
webrtc/peer.go
package webrtc
import (
"encoding/json"
"fmt"
"log"
"sync"
"github.com/pion/webrtc/v3"
)
// Peer represents a WebRTC peer connection
type Peer struct {
ID string
PC *webrtc.PeerConnection
DataChannel *webrtc.DataChannel
mu sync.RWMutex
OnICE func(candidate *webrtc.ICECandidate)
OnTrack func(track *webrtc.TrackRemote)
OnData func(msg []byte)
}
// Config for WebRTC
var webrtcConfig = webrtc.Configuration{
ICEServers: []webrtc.ICEServer{
{
URLs: []string{"stun:stun.l.google.com:19302"},
},
// Add TURN server for production
// {
// URLs: []string{"turn:turn.example.com:3478"},
// Username: "username",
// Credential: "password",
// },
},
}
// NewPeer creates a new WebRTC peer connection
func NewPeer(id string) (*Peer, error) {
// Create media engine
mediaEngine := &webrtc.MediaEngine{}
if err := mediaEngine.RegisterDefaultCodecs(); err != nil {
return nil, err
}
// Create API with media engine
api := webrtc.NewAPI(webrtc.WithMediaEngine(mediaEngine))
// Create peer connection
pc, err := api.NewPeerConnection(webrtcConfig)
if err != nil {
return nil, err
}
peer := &Peer{
ID: id,
PC: pc,
}
// Setup ICE candidate handler
pc.OnICECandidate(func(candidate *webrtc.ICECandidate) {
if candidate != nil && peer.OnICE != nil {
peer.OnICE(candidate)
}
})
// Handle connection state changes
pc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
log.Printf("Peer %s: Connection state changed to %s", id, state.String())
})
// Handle ICE connection state
pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
log.Printf("Peer %s: ICE state changed to %s", id, state.String())
if state == webrtc.ICEConnectionStateFailed {
log.Printf("Peer %s: ICE connection failed", id)
}
})
// Handle incoming tracks
pc.OnTrack(func(track *webrtc.TrackRemote, receiver *webrtc.RTPReceiver) {
log.Printf("Peer %s: Received track: %s (type: %s)", id, track.ID(), track.Kind())
if peer.OnTrack != nil {
peer.OnTrack(track)
}
})
return peer, nil
}
// CreateOffer creates an SDP offer
func (p *Peer) CreateOffer() (*webrtc.SessionDescription, error) {
p.mu.Lock()
defer p.mu.Unlock()
offer, err := p.PC.CreateOffer(nil)
if err != nil {
return nil, err
}
if err = p.PC.SetLocalDescription(offer); err != nil {
return nil, err
}
return &offer, nil
}
// CreateAnswer creates an SDP answer
func (p *Peer) CreateAnswer() (*webrtc.SessionDescription, error) {
p.mu.Lock()
defer p.mu.Unlock()
answer, err := p.PC.CreateAnswer(nil)
if err != nil {
return nil, err
}
if err = p.PC.SetLocalDescription(answer); err != nil {
return nil, err
}
return &answer, nil
}
// SetRemoteDescription sets remote SDP
func (p *Peer) SetRemoteDescription(sdp webrtc.SessionDescription) error {
p.mu.Lock()
defer p.mu.Unlock()
return p.PC.SetRemoteDescription(sdp)
}
// AddICECandidate adds ICE candidate
func (p *Peer) AddICECandidate(candidate webrtc.ICECandidateInit) error {
p.mu.Lock()
defer p.mu.Unlock()
return p.PC.AddICECandidate(candidate)
}
// CreateDataChannel creates a data channel
func (p *Peer) CreateDataChannel(label string) error {
p.mu.Lock()
defer p.mu.Unlock()
dc, err := p.PC.CreateDataChannel(label, nil)
if err != nil {
return err
}
p.DataChannel = dc
// Setup data channel handlers
dc.OnOpen(func() {
log.Printf("Peer %s: Data channel '%s' opened", p.ID, label)
})
dc.OnMessage(func(msg webrtc.DataChannelMessage) {
log.Printf("Peer %s: Received data: %s", p.ID, string(msg.Data))
if p.OnData != nil {
p.OnData(msg.Data)
}
})
dc.OnClose(func() {
log.Printf("Peer %s: Data channel '%s' closed", p.ID, label)
})
return nil
}
// SendData sends data over data channel
func (p *Peer) SendData(data []byte) error {
p.mu.RLock()
defer p.mu.RUnlock()
if p.DataChannel == nil {
return fmt.Errorf("data channel not initialized")
}
return p.DataChannel.Send(data)
}
// AddTrack adds media track
func (p *Peer) AddTrack(track *webrtc.TrackLocalStaticRTP) (*webrtc.RTPSender, error) {
p.mu.Lock()
defer p.mu.Unlock()
return p.PC.AddTrack(track)
}
// Close closes the peer connection
func (p *Peer) Close() error {
p.mu.Lock()
defer p.mu.Unlock()
if p.DataChannel != nil {
p.DataChannel.Close()
}
return p.PC.Close()
}
2. Signaling Server with WebSocket
signaling/server.go
package signaling
import (
"encoding/json"
"log"
"net/http"
"sync"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true // In production, validate origin
},
}
// MessageType represents signaling message types
type MessageType string
const (
TypeOffer MessageType = "offer"
TypeAnswer MessageType = "answer"
TypeCandidate MessageType = "candidate"
TypeJoin MessageType = "join"
TypeLeave MessageType = "leave"
TypePeers MessageType = "peers"
)
// Message represents a signaling message
type Message struct {
Type MessageType `json:"type"`
From string `json:"from,omitempty"`
To string `json:"to,omitempty"`
Room string `json:"room,omitempty"`
Data json.RawMessage `json:"data,omitempty"`
}
// Client represents a signaling client
type Client struct {
ID string
Conn *websocket.Conn
Room *Room
Send chan *Message
}
// Room represents a signaling room
type Room struct {
ID string
Clients map[string]*Client
mu sync.RWMutex
}
// Server manages signaling
type Server struct {
rooms map[string]*Room
mu sync.RWMutex
}
// NewServer creates a new signaling server
func NewServer() *Server {
return &Server{
rooms: make(map[string]*Room),
}
}
// HandleWebSocket handles WebSocket connections
func (s *Server) HandleWebSocket(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Printf("Upgrade error: %v", err)
return
}
client := &Client{
Conn: conn,
Send: make(chan *Message, 256),
}
go client.writePump()
client.readPump(s)
}
func (c *Client) readPump(s *Server) {
defer func() {
if c.Room != nil {
c.Room.Leave(c)
}
c.Conn.Close()
}()
for {
var msg Message
err := c.Conn.ReadJSON(&msg)
if err != nil {
if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway, websocket.CloseAbnormalClosure) {
log.Printf("WebSocket error: %v", err)
}
break
}
msg.From = c.ID
s.handleMessage(c, &msg)
}
}
func (c *Client) writePump() {
for msg := range c.Send {
if err := c.Conn.WriteJSON(msg); err != nil {
log.Printf("Write error: %v", err)
return
}
}
}
func (s *Server) handleMessage(client *Client, msg *Message) {
switch msg.Type {
case TypeJoin:
var data struct {
Room string `json:"room"`
UserID string `json:"userId"`
}
if err := json.Unmarshal(msg.Data, &data); err != nil {
log.Printf("Invalid join data: %v", err)
return
}
client.ID = data.UserID
room := s.getOrCreateRoom(data.Room)
room.Join(client)
case TypeOffer, TypeAnswer, TypeCandidate:
// Forward to specific peer
if client.Room != nil && msg.To != "" {
client.Room.SendTo(msg.To, msg)
}
case TypeLeave:
if client.Room != nil {
client.Room.Leave(client)
}
}
}
func (s *Server) getOrCreateRoom(id string) *Room {
s.mu.Lock()
defer s.mu.Unlock()
room, exists := s.rooms[id]
if !exists {
room = &Room{
ID: id,
Clients: make(map[string]*Client),
}
s.rooms[id] = room
log.Printf("Created room: %s", id)
}
return room
}
// Room methods
func (r *Room) Join(client *Client) {
r.mu.Lock()
defer r.mu.Unlock()
client.Room = r
r.Clients[client.ID] = client
log.Printf("Client %s joined room %s (total: %d)", client.ID, r.ID, len(r.Clients))
// Send list of existing peers
peers := make([]string, 0, len(r.Clients))
for id := range r.Clients {
if id != client.ID {
peers = append(peers, id)
}
}
peersData, _ := json.Marshal(map[string]interface{}{
"peers": peers,
})
client.Send <- &Message{
Type: TypePeers,
Data: peersData,
}
// Notify others
r.broadcastExcept(client.ID, &Message{
Type: TypeJoin,
From: client.ID,
})
}
func (r *Room) Leave(client *Client) {
r.mu.Lock()
defer r.mu.Unlock()
delete(r.Clients, client.ID)
close(client.Send)
log.Printf("Client %s left room %s (remaining: %d)", client.ID, r.ID, len(r.Clients))
// Notify others
r.broadcastExcept(client.ID, &Message{
Type: TypeLeave,
From: client.ID,
})
}
func (r *Room) SendTo(clientID string, msg *Message) {
r.mu.RLock()
defer r.mu.RUnlock()
if client, exists := r.Clients[clientID]; exists {
select {
case client.Send <- msg:
default:
log.Printf("Client %s send buffer full", clientID)
}
}
}
func (r *Room) broadcastExcept(exceptID string, msg *Message) {
for id, client := range r.Clients {
if id != exceptID {
select {
case client.Send <- msg:
default:
log.Printf("Client %s send buffer full", id)
}
}
}
}
3. Main Server Application
main.go
package main
import (
"log"
"net/http"
"webrtc-server/signaling"
)
func main() {
// Create signaling server
sigServer := signaling.NewServer()
// Setup routes
mux := http.NewServeMux()
// WebSocket signaling endpoint
mux.HandleFunc("/ws", sigServer.HandleWebSocket)
// Serve static files
mux.Handle("/", http.FileServer(http.Dir("./static")))
// Health check
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte("OK"))
})
log.Println("WebRTC signaling server starting on :8080")
log.Println("Open http://localhost:8080 in your browser")
if err := http.ListenAndServe(":8080", mux); err != nil {
log.Fatal(err)
}
}
4. Browser Client
static/index.html
<!DOCTYPE html>
<html>
<head>
<title>WebRTC Demo</title>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
video { width: 400px; height: 300px; background: #000; }
.container { display: flex; gap: 20px; }
.controls { margin: 20px 0; }
button { padding: 10px 20px; margin: 5px; }
#messages { border: 1px solid #ccc; padding: 10px; height: 200px; overflow-y: auto; }
</style>
</head>
<body>
<h1>WebRTC Peer-to-Peer Demo</h1>
<div class="controls">
<input type="text" id="roomId" placeholder="Room ID" value="room1">
<input type="text" id="userId" placeholder="Your User ID" value="user1">
<button onclick="joinRoom()">Join Room</button>
<button onclick="startVideo()">Start Video</button>
<button onclick="stopVideo()">Stop Video</button>
</div>
<div class="container">
<div>
<h3>Local Video</h3>
<video id="localVideo" autoplay muted></video>
</div>
<div>
<h3>Remote Video</h3>
<video id="remoteVideo" autoplay></video>
</div>
</div>
<h3>Data Channel Messages</h3>
<div class="controls">
<input type="text" id="messageInput" placeholder="Type a message">
<button onclick="sendMessage()">Send</button>
</div>
<div id="messages"></div>
<script>
let ws;
let peerConnection;
let localStream;
let dataChannel;
let roomId;
let userId;
const peers = {};
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' }
]
};
function joinRoom() {
roomId = document.getElementById('roomId').value;
userId = document.getElementById('userId').value;
ws = new WebSocket('ws://localhost:8080/ws');
ws.onopen = () => {
console.log('Connected to signaling server');
ws.send(JSON.stringify({
type: 'join',
data: { room: roomId, userId: userId }
}));
};
ws.onmessage = async (event) => {
const msg = JSON.parse(event.data);
await handleSignalingMessage(msg);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
console.log('Disconnected from signaling server');
};
}
async function handleSignalingMessage(msg) {
console.log('Received:', msg);
switch (msg.type) {
case 'peers':
// Create offers to all existing peers
const peers = JSON.parse(msg.data).peers;
for (const peerId of peers) {
await createPeerConnection(peerId, true);
}
break;
case 'join':
// New peer joined - wait for their offer
console.log('Peer joined:', msg.from);
break;
case 'offer':
await handleOffer(msg);
break;
case 'answer':
await handleAnswer(msg);
break;
case 'candidate':
await handleCandidate(msg);
break;
case 'leave':
handleLeave(msg);
break;
}
}
async function createPeerConnection(peerId, createOffer) {
const pc = new RTCPeerConnection(config);
peers[peerId] = pc;
// Add local tracks
if (localStream) {
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
}
// Create data channel
if (createOffer) {
dataChannel = pc.createDataChannel('chat');
setupDataChannel();
} else {
pc.ondatachannel = (event) => {
dataChannel = event.channel;
setupDataChannel();
};
}
// Handle ICE candidates
pc.onicecandidate = (event) => {
if (event.candidate) {
ws.send(JSON.stringify({
type: 'candidate',
to: peerId,
data: JSON.stringify(event.candidate)
}));
}
};
// Handle remote tracks
pc.ontrack = (event) => {
console.log('Received remote track');
const remoteVideo = document.getElementById('remoteVideo');
remoteVideo.srcObject = event.streams[0];
};
pc.onconnectionstatechange = () => {
console.log('Connection state:', pc.connectionState);
};
if (createOffer) {
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
ws.send(JSON.stringify({
type: 'offer',
to: peerId,
data: JSON.stringify(offer)
}));
}
return pc;
}
async function handleOffer(msg) {
const pc = await createPeerConnection(msg.from, false);
const offer = JSON.parse(msg.data);
await pc.setRemoteDescription(new RTCSessionDescription(offer));
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
ws.send(JSON.stringify({
type: 'answer',
to: msg.from,
data: JSON.stringify(answer)
}));
}
async function handleAnswer(msg) {
const pc = peers[msg.from];
if (pc) {
const answer = JSON.parse(msg.data);
await pc.setRemoteDescription(new RTCSessionDescription(answer));
}
}
async function handleCandidate(msg) {
const pc = peers[msg.from];
if (pc) {
const candidate = JSON.parse(msg.data);
await pc.addIceCandidate(new RTCIceCandidate(candidate));
}
}
function handleLeave(msg) {
const pc = peers[msg.from];
if (pc) {
pc.close();
delete peers[msg.from];
}
}
function setupDataChannel() {
dataChannel.onopen = () => {
console.log('Data channel opened');
};
dataChannel.onmessage = (event) => {
const messages = document.getElementById('messages');
messages.innerHTML += `<div><strong>Peer:</strong> ${event.data}</div>`;
messages.scrollTop = messages.scrollHeight;
};
dataChannel.onclose = () => {
console.log('Data channel closed');
};
}
async function startVideo() {
try {
localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
document.getElementById('localVideo').srcObject = localStream;
// Add tracks to existing peer connections
for (const pc of Object.values(peers)) {
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
}
console.log('Local video started');
} catch (error) {
console.error('Error accessing media devices:', error);
}
}
function stopVideo() {
if (localStream) {
localStream.getTracks().forEach(track => track.stop());
document.getElementById('localVideo').srcObject = null;
localStream = null;
}
}
function sendMessage() {
const input = document.getElementById('messageInput');
const message = input.value;
if (dataChannel && dataChannel.readyState === 'open') {
dataChannel.send(message);
const messages = document.getElementById('messages');
messages.innerHTML += `<div><strong>You:</strong> ${message}</div>`;
messages.scrollTop = messages.scrollHeight;
input.value = '';
} else {
alert('Data channel is not open');
}
}
</script>
</body>
</html>
Selective Forwarding Unit (SFU) for Scaling
For multi-party video conferencing, use an SFU to forward streams efficiently:
webrtc/sfu.go
package webrtc
import (
"io"
"log"
"sync"
"github.com/pion/webrtc/v3"
)
// SFU manages media routing between multiple peers
type SFU struct {
peers map[string]*Peer
mu sync.RWMutex
}
// NewSFU creates a new SFU
func NewSFU() *SFU {
return &SFU{
peers: make(map[string]*Peer),
}
}
// AddPeer adds a peer to the SFU
func (s *SFU) AddPeer(peer *Peer) {
s.mu.Lock()
defer s.mu.Unlock()
s.peers[peer.ID] = peer
// Handle incoming tracks
peer.OnTrack = func(track *webrtc.TrackRemote) {
s.forwardTrack(peer.ID, track)
}
log.Printf("SFU: Added peer %s (total: %d)", peer.ID, len(s.peers))
}
// RemovePeer removes a peer from the SFU
func (s *SFU) RemovePeer(peerID string) {
s.mu.Lock()
defer s.mu.Unlock()
if peer, exists := s.peers[peerID]; exists {
peer.Close()
delete(s.peers, peerID)
log.Printf("SFU: Removed peer %s (remaining: %d)", peerID, len(s.peers))
}
}
// forwardTrack forwards a track to all other peers
func (s *SFU) forwardTrack(senderID string, track *webrtc.TrackRemote) {
s.mu.RLock()
defer s.mu.RUnlock()
// Create a local track to forward
localTrack, err := webrtc.NewTrackLocalStaticRTP(
track.Codec().RTPCodecCapability,
track.ID(),
track.StreamID(),
)
if err != nil {
log.Printf("Error creating local track: %v", err)
return
}
// Add track to all other peers
for id, peer := range s.peers {
if id != senderID {
_, err := peer.AddTrack(localTrack)
if err != nil {
log.Printf("Error adding track to peer %s: %v", id, err)
}
}
}
// Forward packets
go func() {
buffer := make([]byte, 1500)
for {
n, _, err := track.Read(buffer)
if err == io.EOF {
return
}
if err != nil {
log.Printf("Error reading track: %v", err)
return
}
if _, err := localTrack.Write(buffer[:n]); err != nil {
log.Printf("Error writing track: %v", err)
return
}
}
}()
log.Printf("SFU: Forwarding track %s from peer %s to %d peers", track.ID(), senderID, len(s.peers)-1)
}
STUN/TURN Server Setup
For production, you need TURN servers for NAT traversal:
coturn.conf
listening-port=3478
external-ip=YOUR_PUBLIC_IP
fingerprint
lt-cred-mech
user=username:password
realm=your.domain.com
Start coturn:
docker run -d --network=host \
coturn/coturn \
-n \
--log-file=stdout \
--external-ip=YOUR_PUBLIC_IP \
--listening-port=3478 \
--fingerprint \
--lt-cred-mech \
--user=username:password \
--realm=your.domain.com
Best Practices
1. Always Implement Signaling Timeouts
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Wait for ICE gathering with timeout
select {
case <-gatherComplete:
// Proceed
case <-ctx.Done():
return errors.New("ICE gathering timeout")
}
2. Handle Connection Failures
pc.OnConnectionStateChange(func(state webrtc.PeerConnectionState) {
if state == webrtc.PeerConnectionStateFailed {
// Attempt ICE restart
pc.RestartICE()
}
})
3. Clean Up Resources
defer func() {
if localStream != nil {
localStream.getTracks().forEach(track => track.stop())
}
if peerConnection != nil {
peerConnection.close()
}
}()
4. Monitor Media Quality
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for range ticker.C {
stats := pc.GetStats()
// Monitor packet loss, jitter, bitrate
log.Printf("Stats: %+v", stats)
}
Common Pitfalls
1. Not Handling ICE Restart
// Handle ICE failures with restart
pc.OnICEConnectionStateChange(func(state webrtc.ICEConnectionState) {
if state == webrtc.ICEConnectionStateFailed {
pc.RestartICE()
}
})
2. Missing TURN Servers
// Always include TURN for production
ICEServers: []webrtc.ICEServer{
{URLs: []string{"stun:stun.l.google.com:19302"}},
{
URLs: []string{"turn:turn.example.com:3478"},
Username: "user",
Credential: "pass",
},
}
3. Not Implementing Bandwidth Management
// Set bandwidth limits
pc.SetConfiguration(webrtc.Configuration{
SDPSemantics: webrtc.SDPSemanticsUnifiedPlan,
})
When to Use WebRTC
✅ Use WebRTC When:
- Low latency critical (< 500ms)
- Peer-to-peer preferred - Reduce server bandwidth
- Real-time audio/video required
- Interactive applications - Gaming, collaboration
- Direct file transfer needed
- Screen sharing required
❌ Avoid WebRTC When:
- One-to-many broadcasting - Use HLS/DASH instead
- Recorded playback - Use traditional streaming
- Simple data exchange - WebSockets simpler
- No NAT traversal support - Firewall restrictions
- Wide device support needed - Limited on older devices
Advantages
- Low Latency - Sub-second delay
- Peer-to-Peer - Reduces server bandwidth
- Built-in Security - DTLS/SRTP encryption
- NAT Traversal - STUN/TURN support
- Browser Native - No plugins required
- Multiple Streams - Audio, video, and data
Disadvantages
- Complex Setup - Signaling, STUN/TURN required
- Bandwidth Intensive - Each peer connection uses bandwidth
- Scaling Challenges - Mesh doesn’t scale beyond ~4-6 peers
- NAT/Firewall Issues - TURN servers needed
- Browser Compatibility - Not all features universally supported
- Debugging Difficulty - Complex protocol stack
Architecture Patterns
graph TB
subgraph "Mesh (2-4 peers)"
P1[Peer 1] <--> P2[Peer 2]
P1 <--> P3[Peer 3]
P2 <--> P3
end
subgraph "SFU (4+ peers)"
P4[Peer A] <--> SFU[SFU Server]
P5[Peer B] <--> SFU
P6[Peer C] <--> SFU
P7[Peer D] <--> SFU
end
subgraph "MCU (high quality)"
P8[Peer 1] --> MCU[MCU Server]
P9[Peer 2] --> MCU
P10[Peer 3] --> MCU
MCU --> P8
MCU --> P9
MCU --> P10
end
style P1 fill:#e1f5ff
style SFU fill:#ffe1e1
style MCU fill:#fff4e1
Backend Communication
Current:
WebRTC