·10 min read·

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

LayerTechnologyWhy
Audience front-endNuxt + VueSSR for performance, easy hand-off to production team
Production control surfaceVue + TailwindBrowser-first so producers can run from anywhere
Realtime signallingSocket.ioMature, fan-out works at scale, fallbacks for older browsers
Session stateRedisSub-millisecond reads, easy multi-region replication
Persistent dataMongoDBSchema-fluid for shows that change shape every week
Video layerWebRTC (audience), RTMP (broadcast)WebRTC for low latency to the audience tile; RTMP for the production feed
Application backendExpress + NodeSame JS runtime as the front-end, easy to share types
Virtual Audience architecture, March 2020 → December 2020.

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.

server/socket.tstypescript
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.

Talk to Remiam about a system like this.