Co-authored-by: Aaron D. Lee <himself@adlee.work> Co-committed-by: Aaron D. Lee <himself@adlee.work>
4.0 KiB
Quickstart
Get Rutster running and hear your own voice echoed back in under 5 minutes.
Status: Slices 1–3 are merged to
main. Slice 4 (barge-in / VAD-driven playout kill) is the active build target, in flight on theslice-4-dev-a-reflex+slice-4-dev-b-tapbranches. This quickstart exercises the slice-1 WebRTC media loopback, which remains the simplest end-to-end demo onmain. Seedocs/superpowers/specs/2026-07-01-slice-4-barge-in-design.mdfor the active build target's design.
Prerequisites
1. Rust toolchain
Install via rustup:
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
The repo pins a specific stable channel in rust-toolchain.toml — rustup
will pick it up automatically on first cargo invocation. No manual
toolchain selection needed.
2. libopus (FFI dependency)
The opus crate links system libopus via FFI (per PORT_PLAN §7's
"🦀 Core (FFI)" disposition — Opus is the codec surface Rust doesn't need
to re-implement). Install the dev headers:
| Platform | Command |
|---|---|
| Debian/Ubuntu | sudo apt-get install -y libopus-dev |
| Fedora | sudo dnf install -y opus-devel |
| Arch | sudo pacman -S opus |
| macOS (Homebrew) | brew install opus |
Verify: pkg-config --cflags opus should print a path with no error.
That's the only system dependency in slice 1. Everything else is pure Rust from crates.io.
Run the server
cargo run
# listening on http://0.0.0.0:8080
First build takes ~2 minutes (str0m + axum + tokio compile fresh). Subsequent builds are incremental.
Hear the echo
- Open a browser to http://localhost:8080/.
- Click Start call.
- Grant microphone permission when the browser prompts.
- Speak — you should hear yourself back within ~200 ms (no perceptible delay).
- Click Hang up to tear down. The server logs
Closing → Closedfor the session.
Verbose tracing for debugging:
RUST_LOG=rutster=debug cargo run
Troubleshooting
| Symptom | Likely cause / fix |
|---|---|
error: linking with cc failed / could not find opus |
libopus dev headers not installed. Re-run the install command above. |
| Browser shows no mic prompt | Another tab/app holding the mic, or mic permissions disabled for localhost. Check browser settings. |
ICE connection failed in the browser |
Shouldn't happen on loopback (host candidates only). If it does, check the server console for the str0m error. |
| Click Start call, nothing happens | Open the browser console (F12). The page logs ICE state + connection state to a <pre> element. Look for the failure there. |
| Port 8080 already in use | Set RUTSTER_HTTP_BIND, e.g. RUTSTER_HTTP_BIND=0.0.0.0:8090 cargo run -p rutster |
The browser test page at GET / is a single self-contained HTML file
with inline JS — no build step. View source to see exactly what the
client side is doing.
What's happening
When you click "Start call":
- Browser captures microphone audio via
getUserMedia. - Browser creates an
RTCPeerConnectionand generates an SDP offer (audio-only, Opus codec). - Browser POSTs the offer to
POST /v1/sessions/:id/offer. - The Rutster core (built on
str0m, a sans-IO WebRTC implementation) accepts the offer, generates an SDP answer with its DTLS fingerprint + ICE credentials. - Browser sets the answer as remote description; ICE + DTLS handshake completes.
- RTP starts flowing: browser → core terminates DTLS-SRTP → decodes Opus to 16-bit PCM @ 24 kHz mono → echoes PCM back → re-encodes to Opus → DTLS-SRTP → browser plays it.
The "codec-to-PCM boundary" is the canonical point where, in a future slice, the audio tap for an external AI brain splices in. Slice 1 just echoes; step 2 of the spearhead swaps the echo for a real tap.
For the why, see ARCHITECTURE.md. For the dev loop,
see DEVELOPMENT.md.