Tech 4 min read

Architecture patterns for large-scale synchronized viewing events

IkesanContents

Think of the kind of event where the official site says, “Everyone starts watching at 7:00 PM.” Tens of thousands of people gather on the site, start the same video at roughly the same moment, and the web UI changes in sync with playback.

How do you design that?

How this differs from a watch party

Services like Teleparty or Amazon Watch Party are built around a host. Everyone else follows the host’s playback position.

A large synchronized viewing event is different:

  • tens of thousands of viewers
  • no host
  • all clients align to server time as an absolute reference
  • the site runs visual effects tied to playback

That leads to a completely different architecture.

Common failure mode

Trying to do everything on one server is how these projects fail.

[Client] -> [Main server]
             |- time sync API
             |- effect data
             |- comments
             `- everything else

If tens of thousands of clients poll every few seconds for time synchronization, the request rate quickly climbs into the thousands or tens of thousands per second.

The key move is to split out the time server.

[Client] -> [Time server, e.g. Cloudflare Workers]
        -> [CDN for cue sheets and assets]
        -> [Main server for dynamic features only]

The role of the time server

It should be a tiny endpoint that does only two things:

  1. return current server time
  2. return event status such as running or paused
{
  "serverTime": 1704067200000,
  "status": "running",
  "message": null
}

Edge platforms such as Cloudflare Workers or Vercel Edge Functions are a good fit because they are cheap, globally distributed, and low-latency.

Using status for incident control

If the time server also exposes status, it becomes an emergency broadcast channel.

If something breaks and the event has to pause, returning paused lets every client know immediately. Even if the main server is struggling, the time service can still coordinate state.

Client-side time synchronization

On first visit

  1. request the time server
  2. measure RTT
  3. calculate offset
const start = Date.now();
const res = await fetch(TIME_SERVER_URL);
const data = await res.json();
const rtt = Date.now() - start;

const serverTime = data.serverTime + (rtt / 2);
const offset = serverTime - Date.now();

After that, Date.now() + offset becomes the client’s best estimate of current server time.

Re-sync sparingly

Once the initial offset is known, most progression can be calculated locally.

  • re-sync maybe once every five minutes
  • do not stop the event if the time server is temporarily unreachable
  • combine re-sync with status checks

At that cadence, even tens of thousands of clients only generate a modest request rate.

Cue sheet design

Define presentation timing as JSON and distribute it ahead of time through the CDN.

[
  { "time": 0, "action": "showOpening" },
  { "time": 560, "action": "startSong1" },
  { "time": 1200, "action": "showParticles" },
  { "time": 6800, "action": "discChange" }
]

The client scans the cue sheet against corrected server time and fires the relevant effect locally:

function checkCue(currentTime) {
  const elapsed = currentTime - eventStartTime;
  for (const cue of timesheet) {
    if (cue.time <= elapsed && !cue.fired) {
      cue.fired = true;
      triggerAnimation(cue.action);
    }
  }
}

That means almost no runtime load on the origin server.

Practical choices for visual effects

Perfect sync is impossible

You will still see drift on the order of hundreds of milliseconds or even a few seconds because of:

  • variable network delay
  • differences in playback start timing
  • device performance differences

Choose effects that tolerate drift

Effect typeTolerance
Background color slowly shiftsGood
Particles fallGood
Text fades inGood
Fireworks at an exact instantPoor
Explosion plus flash at the same framePoor

Short, sharply timed effects make drift obvious. Effects that feel acceptable even when they start slightly late are much safer.

Scaling summary

ComponentPlacementWhy
Time serverEdgeTiny, cheap, highly available
Cue sheetCDNStatic and cacheable
Effect assetsCDNSame
Comments and live dynamicsMain serverKeep origin focused on what must be dynamic

The point is to strip as much load as possible away from the main server. If time sync and asset delivery live at the edge, the origin can focus on genuinely dynamic features.

Summary

  1. Split time sync into a separate service
  2. Fetch time once, then progress locally
  3. Distribute cue sheets in advance via CDN
  4. Design effects that tolerate drift
  5. Reuse the time server as an emergency state channel

The real shift is moving from “do everything on one server” to “separate responsibilities by function.”