Hi, I’m Hugo

Node to Go for 1:1 calls: most of the 12x was a database query

The earlier posts (scale and liveness) measured 1:1 call signaling on Node. Each call runs a state machine (FSM): IDLE → OFFERING → CONNECTED → RENEGOTIATING, plus AWAITING_REJOIN and ENDED. I wrote a minimal Go version of that state machine (gosignal) to benchmark it next to Node, and completed the port afterwards. The prototype benchmark looked lopsided. At 400 simultaneous calls the load driver measured a median offer-to-answer time of 446ms on Node and 37ms on gosignal, about 12x lower. Then I replayed Node’s lifecycle tests on the port, and three gaps fell out that the benchmark had no way to see: a rejoin that reversed the caller and callee roles, a second offer that overwrote a live call, and renegotiation accepted from any state. ...

October 9, 2026 · 18 min · Hugo Lavernhe

Debugging a Go Call-Transcriber on Jitsi's Video Bridge

TL;DR: I built a Go call-transcriber for Jitsi group calls: audio in from JVB, transcription via Deepgram, storage in MongoDB, events out on NATS. Five protocol-level bugs shaped how it works. Studying opus-transcriber-proxy opus-transcriber-proxy is a transcription proxy from the Jitsi project: multi-provider (OpenAI, Deepgram, Gemini, xAI), built-in failover, translation. I studied it as a reference for how JVB’s transcription protocol works (a WebSocket JVB opens to stream conference audio out to a listener), and how a production relay handles multi-provider routing. Then I built a smaller version. ...

September 28, 2026 · 9 min · Hugo Lavernhe

Keeping a WebRTC call alive when a signaling instance goes down

The previous post named a gap as future work: horizontal scale-out for the signaling layer worked, but a crash on any instance left the surviving peer stranded, with no way to know. The session state kept pointing at a peer that no longer existed. This post closes that gap, then goes further: it builds two different ways to detect a failed instance, measures both, and looks at what each actually costs. ...

September 16, 2026 · 8 min · Hugo Lavernhe

Scaling a WebRTC signaling layer horizontally

one-chat is a WebRTC app with 1:1 video calls, whose call-setup path ran through a single Node process with call state in memory. I wanted to know how far that could go, so I load-tested it. What I found wasn’t a bug. It’s just what a single process running everything adds up to: one event loop, one thread, and all the call state sitting in memory. So I rebuilt that layer around an explicit state machine (explicit because the old code didn’t really have one, more on that below), backed it with a session store that isn’t tied to any particular process (the piece that later makes Redis a drop-in swap, not a rewrite), then ran the whole thing across multiple backend instances behind a load balancer, to see whether it held up. ...

September 7, 2026 · 13 min · Hugo Lavernhe

Rewriting Jibri to Use the Jitsi Meet External API

Project Overview Jibri (Jitsi BRoadcasting Infrastructure) records and live-streams Jitsi Meet conferences by launching a Chrome instance on a virtual display that joins the conference as a participant, then captures the media with FFmpeg. Jibri’s pre-existing approach reads conference state by visiting the Jitsi Meet page and reaching into its internals (querying APP.conference and APP.store). This project adds an alternative: driving Jibri through Jitsi Meet’s External API instead, the same event-driven, iframe-based interface third-party apps use to embed Jitsi Meet. Both modes are supported today, toggled by a feature flag. ...

August 20, 2026 · 5 min · Hugo Lavernhe