How it works: a room, a code, a shared clock

This page explains what the extension does, with the numbers. Short version: two browsers report their position once a second; whoever is behind catches up.

Room and code

A room is nothing more than an 8-character code. The alphabet has 32 symbols: the letters a to z and the digits 2 to 9, minus l, o, 0 and 1, because they get confused when read aloud or typed. 32 to the power of 8 is about 1.1 trillion possibilities; nobody finds a room without the code.

Room codek7mq2xw4

No account, no password; the code is the room. Whoever opens the room first is the host. If the host presses “Close room” or “Leave”, everyone is removed. A dropped connection doesn't close the room: the extension reconnects with increasing intervals between 1 and 30 seconds.

A mistyped code doesn't open an empty room. The server answers “No such room”, so two people never wait for each other in different rooms.

A shared clock

On connecting, the extension sends the server 5 pings 250 ms apart. The fastest round trip gives the offset between the local clock and the server clock. The offset is refreshed every 5 minutes, because two machines' clocks drift over time.

The latency you see in the panel is that round trip. It has nothing to do with the video; it shows your distance to the server.

Pressing play doesn't start immediately: a shared moment 3 seconds ahead on the server clock is agreed, both sides wait for it and start at the same instant. In background tabs the browser can delay timers by up to half a second; the side that fires late moves its position forward by that much to compensate.

Play is pressed at t = 0. Both sides start at t = 3 s, on the server clock.

The one-second heartbeat

During playback each side sends a small message once a second: position, playing or not, server time, whether an ad is showing, whether a seek is in progress. The receiver accounts for the message's age in transit, estimates where the other side is right now, and compares it with its own position.

Heartbeats sent during an ad or a seek are not used for comparison, because the position doesn't belong to the actual video. With three or more people the decision follows the one furthest ahead: only those behind speed up, so everyone converges on the leader.

21:04:12.001  nabiz                      drift=0.031 kim=Uykulu Tilki age=39
21:04:13.003  nabiz                      drift=0.028 kim=Uykulu Tilki age=42
21:04:14.000  nabiz                      drift=0.036 kim=Uykulu Tilki age=40
Three heartbeats: the gap is around 30 ms, nothing is touched.

Nudging and seeking

Below 50 ms the gap is left alone; it is less than a frame. Between 50 ms and 1 second the side that's behind speeds up: 2 percent for a gap up to 200 ms, 5 percent at 500 ms and above, proportional in between. Once set, a rate is held for at least 3 seconds; every rate change costs about 10 ms of playback, so toggling it often actually lowered the net speed.

Above 1 second it seeks. Seeking for smaller gaps doesn't help: in one log a seek at 0.318 s drift landed at 0.304 s. The seek itself takes 90 to 163 ms; while playing, the target is moved ahead by that much, and the measured duration is used for the next seek.

0.08 s · green
0.41 s · amber, nudging
1.30 s · red, seeked
21:04:17.002  nabiz                      drift=0.312 kim=Uykulu Tilki age=38
21:04:17.004  durtme-basladi             drift=0.312 hiz=1.031
21:04:18.001  nabiz                      drift=0.281 kim=Uykulu Tilki age=41
21:04:19.003  nabiz                      drift=0.250 kim=Uykulu Tilki age=39
21:04:20.998  durtme-hizi                drift=0.187 hiz=1.020
21:04:30.412  durtme-bitti
A 0.312 s gap: at 1.031× it drops to 0.187 in four seconds; the rate steps down to 1.020×, and below 0.02 the nudge ends.

Buffering and ads

If one side buffers for longer than 700 ms, the other side is paused; when buffering ends, both resume together. Shorter stalls, especially in the first moment of playback, count as noise.

On most sites an ad plays in the same element as the actual video, so looking at the video isn't enough; the extension looks at the player container's class instead. The markers, polled every 400 ms:

PlayerClass added during an ad
YouTube.ad-showing, .ad-interrupting
JW Player.jw-flag-ads
Video.js.vjs-ad-playing

The ad keeps playing on the side that has it; only the other side's actual video is paused. When both ads are over, both resume. Ads in players other than these three aren't detected; the other side then just sees a normal pause.

Ad on the other side

Chat

Messages are timestamped as they pass through the server and relayed to the room. If the panel is closed or the video is fullscreen, the message shows at the bottom right of the video for 6 seconds; at most 3 bubbles stack. With the panel closed you also get a Chrome notification.

On your device, messages are kept only for the browser session, at most 200 of them; they are gone when the browser closes. They are not stored on the server.

The server

The server is a WebSocket relay: it carries small JSON messages between the browsers in a room and provides the shared clock. It never touches video content. Room records live in memory and are deleted when the last person leaves. Each client may send at most 40 messages per second, and a single message can't exceed 32 KB.

A connection costs about 155 KB of memory and 0.017 percent of a CPU core; a thousand concurrent connections are around 200 MB. That is why it can stay free.

You can point the extension at your own server: in Settings › Server, enter an address starting with wss://. Everyone in the room has to use the same server.

Limits

  • At most 5 people per room. More would work, but everyone's buffering and ads stop the whole room.
  • Chrome 116 or newer; Manifest V3 and the side panel are required. Chromium-based browsers haven't been tested.
  • If the two sides aren't on the same page, sync waits; the panel offers to open the page but never redirects on its own.
  • DRM services (Netflix, Disney+, Prime Video) haven't been tested.
  • Ads in players other than YouTube, JW Player and Video.js aren't detected.

One room is enough to try it.