DEV Community

desgh white
desgh white

Posted on

Keeping a Live Stream and Its Game State in Sync

A video feed and a state channel travel over different paths with different buffering. If you render state the instant it arrives, the UI narrates the future: the result appears before the viewer sees it happen. Fixing that is mostly about sequencing, not speed.

Two clocks, one round

Video is delayed by encoding, CDN and player buffer — typically several seconds. The WebSocket message is delayed by milliseconds. So state must be held back to the stream, not pushed ahead of it.

const STREAM_DELAY_MS = () => player.buffered.length
  ? (player.buffered.end(0) - player.currentTime) * 1000
  : 0;

function schedule(event) {
  const wait = Math.max(0, event.showAtMs - Date.now() + STREAM_DELAY_MS());
  setTimeout(() => render(event), wait);
}
Enter fullscreen mode Exit fullscreen mode

Server timestamps carry the intent (showAtMs); the client aligns them to its own buffer.

Sequence numbers beat timestamps

Clock skew makes wall-clock ordering unreliable. Give every state change a monotonic seq per round and drop anything you have already applied:

let applied = -1;

function onMessage(msg) {
  if (msg.seq <= applied) return;        // duplicate or out-of-order
  if (msg.seq > applied + 1) return resync(msg.roundId);  // gap -> refetch
  applied = msg.seq;
  schedule(msg);
}
Enter fullscreen mode Exit fullscreen mode

The gap branch matters more than the duplicate branch: a missed transition leaves the UI confidently wrong, which is worse than a brief spinner.

Reconnect by snapshot, not by replay

After a disconnect, replaying the backlog re-animates events the viewer already missed. Fetch a snapshot of the current round instead, set applied to its seq, and resume the live channel from there. Backlogs are for audit logs; snapshots are for UIs.

Make the states explicit

betting_open -> betting_closed -> spinning -> resolved as an enum, with illegal transitions rejected, keeps a late message from reopening a closed round. The same discipline that protects a payments ledger protects a round.

Reference

Live-dealer formats are the demanding case, since bets close while the wheel is already moving. Looking at how a live catalogue such as Crazy Time organizes its rounds and bet windows is a practical way to see which transitions have to be strictly ordered and which can tolerate a late arrival.

Takeaway

Delay state to the stream, order by sequence rather than clock, resync from a snapshot, and forbid illegal transitions. The feed and the UI then tell the same story.

Top comments (0)