Building for the pandemic, in real time.
When every live event got cancelled, the brief changed overnight. Here's what we built when everyone needed to be in the same room without being in the same room — the architecture, the team rituals, the lessons that survived 2020.
In March 2020 every live show in our calendar was cancelled in the space of a week. Festivals, awards ceremonies, brand activations, in-person workshops — every project that depended on bodies in a room. The teams who had been building physical sets, ticketed venues, and in-person installations were suddenly all trying to answer the same question: how does the show go on when nobody can be in the room?
The answer was browsers. Specifically, browsers running WebRTC, talking to each other through low-latency signalling, with a production team in the middle who could cue, switch, and react to thousands of people on screens at home. We shipped the first version of what became Virtual Audience in nine days. By December, the same system was running for Britain's Got Talent and Royal Variety.
The architecture we landed on
| Layer | Technology | Why |
|---|---|---|
| Audience front-end | Nuxt + Vue | SSR for performance, easy hand-off to production team |
| Production control surface | Vue + Tailwind | Browser-first so producers can run from anywhere |
| Realtime signalling | Socket.io | Mature, fan-out works at scale, fallbacks for older browsers |
| Session state | Redis | Sub-millisecond reads, easy multi-region replication |
| Persistent data | MongoDB | Schema-fluid for shows that change shape every week |
| Video layer | WebRTC (audience), RTMP (broadcast) | WebRTC for low latency to the audience tile; RTMP for the production feed |
| Application backend | Express + Node | Same JS runtime as the front-end, easy to share types |
What we shipped
Virtual Audience — a Nuxt front-end, an Express + Mongo backend, and Redis + Socket.io powering the realtime layer. The production team had a control surface; the audience had a tile in a wall; producers could cue reactions, lights, sound, and the moment anyone was on camera. The audience saw the show on their primary monitor; the show saw them as a wall of tiles that producers could pull into the broadcast at will.
By December the same tool was running for Britain's Got Talent, Royal Variety, and a series of Lenovo activations. It became the UK go-to for virtual-audience production. We shipped 23 major broadcast events through the system in the back half of 2020.
The realtime layer — a minimal example
What the production-team control surface looks like, stripped down. The producer hits a button; every audience client receives the event in sub-100ms; the audience UI reacts.
import { Server } from 'socket.io'
import { createAdapter } from '@socket.io/redis-adapter'
import { createClient } from 'redis'
const io = new Server({
cors: { origin: process.env.AUDIENCE_ORIGIN }
})
// Redis adapter so producer events fan out across all server instances
const pub = createClient({ url: process.env.REDIS_URL })
const sub = pub.duplicate()
await Promise.all([pub.connect(), sub.connect()])
io.adapter(createAdapter(pub, sub))
io.on('connection', (socket) => {
socket.on('producer:cue', async (cue) => {
// Validate the cue, then broadcast to every audience client
await pub.publish('show:events', JSON.stringify(cue))
io.to('audience').emit('cue', cue)
})
socket.on('audience:join', (showId) => {
socket.join(`show:${showId}`)
socket.join('audience')
})
})Producers care about milliseconds. Designers care about polish. You need both teams in the same room — even when 'the room' is a Zoom call at 6am, three hours before the live broadcast.
What we learned
- 01If your system is critical to a live broadcast, the boring infrastructure matters most — load balancing, sticky sessions, sensible Redis layouts, multi-region failover.
- 02WebRTC is excellent. WebRTC at scale, with broadcast-grade latency, is a different sport — and the SFU (selective forwarding unit) choice matters more than the framework choice.
- 03Producers care about milliseconds. Designers care about polish. You need both teams in the same room from day one.
- 04Rehearse the full pipeline before the show, ideally twice. The bugs you find in rehearsal are the bugs you avoid on broadcast night.
- 05Have a fallback for every critical path. If realtime fails, fall back to a five-second poll. If video fails, fall back to a static frame.
- 06Monitor in real time, in a way the producer can see. Hidden dashboards are useless during the show.
- 07Communicate operational state to the audience honestly. 'Re-connecting' is fine; silent failure is not.
What happened to the team
- 01We grew from five to nine engineers across the year. Every hire was remote.
- 02Standup moved to written-only in async Slack threads. We never went back.
- 03Production rehearsals became multi-timezone — a producer in LA, a director in London, an SRE in Berlin.
- 04The studio kit doubled — every engineer now had a broadcast-quality home setup, paid for by the practice.
Two years later, the same architecture pattern still underpins our live-event work — even when the rooms came back. The lessons compounded into how we now scope every realtime project, even ones that have nothing to do with live broadcast.