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.

The room

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.

Ayla is holding the remoteBoth connected · in sync within 40 ms

“We have all the time in the world.”

Pause for both00:48:12 / 02:07:33Subtitles: English
  • 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.
Sync

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.

Scope

Ten components, nothing speculative

Each part of the platform has a single job and a clear owner in the codebase.

01

Ingestion

Resumable uploads land in a private MinIO bucket, checksummed and probed before anything else happens.

02

Transcoding

FFmpeg builds a three-rung HLS ladder in fragmented MP4 with 2-second segments and aligned keyframes.

03

Subtitles & chapters

Embedded tracks are extracted, normalised to WebVTT, and stored alongside chapter markers and language tags.

04

Private rooms

Every room is unlisted. Access comes from a single-purpose invite link with an expiry and a revoke switch.

05

Sync service

One WebSocket process owns the canonical room clock, orders commands, and rejects stale ones.

06

Drift correction

Clients compare against server time every second and close gaps with playback-rate nudges, not seeks.

07

Chat & reactions

A collapsible side rail. Ephemeral by default, never the centre of the screen.

08

Moderation tools

Admin routes to inspect an asset, kill a room, revoke a link, and purge media on request.

09

Technical analytics

Startup time, rebuffer ratio, drift percentiles, join success. No behavioural profiling.

10

Access control

Short-lived signed media URLs, per-owner object prefixes, and hard deletion that actually deletes.

Stack

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.

LayerChoiceWhy this one
ClientNext.js + React, hls.jsOne codebase for desktop and mobile web; hls.js gives frame-accurate position control and buffer telemetry that native HLS hides.
APIFastify (TypeScript)Small, fast, schema-validated routes. Shares types with the client, so command payloads cannot drift.
Realtimews over WSSControl traffic is a few hundred bytes per second. WebRTC would add NAT traversal and codec negotiation for no gain.
DatabasePostgreSQL 16Rooms, assets, invites, audit trail, and the job queue. LISTEN/NOTIFY replaces a broker at this size.
Object storageMinIOS3 API without the bill. Private buckets, per-owner prefixes, server-side encryption, versioning off for media.
MediaFFmpegLadder generation, subtitle extraction, poster frames, sprite sheets. Nothing else is needed.
EdgeCaddy or NginxTLS, HTTP/2, media token verification, and an optional segment cache in front of MinIO.
IdentityMagic-link sessionsEmail link plus signed cookie. No passwords to leak; Keycloak stays on the shelf until SSO is a real requirement.
ObservabilityPrometheus, Grafana, Loki, OpenTelemetryThe standard open stack. Dashboards track playback health, not people.
RuntimeDocker Compose, then KubernetesOne VPS runs the MVP. The same images scale out when concurrency demands it.