Jitter buffer v2, per-minute rx logging, websocket keepalive
The cohost sounded jittery on the 2026-11-03 show. v1 skipped ahead on an underrun and kept playing from an empty buffer, so after the first hiccup every later wobble was another glitch. It also never corrected a slow guest clock: ~12 glitches/min in simulation at 30 ppm slow. - Underrun: hold the sequence position and refill to a larger target - Drift correction both ways (drop/insert a frame in quiet moments), quiet judged relative to the guest's own noise floor - 80 ms start, 300 ms max, relaxes after 2 calm minutes - Log 'rx 1m:' stats, the selected route, and call totals - Ping the guest websocket every 25 s (the proxy closed it every 90 s) - Simulation kept in test/jb_sim_test.go.txt Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b0e3b805b1
commit
de7ed62d96
5 files changed
+346
-64
No files matched your search
@@ -0,0 +1,79 @@
|
||||
# guestlink - MMO remote guest line
|
||||
|
||||
Self-hosted replacement for Cleanfeed: one remote guest, live audio both ways, plus an
|
||||
anonymous text chat between the guest and the host. Built 2026-11-02.
|
||||
|
||||
| What | Where |
|
||||
|---|---|
|
||||
| Guest page | `https://guest.mmo.show/j/<token>` (NPM on .158 -> 192.168.50.204:8790) |
|
||||
| Host console | `http://192.168.50.204:8791` (LAN/VPN only, never proxied) |
|
||||
| Media | UDP **50090** - one port for all WebRTC traffic; forwarded on the router to .204 |
|
||||
| Service | `systemctl --user {status,restart} guestlink` · logs: `journalctl --user -u guestlink -f` |
|
||||
| Invite token | `~/.local/state/guestlink/token` (0600) - survives restarts |
|
||||
|
||||
## Audio path (same as `motu-cleanfeed`)
|
||||
- guest voice -> `pacat` -> sink **`motu_guest`** -> From Computer 1-2 -> guest strip
|
||||
- **`motu_mixminus.monitor`** (To Computer 3-4, show minus guest) -> `parec` -> guest
|
||||
|
||||
Both are ordinary PipeWire client streams that exist only during a call. They never reopen
|
||||
the MOTU. They carry `node.dont-fallback/dont-reconnect/dont-move`, so if `motu_guest`
|
||||
disappears playback **fails** instead of landing on the default output (tested).
|
||||
Restarting guestlink is safe for the rest of the rig. It only drops the guest's call.
|
||||
|
||||
Opus 48 kHz stereo, 128 kb/s, in-band FEC. The guest page disables browser echo cancellation,
|
||||
noise suppression and AGC. The "I'm wearing headphones" box is ticked by default; unticking it
|
||||
turns echo cancellation back on for guests on speakers.
|
||||
|
||||
The receive side has an adaptive jitter buffer (v2, 2026-11-04): 80 ms start, max 300 ms.
|
||||
On an underrun it holds its place and refills to a larger target, so delayed packets are
|
||||
played rather than thrown away. It corrects guest/MOTU clock drift in both directions by
|
||||
dropping (guest fast) or adding (guest slow) a frame during a quiet moment. "Quiet" is
|
||||
relative to the guest's own noise floor. The target rises on underruns and late packets
|
||||
and relaxes after two calm minutes. Host console: `buffer x/y ms`, lost/FEC/concealed,
|
||||
late, drift-drops, inserted, underruns.
|
||||
|
||||
**Logs for diagnosing a bad night:** one `rx 1m:` line a minute per call (buffer, losses,
|
||||
underruns, drift corrections), the network `route:` when it is chosen, and `call totals:`
|
||||
when it ends. `journalctl --user -u guestlink --since ...`
|
||||
|
||||
Why v2: v1 skipped ahead on an underrun and kept playing from an empty buffer, so after the
|
||||
first hiccup every later wobble was another glitch. It also never corrected for a slow guest
|
||||
clock. In simulation that was ~12 glitches/min with a guest clock 30 ppm slow, against ~0
|
||||
for v2. The 3 Nov 2026 cohost "jitter" was probably this, but that night was not measured.
|
||||
The simulation is kept in `test/jb_sim_test.go.txt` (rename to `_test.go` in the package dir to run).
|
||||
|
||||
The guest websocket is pinged every 25 s. Without that, the proxy closed it every 90 s
|
||||
(signalling only, audio unaffected, but the guest saw "reconnecting...").
|
||||
|
||||
## Host console
|
||||
New link (ends the session, clears chat), Revoke, Copy, End audio, Kick, Clear chat. It shows
|
||||
live meters for both directions and the selected network route. The page never plays sound
|
||||
(it is on the rig): a new guest message flashes the tab title.
|
||||
|
||||
## Chat
|
||||
Anonymous: no accounts, and the guest's name is optional (default "Guest"). The guest sees
|
||||
the host as `-host-label` (default "Host"). Messages are kept **in memory only** (last 300),
|
||||
are never written to disk or logs, and are cleared by New link / Clear / restart. Guest
|
||||
messages are rate limited and rendered as plain text.
|
||||
|
||||
## Public IP
|
||||
Learned per call via STUN (google, then cloudflare), falling back to the A record of
|
||||
guest.mmo.show. Override with `-public-ip`.
|
||||
|
||||
## Build / test
|
||||
cd ~/apps/guestlink && go build -tags nolibopusfile -o guestlink . && go build -tags nolibopusfile -o glprobe ./cmd/glprobe
|
||||
systemctl --user restart guestlink # drops a live guest - not during a show
|
||||
|
||||
`glprobe` is a headless test guest. **It creates a new link (ending the current session)**
|
||||
unless given `-token`. Safe offline test on spare ports, with no PipeWire playback:
|
||||
|
||||
./guestlink -public 127.0.0.1:18790 -host 127.0.0.1:18791 -udp 50091 \
|
||||
-sink none -test-playout-file /tmp/rx.raw -state /tmp/glstate &
|
||||
./glprobe -hostws ws://127.0.0.1:18791/ws -guest ws://127.0.0.1:18790 -tone -20
|
||||
|
||||
## Fallback
|
||||
`motu-cleanfeed` is untouched and still works. Don't run both at once: both would feed `motu_guest`.
|
||||
|
||||
## Not done (v1)
|
||||
One guest only. No TURN relay: a guest on a network that blocks all outbound UDP (some
|
||||
corporate/hotel Wi-Fi) cannot connect; they can use a phone hotspot. No recording.
|
||||
Reference in new issue
Block a user