- serve(Map config, events) is the one server entrypoint: the app hosts it via Isolate.spawn (crash surfaced on the Host card, logs forwarded as [server]); bin/server.dart is the dedicated server, compiled into the bundle as watch-together-server. --headless gone. - Host identity via a per-hosting hostKey in hello, replacing the loopback heuristic. - Rename room -> server across code, files and wire (t:"state", /health name); protocol bumped to 5. - CI compiles the server binary; isolate smoke tests in server and desktop suites. Docs updated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
3.5 KiB
3.5 KiB
How it works
A five-minute tour. Details: ARCHITECTURE.md, wire format: PROTOCOL.md.
The pieces
your PC friend's PC
+-------------------+ +-------------------+
| app (mpv inside) | | app (mpv inside) |
+---------+---------+ +---------+---------+
| sync (WebSocket) | sync (WebSocket)
v v
+----------------------------------------+
| server (who is ready, where are we) |
+----------------------------------------+
video bytes come from somewhere else:
- a link (Jellyfin, any http/https URL): each app fetches it directly
- a file on the host's disk: the server streams it (/m/... endpoint)
- App: the desktop window. It draws the video with mpv, shows who is here, and sends what you do (ready, seek, chat).
- Server: keeps one shared clock and the list of people. It never sees video frames, only "position 123 s, playing". There is exactly one server and one watch session on it.
- Jellyfin (or any web host): where a link's video lives. The server does not touch it today; each app downloads it itself.
Ways to run
- Host from the app: the app you click Host in also runs the server. Simplest. The server lives as long as that app.
- Dedicated server:
watch-together-server, a separate program in the release tarball, for an always-on box. See the README.
Friends join either way with the Join card on Home.
One session, start to finish
- Connect. The app opens a WebSocket to the server and says hello (version, password, name, avatar). Wrong password or version: refused with a reason. Otherwise the server answers with a welcome and the current state.
- Catch up. The app opens the current video, jumps to where the server says we are, and buffers. A new arrival is not ready yet and never holds anyone back.
- Ready gate. Press Space to say you are ready. The server starts playback only when everyone who is ready has at least 4 seconds buffered. If two or more people are ready, one person alone cannot start it for the others. A lone viewer can play alone.
- Play. The server owns the clock. Each app compares its mpv position to the clock: a small lead slows mpv a little (0.95x), a big gap (over 5 s) jumps. If someone's buffer runs dry the server pauses everyone and resumes when they have caught up.
- Seek. Anyone can seek, anytime. The server moves the clock and everybody follows. Seeking does not change anyone's ready flag.
- Swap. Anyone can change the video by pasting another link. Everybody stops at 0, ready flags clear, and the gate runs again.
- Leave. Closing or leaving drops you from the server; the others keep going. If you vanish without saying goodbye, you are marked stale after 15 s (stops blocking) and removed after 30 s. The app reconnects by itself and restores your ready flag.
There are no roles: anyone can seek, swap, force-ready others and kick anyone except the host.
Where video comes from
- Link: you paste an http(s) URL. Every app plays it directly with mpv. Works for a Jellyfin direct-download link or any file on the web.
- Local file (host only): you pick a file on your disk. The server streams it to everyone, host included, over HTTP with range requests, each with a per-person token. Nothing is stored or converted.
- No transcoding, ever. Quality is whatever the file is.