downbeat

Fill a room with phones. Every one of them plays the same song at the same instant — no delay, no echo, no "wait, mine's a bit behind." It sounds like one big speaker. To join, you point your camera at a QR code and tap once. That's the whole thing.

Here's the part that still gets me. I'm sitting in one apartment with two phones on the table, and to keep them locked together, every scrap of audio takes a trip out to Cloudflare's network and back before it plays. Two devices an arm's length apart, synchronised by way of a data centre — and it holds them to about two thousandths of a second. That's the trick that makes a whole room work.


Why not just use a Spotify Jam?

Because a Jam doesn't actually do this. It's a shared queue — everyone can add songs, but the music plays out of one device. When several phones do play at once, nothing keeps them together: Spotify's own forums are full of people reporting listeners audibly off the beat, no fix for Bluetooth delay, tracks drifting seconds apart. And every listener needs their own Premium account. "All our phones as one speaker" is the thing people assume a Jam is, discover it isn't, and then ask Spotify for — for years now.

downbeat is built for exactly that gap. The host's Mac plays anything — Spotify included — and every phone in the room is a speaker for it. Joining is a QR code and a six-letter room code, so it works every single time instead of "when the proximity pairing feels like it." A guest needs nothing but a browser tab.

The one idea that makes it work

Streaming audio to a dozen phones and hoping they stay together is a losing game — one network hiccup and a phone is behind for good. So downbeat refuses to stream at playback time. Instead it gets the audio onto every phone ahead of when it's due, and then the only thing the whole room has to agree on is a clock. Clocks can be made to agree astonishingly well; audio streams can't. Everything else follows from that inversion.

No phone is even given a start time until every phone has reported it's decoded and ready — an arm barrier, not a countdown. From then on, playback is just "play sample number N at instant T," and the network can be as unreliable as it likes about everything except the shared clock.

The round trip through Cloudflare

The whole backend is one Cloudflare Worker and one Durable Object — a single tiny program living at the edge, near whoever's in the room. On the Mac, a native Swift binary uses a Core Audio process tap to capture whatever's playing (and mute it at the real output in the same move, so the Mac hears itself back through downbeat, in step with the phones). That audio goes out as Opus — 48 kHz, one packet every 20 milliseconds, about 110 kbit/s — up to the Durable Object, which fans it out to every phone over WebSockets and, in the same tick, doubles as the room's time server. Session ownership lives in a D1 database so two hosts can't fight over one room, and an hourly job sweeps away expired sessions. It all runs on your Cloudflare account, comfortably inside the free tiers — no service in the middle, no audio touching anyone else's servers.

How the clocks are made to agree

Each phone repeatedly asks the Durable Object what time it is and keeps only the answers that came back fastest — a slow round trip hides the network asymmetry that this kind of clock sync can't see, so the quick ones are the honest ones. Two things make this work specifically on Cloudflare: the platform freezes its clock between I/O as a security measure, so the timestamp taken the instant a message arrives is exactly fresh; and because every device reads from the same server clock, any error in that clock cancels out — all that's left is per-device jitter, which the filtering removes.

Two phones' crystals still tick at very slightly different rates — parts per million — which would drift them a millisecond apart every minute or two. A controller erases that continuously by nudging playback speed by a few cents of pitch, far too small to hear. In the browser, decoded audio is written straight into a shared-memory ring that the audio thread reads directly, so even a stuttering webpage can't interrupt the sound.

How tight it actually holds

Measured against the live deployment:

WhatMeasured
Room clock agreement across phones~0.5 ms
Playout spread between devices (4 min)2.4 ms, and flat
Long-run drift, opposite crystals0.0 ms
Packets late over a 4-minute stream0 of ~11,980

One honest caveat: those numbers describe the software pipeline, not the air in the room. Sound itself moves about 34 cm per millisecond, so two speakers three metres apart are already ~9 ms apart no matter what the code does. downbeat removes the software's share of the error; the room is still the room.

Status

Working end to end — file playback and live capture both — against the reference deployment on my own Cloudflare account. It's the project currently on my front burner.