ServeDir now falls back to index.html for unknown paths (SPA routing).
https://host:port/manwe loads the page with room input pre-filled as "manwe".
JS getRoom() already reads the path, now the page actually loads.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PTT mode was causing delayed/lost audio because:
1. Silence suppression ate the start of speech after PTT release
2. Jitter buffer target depth was too high for interactive use
Web bridge now uses:
- suppression_enabled: false (PTT handles silence at browser level)
- jitter_target: 3 (60ms vs ~1s default)
- jitter_max: 20 (400ms cap)
- jitter_min: 1 (start playing after 20ms)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Remove unused imports in featherchat.rs (tracing, QualityProfile)
- Remove unused comfort_noise field from CallEncoder (cn_level is used instead)
- Prefix unused _metrics_file in CliArgs
- Prefix unused _addr in Participant
- Remove unused RoomSlot struct and rooms field from web AppState
- Remove unused HashMap import from web main
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
WZP-S-4: Room access control
- hash_room_name() in wzp-crypto: SHA-256("featherchat-group:"+name)[:16]
- CLI --room flag hashes before SNI, web bridge does the same
- RoomManager gains ACL: with_acl(), allow(), is_authorized()
- join() returns Result, rejects unauthorized fingerprints
WZP-S-5: Crypto handshake wired into all live paths
- CLI: perform_handshake() after connect, before any mode
- Relay: accept_handshake() after auth, before room join
- Web bridge: perform_handshake() after auth, before audio
- Relay generates ephemeral identity at startup
WZP-S-6: Web bridge featherChat auth
- --auth-url flag: browsers send {"type":"auth","token":"..."} as first WS msg
- Validates against featherChat, passes token to relay
- --cert/--key flags for production TLS (replaces self-signed)
WZP-S-7: wzp-proto standalone
- Cargo.toml uses explicit versions (no workspace inheritance)
- FC can use as git dependency
WZP-S-9: All 6 hardcoded assumptions resolved
- Auth, hashed rooms, mandatory handshake, real TLS certs,
profile negotiation, token validation
CLI also gains --room and --token flags.
179 tests passing across all crates.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Room-based SFU relay:
- Clients join named rooms (room name from QUIC SNI)
- Each participant's packets forwarded to all others (no mixing)
- Multiple rooms run concurrently on one relay
- Web bridge passes room name from URL path to relay
Push-to-talk (radio mode):
- Toggle "Radio mode" checkbox after connecting
- Hold PTT button or spacebar to transmit
- Release to mute mic (receive-only)
- Works on desktop (spacebar) and mobile (touch)
URL routing:
- /myroom → joins room "myroom"
- Room name input field as fallback
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rooms:
- URL-based: open /myroom to join a room
- Two clients in same room get bridged through relay
- Input field for room name, also supports URL path and hash
- Each room creates independent relay connections
AudioWorklet (replaces deprecated ScriptProcessorNode):
- capture-processor.js: accumulates mic samples, sends 960-sample frames
- playback-processor.js: pull-based output with 200ms buffer cap
- Falls back to ScriptProcessor if AudioWorklet unavailable
- Eliminates drift: worklet runs on audio thread, not main thread
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bridge mode rewrite:
- First client echoes while waiting, checks every 100ms if paired
- Second client triggers bridge immediately, first exits echo loop
- After bridge ends, slot is cleared for the next pair
- No more two tasks competing for the same transport recv
Web client auto-reconnect:
- On WebSocket close/error, automatically reconnects after 1s
- Keeps retrying as long as the user hasn't clicked Disconnect
Test fix:
- Install rustls crypto provider in transport config tests
(fixes race condition when running full workspace tests)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
150ms cap was too tight for Iran relay (high jitter), causing constant
audio drops. Raised to 1s — packet bursts are absorbed smoothly,
drift reset only fires on real accumulation.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Pull-based ScriptProcessor approach broke audio completely.
Back to createBufferSource scheduling which worked, but with
tighter 200ms max drift (was 300ms). Snaps back when exceeded.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Previous version output 960 samples into 1024-sample callback frames,
causing 64 samples of silence per frame (choppy/robotic sound).
Now accumulates float samples in a continuous buffer, output callback
pulls exactly 1024 at a time regardless of input frame size.
Buffer capped at 200ms to prevent drift.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Web playback rewritten from push-scheduling to pull-based ring buffer:
- ScriptProcessorNode pulls frames from buffer every ~21ms
- Buffer capped at 10 frames (~200ms) — drops oldest on overflow
- Latency permanently bounded, no drift over time
Also: install ring crypto provider for rustls TLS on Linux,
build on debian-12 to match mequ glibc.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Generates a self-signed certificate at startup for HTTPS.
Required for mic access on Android/remote browsers (getUserMedia
needs a secure context).
Usage: wzp-web --port 9090 --relay 127.0.0.1:4433 --tls
Browser: accept the self-signed cert warning, then mic works.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When playback buffer drifts beyond 300ms ahead of real-time, reset
to 40ms. This prevents the unbounded latency growth over long sessions.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Schedule each playback buffer to start exactly where the last ended
(was causing gaps/overlaps with fixed 60ms offset)
- Log AudioContext sample rate to console for debugging
- Reset playback timeline when falling behind
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use power-of-2 buffer (1024) for ScriptProcessorNode
- Accumulate samples and send exact 960-sample frames
- Remove unused watch import from relay
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
New wzp-web crate serves a web page with:
- Browser mic capture via Web Audio API (48kHz mono)
- WebSocket transport for raw PCM audio
- Server-side Opus encode/decode + FEC through wzp relay
- Real-time audio playback in browser
- Level meter and connection stats
Usage:
wzp-relay --listen 0.0.0.0:4433 # start relay
wzp-web --port 8080 --relay 127.0.0.1:4433 # start web bridge
Open http://localhost:8080 in browser
Two browsers connected to the same relay get bridged for a call.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>