Two Screens in Sync for 48 Hours: DataChannel Clock Drift and Leader Election in a Museum Exhibit

Two screens, one room, forty-eight hours of continuous operation. That is the shape of the problem: a museum exhibit where a pair of browser instances must stay in step for two full days without a technician touching them. The interesting engineering is not the media itself. It is the timebase and the authority model. Get those wrong and the exhibit drifts apart, or both screens decide they are in charge, or one screen reboots and the other keeps playing into an empty room.

This is a bench-test write-up of the design decisions, grounded in the specifications that actually govern the behaviour. Where I state a number as a target, it is a target I would set for the rig, not a measurement from a study. Where I cite a specification, the citation is to the primary document.

Why a DataChannel at all

The two screens are separate browser instances. They need a low-latency control path that survives a flaky venue network and does not depend on a cloud relay. WebRTC data channels give us exactly that: SCTP over DTLS over ICE/UDP, with ordered and unordered delivery, reliable and partially reliable modes, and multiple streams within one association. RFC 8831 is the primary reference for the transport model, and it is explicit that the stack supports “ordered and unordered delivery of user messages” and “reliable and partially reliable transport of user messages” (RFC 8831, Section 5).

The W3C WebRTC Recommendation defines the API surface: RTCPeerConnection, RTCDataChannel, and the connection state machine. It is a W3C Recommendation dated 13 March 2025. The specification is developed in conjunction with the IETF RTCWEB group, and the data-channel transport is specified in RFC 8831.

For a two-screen exhibit, the data channel carries three kinds of message:

  • Heartbeat and clock samples — small, frequent, unordered, unreliable. Losing one is fine.
  • Leader claims and state snapshots — small, infrequent, ordered, reliable. Losing one is not fine.
  • Playback commands — small, ordered, reliable, with an explicit effective time.

Mixing these on one channel is a mistake. RFC 8831 notes that all SCTP streams within a single association share the same congestion window (Section 5). A reliable retransmit storm on the control channel will delay the heartbeat samples that the clock estimator depends on. Use separate channels, or at minimum separate SCTP streams, and set the reliability mode per channel.

The clock problem, stated plainly

Two browsers on two machines do not share a clock. Date.now() is wall-clock time and is subject to skew and adjustment. The High Resolution Time specification is blunt about this: the wall clock “sometimes needs to be adjusted, which means the unsafe current time can decrease, making it unreliable for performance measurement or recording the orders of events” (W3C High Resolution Time Level 3, Section 2.1).

performance.now() is monotonic within a single user-agent execution, but the same specification warns that the monotonic clock “only exists within a single execution of the user agent, so it can’t be used to compare events that might happen in different executions” (Section 2.1). Two machines, two executions, two monotonic clocks. You cannot compare them directly.

The Web Audio API gives you AudioContext.currentTime, which is the time in seconds of the sample frame immediately following the last rendered sample frame. It is a high-resolution, audio-thread-driven clock. It is excellent for scheduling audio events on one machine. It is not a shared clock across machines.

So the design has to build a shared timebase on top of the data channel. The standard approach is a round-trip estimator: send a timestamp, receive it back, compute offset and round-trip time, keep the sample with the lowest RTT. This is the same shape as NTP and PTP, and it is the only honest way to do it without a shared hardware clock.

What the estimator actually does

Let t1 be the sender’s monotonic time when the probe is sent, t2 the receiver’s monotonic time when it arrives, t3 the receiver’s monotonic time when the reply is sent, and t4 the sender’s monotonic time when the reply arrives. The offset estimate is ((t2 - t1) + (t3 - t4)) / 2 and the round-trip time is (t4 - t1) - (t3 - t2). The RTT is the uncertainty on the offset. Keep the sample with the smallest RTT over a sliding window, and re-estimate continuously.

This is not a new algorithm. It is the same arithmetic NTP uses. The point is that the data channel gives you a transport for the probes, and the estimator gives you a shared timebase with a known uncertainty.

Drift over 48 hours

Two independent crystal oscillators will drift relative to each other. Typical consumer hardware oscillators are specified in the tens of parts per million. At 50 ppm, the relative drift is 50 microseconds per second, which is 180 milliseconds per hour, which is 4.3 seconds per day. Over 48 hours that is roughly 8.6 seconds of accumulated offset if you never re-estimate.

That number is a design target, not a measurement. The actual drift depends on the hardware, the temperature, and the load. The point is that you cannot assume the two clocks stay aligned. You must re-estimate continuously, and you must decide what to do when the estimate changes.

The practical rule: re-estimate every few seconds, keep a sliding window of the best samples, and never apply a step correction to a running playback clock. Apply a slew — a small rate adjustment — so the audio and video do not glitch. If the offset exceeds a threshold you cannot slew away, stop, re-sync, and resume from a known point.

