Partmov
A private cinema for two, synchronised to the frame.
One room, one invite link, one canonical clock. Self-hosted on free software from the object store to the dashboards.
Everything on screen serves the film
A watch room is a player, a one-line status of who holds the remote and how tightly you are synced, and a control strip that fades away. Chat lives behind a collapsed rail. Nothing counts likes, nothing suggests what to watch next.
“We have all the time in the world.”
- Start together — playback begins on a scheduled timestamp once both clients report a healthy buffer, so nobody is left staring at a spinner.
- Pause for both — either participant can stop the film; only the host can seek unless shared control is switched on.
- Rejoin session — a reload or a dropped train tunnel restores the exact server position, not the last thing the browser remembered.
- Continue from last time — the room keeps its position when both people leave, so tomorrow night resumes where tonight ended.
- Shared subtitles — track selection is room state, so a subtitle change lands on both screens at once.
The server owns the clock
Clients never guess where the film is. They report where they are, receive the authoritative position, and correct the difference so gently that neither viewer notices.
Drift under 40 ms counts as locked. Between 40 ms and 1.5 s the client trims playback rate by up to 5 percent, which is inaudible on dialogue. Only past 1.5 s, or after an explicit seek, does anyone jump. See the full protocol.
Ten components, nothing speculative
Each part of the platform has a single job and a clear owner in the codebase.
Ingestion
Resumable uploads land in a private MinIO bucket, checksummed and probed before anything else happens.
Transcoding
FFmpeg builds a three-rung HLS ladder in fragmented MP4 with 2-second segments and aligned keyframes.
Subtitles & chapters
Embedded tracks are extracted, normalised to WebVTT, and stored alongside chapter markers and language tags.
Private rooms
Every room is unlisted. Access comes from a single-purpose invite link with an expiry and a revoke switch.
Sync service
One WebSocket process owns the canonical room clock, orders commands, and rejects stale ones.
Drift correction
Clients compare against server time every second and close gaps with playback-rate nudges, not seeks.
Chat & reactions
A collapsible side rail. Ephemeral by default, never the centre of the screen.
Moderation tools
Admin routes to inspect an asset, kill a room, revoke a link, and purge media on request.
Technical analytics
Startup time, rebuffer ratio, drift percentiles, join success. No behavioural profiling.
Access control
Short-lived signed media URLs, per-owner object prefixes, and hard deletion that actually deletes.
Free software, chosen on merit
Every dependency below is open source and self-hostable. Where a managed service would normally appear, the design explains what replaces it.
| Layer | Choice | Why this one |
|---|---|---|
| Client | Next.js + React, hls.js | One codebase for desktop and mobile web; hls.js gives frame-accurate position control and buffer telemetry that native HLS hides. |
| API | Fastify (TypeScript) | Small, fast, schema-validated routes. Shares types with the client, so command payloads cannot drift. |
| Realtime | ws over WSS | Control traffic is a few hundred bytes per second. WebRTC would add NAT traversal and codec negotiation for no gain. |
| Database | PostgreSQL 16 | Rooms, assets, invites, audit trail, and the job queue. LISTEN/NOTIFY replaces a broker at this size. |
| Object storage | MinIO | S3 API without the bill. Private buckets, per-owner prefixes, server-side encryption, versioning off for media. |
| Media | FFmpeg | Ladder generation, subtitle extraction, poster frames, sprite sheets. Nothing else is needed. |
| Edge | Caddy or Nginx | TLS, HTTP/2, media token verification, and an optional segment cache in front of MinIO. |
| Identity | Magic-link sessions | Email link plus signed cookie. No passwords to leak; Keycloak stays on the shelf until SSO is a real requirement. |
| Observability | Prometheus, Grafana, Loki, OpenTelemetry | The standard open stack. Dashboards track playback health, not people. |
| Runtime | Docker Compose, then Kubernetes | One VPS runs the MVP. The same images scale out when concurrency demands it. |