01Problem
What was broken
Movie night with friends in other cities usually means someone counting down on a call, then a running "where are you now?" as one player buffers, another hits an ad and a third skips the intro. Screen sharing a streaming service breaks its terms and its DRM, and it looks bad anyway. Everyone already has their own account, so the only thing that needs to travel is the remote control.
02Impact
What it does now
Create a room, send the link, and friends land on the same title. Anyone can play, pause or skip on the service's own player and everyone follows within half a second, with a notice saying who did it. When someone buffers or gets an ad, the room waits for them and says why, then everyone resumes together. A countdown starts every player at the same moment, small drift is corrected quietly, the room can move to the next episode together, and a presence pill shows who is here and in sync, including in full screen. Wi-Fi drops, browser restarts and closed tabs don't break the room. Tested automatically with Playwright against a stand-in player, room-service tests and axe accessibility checks in CI; the two-person acceptance test on real accounts is the last step before v0.1.0 ships.
03Architecture
How it is built
A pnpm monorepo. The Manifest V3 extension has a content script per service page (an adapter plus overlay UI in a shadow root, so the service's CSS and ours never meet), a background service worker that owns the WebSocket connection and reconnects when Chrome wakes it, and a React popup. The room service is FastAPI with WebSockets and Pydantic, holding rooms in memory on Railway. Every message between page, content script, worker, popup and server is a typed envelope validated on receipt, generated from one JSON Schema source so TypeScript and Python never drift. Sync maths lives in a pure package with no DOM or timers.
Modules
- Provider Adapters
- Netflix Page Bridge
- Echo Control
- Sync Engine
- Room Service
- Presence Pill
- Popup
- Invite Links
- Reconnection
- Protocol Schema
04Intelligence
Where the AI sits
No AI at runtime: syncing a video is timing and state, and a model would only add latency. The AI sits in how it was built. Claude Code worked from a written operating manual, with product-manager and project-manager agents checking scope and the definition of done before any use case merged, and every push waiting for my approval.
05Decisions
Trade-offs I chose
- 01
Control the Player, Never the Picture
The extension reads playing or paused, position, duration, rate and which title is open, and commands play, pause and seek. It never reads frames, captures the screen, touches licence traffic or stores any stream. That line keeps it inside DRM and service terms, and it's why everyone needs their own account.
- 02
Server Time Is the Clock
The room service stamps every change with its own time. Each client computes where the room should be now and corrects itself: drift under a second is ignored because streaming players seek in coarse steps, and past six seconds you get a Sync button instead of a surprise jump.
- 03
Echo Control
A pause WatchSync applies fires the same pause event as a pause you click. Events caused by WatchSync's own commands are suppressed for 1.5 seconds, so one person's action never bounces around the room as five.
- 04
One Adapter per Service
Sync logic contains no service-specific code. Each service gets an adapter for detect, read and command. Netflix seeks go through its player API from page context, because setting the video's time directly triggers a playback error; when that API is missing the adapter says so in plain words instead of failing silently.
- 05
Wait for the Slowest Viewer
When one person is buffering or watching an ad, playing on for everyone else means they come back to a spoiler. The room holds, shows who it is waiting for and why, and resumes everyone together.
- 06
No Database Until Accounts Need One
Rooms, names and playback state live in memory and disappear soon after everyone leaves. One instance, no Redis, no viewing history. Persistence arrives with accounts in a later release, not before.