Leader election on two nodes

With two nodes, leader election is not a distributed-systems research problem. It is a tie-break problem. But it is a tie-break problem that must survive a network partition, a browser restart, and a tab that has been backgrounded for an hour.

The naive approach — both nodes send “I am leader” and the first one wins — fails the moment the network hiccups. The correct approach is a deterministic tie-break with a lease.

The lease model

Each node has a stable identifier. The leader is the node with the lower identifier, unless the lower-identifier node has not sent a heartbeat within the lease window. The lease window is a function of the heartbeat interval and the observed RTT. If the heartbeat interval is 500 ms and the observed RTT is under 50 ms, a lease of 2 seconds is generous. If the RTT spikes to 500 ms, the lease must be longer, or the two nodes will flap.

The lease must be renewed. A node that cannot renew its lease must step down. A node that has not heard from the leader for the lease window may claim leadership. This is the same shape as a lease-based leader election in any consensus protocol, reduced to two nodes.

Why ordered, reliable delivery matters here

Leader claims and state snapshots must be ordered and reliable. RFC 8831 specifies that SCTP supports ordered and unordered delivery, and that the partial reliability extension allows limited retransmission (Section 6.1). For the control channel, use ordered, fully reliable delivery. For the heartbeat channel, use unordered, unreliable delivery with zero retransmissions — the UDP-like mode RFC 8831 describes.

This split matters because a reliable retransmit on the control channel can block the heartbeat channel if they share a congestion window. Separate channels, separate reliability modes.

What happens when the leader dies

The follower notices the missing heartbeat after the lease window. It claims leadership, sends a claim message, and waits for an acknowledgement. If the old leader comes back and also claims leadership, the deterministic tie-break resolves it: the lower identifier wins, and the higher-identifier node steps down. The state snapshot is then reconciled from the winner.

The reconciliation must be idempotent. The follower must be able to apply a snapshot from the leader without assuming it has seen every intermediate message. This is why the snapshot carries a sequence number and a timestamp, not a delta.

Connection state and reconnection

The WebRTC specification defines the connection state machine and the ICE restart mechanism. The iceRestart option in RTCOfferOptions, when true, generates a description with new ICE credentials and restarts ICE (W3C WebRTC, Section 4.2.6). This is the mechanism you use when the network path changes — a switch reboots, a cable is reseated, a Wi-Fi AP drops the association.

For a 48-hour unattended exhibit, you must handle three failure modes:

  1. Transient network loss. ICE may recover on its own. The data channel will buffer or drop messages depending on the reliability mode. The heartbeat channel will show gaps; the estimator will widen its uncertainty. No action needed unless the gap exceeds the lease window.
  2. Path change. The ICE connection may fail and need a restart. The application must detect the failure, trigger an ICE restart, and re-establish the data channel. The leader lease must survive this: the leader does not step down just because the transport hiccupped, unless the lease expires.
  3. Process restart. A browser crash or a tab reload destroys the peer connection. The surviving node must detect the loss, claim leadership if it was the follower, and wait for the restarted node to rejoin. The restarted node must not assume it is still leader.

The JSEP specification (RFC 8829) describes the offer/answer model and the decoupling of the ICE state machine from the signaling state machine. It notes that the application has complete control over which offers and answers get handed to the implementation, and when (RFC 8829, Section 3.1). This is the hook for reconnection logic: the application decides when to renegotiate, and the implementation handles the media and transport.

RFC 8829 is now obsolete, superseded by RFC 9429. The architectural description in RFC 8829 remains useful for understanding the model, but the current normative reference is RFC 9429.

Media synchronization

The shared timebase gives you a common clock. The media elements need to be scheduled against that clock. There are two practical approaches:

  • Media Source Extensions (MSE). The Media Source Extensions specification allows JavaScript to dynamically construct media streams for <audio> and <video> elements. It defines a MediaSource object with one or more SourceBuffer objects, and the application appends data segments to those buffers (W3C Media Source Extensions, Section 1). This gives you frame-accurate control over what is buffered and when it plays. It is the right tool when you need to align two screens to a common timeline and you control the encoded segments.
  • Web Audio scheduling. The Web Audio API provides sample-accurate scheduled sound playback with low latency. AudioContext.currentTime is the audio-thread clock, and AudioBufferSourceNode.start(when) schedules playback at a specific time on that clock. For audio-only synchronization, this is the most precise mechanism available in the browser.

The two screens do not need to share a single AudioContext. They need to agree on a common timebase, and each screen schedules its own media against that timebase. The estimator gives you the offset; the scheduler applies it.

For video, the practical limit is the display refresh and the decoder pipeline. You can schedule a seek to a specific presentation timestamp, but the actual frame presentation depends on the compositor. For a museum exhibit, a tolerance of one or two frames is usually acceptable. If it is not, you need a genlock mechanism, which the browser does not provide.

