LiveKit is an open-source SFU (Selective Forwarding Unit) that handles real-time voice and video. OwnCord uses it instead of rolling its own WebRTC stack -- LiveKit handles all the hard parts (DTLS, ICE, codec negotiation, simulcast) while OwnCord manages permissions, state, and room lifecycle.
When running OwnCord via `docker compose`, LiveKit runs as a separate container on the same internal network. OwnCord reaches it at `ws://livekit:7880` via Docker's internal DNS — no port forwarding needed between containers.
### Setup
1.**Edit `.env`** (in `Server/`) — set `LIVEKIT_API_KEY` and `LIVEKIT_API_SECRET`:
3. **Leave `voice.livekit_binary` unset** in your `config.yaml`. The `voice.livekit_url` should be `ws://livekit:7880` (Docker DNS).
4. **Open firewall ports** on your host:
| Port | Protocol | Purpose |
|------|----------|---------|
| `7880` | TCP | LiveKit signaling |
| `7881` | TCP | TCP fallback for WebRTC |
| `50000-60000` | UDP | WebRTC media |
> **`node_ip` is required** for remote clients. Without it, LiveKit advertises internal Docker IP addresses as ICE candidates, which are unreachable from the internet. If your cloud VM has a metadata service (AWS, GCP, DigitalOcean) you can use `use_external_ip: true` instead.
---
## Companion Process (Windows / bare-metal Linux)
### 1. Get the LiveKit Binary
Download `livekit-server` for your platform from one of:
| **50000-60000** | UDP | Media transport (RTP audio/video) |
For LAN-only setups, ensure these ports are open on Windows Firewall. For remote access, forward these through your router or use [Tailscale](tailscale.md).
---
## 4. How the Companion Process Works
When `livekit_binary` is set, OwnCord manages LiveKit as a companion process:
1. **Config generation**: OwnCord auto-generates `data/livekit.yaml` with the API key/secret, port 7880, and UDP range 50000-60000
- **Direct URL** (`ws://localhost:7880`): Used when the client is on localhost.
---
## 6. Webhook Integration
LiveKit sends webhooks to `POST /api/v1/livekit/webhook`. The endpoint verifies the JWT and handles `participant_left` to clean up ghost voice states when a user disconnects from LiveKit without sending a `voice_leave` message.
---
## 7. Troubleshooting
| Symptom | Cause | Fix |
|---------|-------|-----|
| "voice not configured" error | LiveKit client failed to initialize | Check `livekit_api_key` and `livekit_api_secret` are set and secret is >= 32 chars |
| "failed to generate voice token" | API key/secret mismatch | Ensure `config.yaml` key/secret match what LiveKit is using |
| Voice connects but no audio | Firewall blocking UDP 50000-60000 | Open UDP port range in Windows Firewall |
| "backend unavailable" from `/livekit` proxy | LiveKit not running on port 7880 | Check `livekit_binary` path or start LiveKit manually |
| "too many rapid failures, giving up" in logs | LiveKit binary crashes on startup | Run `livekit-server --config data/livekit.yaml` manually to see errors |
| Mixed content / insecure WS error | Client using direct URL over HTTPS page | Client should use the `/livekit` proxy path |
| `GET /api/v1/livekit/health` returns degraded | LiveKit server not reachable | Verify LiveKit is running: `curl http://localhost:7880` |
---
## 8. Production Checklist
- [ ] Change `livekit_api_key` from `"devkey"` to a random string
- [ ] Change `livekit_api_secret` to a random 32+ character string
- [ ] Open firewall ports: 7880/TCP, 50000-60000/UDP
- [ ] If using ACME/manual TLS, ensure LiveKit proxy at `/livekit` is working
- [ ] Test voice by joining a voice channel from two clients