Accessibility and institutional requirements

For a public museum exhibit in the EU/EEA, I would treat WCAG 2.2 Level AA as the recommended baseline for accessibility. The specific legal requirements depend on the member state and the funding source, and should be confirmed with the applicable procurement or institutional policy.

For synchronized time-based media, the success criteria I would plan around include:

  • 1.2.2 Captions (Prerecorded). If the exhibit plays prerecorded audio with speech, captions are recommended.
  • 1.2.3 Audio Description or Media Alternative (Prerecorded). If the visual content carries information not available in the audio, an alternative is recommended.
  • 1.2.5 Audio Description (Prerecorded). At Level AA, audio description is recommended for prerecorded video content.
  • 1.4.2 Audio Control. If audio plays automatically for more than three seconds, the user should be able to pause, stop, or control the volume independently of the system volume.
  • 2.2.2 Pause, Stop, Hide. Any moving, blinking, or scrolling content that starts automatically and lasts more than five seconds should have a mechanism to pause, stop, or hide it.

These are not optional for a public exhibit. They also interact with the synchronization design: if the user pauses one screen, the other must pause too, or the exhibit must present a single control surface that governs both.

EN 301 549 is the European harmonised standard for accessibility requirements for ICT products and services. It references WCAG for web content. The exact version and the applicable clauses depend on the procurement context. For a museum exhibit, the practical approach is to treat the exhibit as a web application and apply WCAG 2.2 Level AA as a design target.

What I would actually build

Here is the rig I would bench-test before committing to a 48-hour run:

  1. Two channels. One unordered, unreliable channel for heartbeat and clock probes. One ordered, reliable channel for control and state.
  2. Clock estimator. NTP-style offset estimation with a sliding window of the lowest-RTT samples. Re-estimate every 2 seconds. Report the current offset and uncertainty to the application.
  3. Lease-based leader election. Deterministic tie-break on a stable node identifier. Lease window of 2 seconds, renewed every 500 ms. Step down on lease expiry.
  4. Idempotent state snapshots. Sequence number plus timestamp. The follower applies the snapshot without assuming it has seen every intermediate message.
  5. ICE restart on path change. Detect the failure, trigger iceRestart, re-establish the data channel. The leader lease survives the transport hiccup.
  6. Media scheduling against the shared timebase. MSE for video, Web Audio for audio. Slew corrections, not step corrections, for small offsets. Stop and re-sync for large offsets.
  7. Accessibility controls. A single control surface that governs both screens. Captions and audio description as recommended by WCAG 2.2 Level AA.

The 48-hour run is the test. Log the offset estimate, the RTT, the lease state, and the media presentation timestamps. If the offset stays within a few milliseconds and the leader never flaps, the design is sound. If it does not, the logs will tell you which part failed.

FAQ

Can I use Date.now() for the shared timebase?

No. The High Resolution Time specification states that the wall clock can be adjusted and its value can decrease, making it unreliable for ordering events (Section 2.1). Use performance.now() for local monotonic time and build a shared timebase with a round-trip estimator over the data channel.

How often should I re-estimate the clock offset?

Every few seconds is a reasonable starting point. The tradeoff is between responsiveness to drift and the overhead of the probes. For a 48-hour run, a 2-second interval gives you 86,400 samples per day, which is more than enough to track drift. The estimator should keep a sliding window of the lowest-RTT samples, not an average of all samples.

What happens if both nodes think they are leader?

The deterministic tie-break resolves it. The node with the lower identifier wins. The higher-identifier node steps down and reconciles its state from the winner. This is why the tie-break must be deterministic and the state snapshot must be idempotent.

Do I need a TURN server for a museum exhibit?

It depends on the network topology. If both screens are on the same LAN, host candidates are usually sufficient. If they are on different subnets or behind a NAT that blocks peer-to-peer, you need a TURN server. The WebRTC specification defines the RTCIceServer dictionary for configuring STUN and TURN servers (Section 4.2.2). For a self-hosted exhibit, a local TURN server is the safe choice.

How do I handle a browser restart during the 48-hour run?

The surviving node detects the loss via the missing heartbeat. If it was the follower, it claims leadership after the lease window. The restarted node rejoins, discovers the current leader, and reconciles its state. The restarted node must not assume it is still leader, even if it was before the restart.

What is the maximum acceptable clock offset for synchronized video?

For a museum exhibit, a tolerance of one or two frames is usually acceptable. At 30 fps, that is 33 to 66 milliseconds. At 60 fps, it is 16 to 33 milliseconds. The actual achievable tolerance depends on the display, the decoder pipeline, and the compositor. The clock estimator should aim for an uncertainty well below the frame duration.

Sources