agentsclimarketplace

Watch together cinema

Skill bertbertov/claude-stack/skills/watch-together-cinema

A working Claude Code config from a solo builder who ships — 156 skills, 6 hooks, conductor routing pattern, auto-dedupe watch.

Install
npx -y skills add bertbertov/claude-stack --skill watch-together-cinema

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its author says it does

Copied from the file, not written here

Build a private, self-hosted "watch a movie together in sync" site for two people in different countries — synced play/pause/seek over WebSocket, WebRTC camera+mic call with a coturn TURN relay, a shared laser pointer, ffmpeg smooth-bitrate transcoding so it never buffers, and a token-gated room link. Use when someone wants to watch local video files in sync with a partner/friend remotely, replace Kosmi/Teleparty/Twoseven with something they own, stream a personal movie library over the web with synced controls, or add a video-call overlay to a video player. Triggers: "watch movies together long distance", "sync video playback two browsers", "self-hosted watch party", "Kosmi alternative", "stream my movie to my girlfriend with subtitles and sync".

SKILL.md

7.8 KB, ~1.9k tokens by cl100k_base, as published. Nobody here has run it

Watch-Together Cinema

A private cinema you own: upload a movie, get an unguessable room link, send it to one other person, and you both watch in sync (either side controls play/pause/seek) with an optional camera+mic call overlaid and a shared laser pointer. No Kosmi/Teleparty subscription, no per-message limits, no sending big files around.

Reference implementation lives in reference/ — a ~180-line FastAPI backend, a YouTube-style player, an admin page, plus nginx + coturn examples. Copy them, fill placeholders, deploy.

Architecture (the important distinction)

There are two separable problems and only one is hard:

  • Sync (mirror play/pause/seek between two browsers) — trivial: a WebSocket relay.
  • Delivering the video — the real work. You serve the file over HTTP with byte-range (so seeking works) directly from the host.
browser A ─┐                         ┌─ nginx :443 (TLS)
           ├── wss sync + signaling ──┤    ├── /media/*  → static file, byte-range (video)
browser B ─┘                         │    └── /        → uvicorn :8088 (FastAPI: pages, upload, WS)
                                     └─ coturn :3478   → WebRTC relay (camera/mic, NAT fallback)
  • Movie stream: peer downloads the file from your server. One viewer = trivial bandwidth.
  • Camera call: WebRTC peer-to-peer (cameras go directly between the two browsers). The WebSocket doubles as the signaling channel; coturn relays only when NAT/CGNAT blocks a direct path (common on mobile/home ISPs across countries).

Components (in reference/)

FileWhat it is
app.pyFastAPI: token-gated watch rooms, key-gated admin upload/list/delete, WebSocket that relays sync + WebRTC signaling + pointer, ffmpeg faststart-remux + .srt.vtt on upload. Serves video via StaticFiles (byte-range capable). Admin key from CINEMA_ADMIN_KEY env — no hardcoded secrets.
player.htmlThe watch room. Custom YouTube-style controls (auto-hide, scrub bar), keyboard (Space/K play, ←/→ 5s, J/L 10s, ↑/↓ volume, M mute, C subs, F fullscreen, P pointer), buffer-aware drift-correction sync, WebRTC call with draggable/resizable tiles, shared laser pointer. Has {{TURN_HOST}} / {{TURN_USER}} / {{TURN_PASS}} placeholders — fill before serving.
admin.htmlUpload (movie + optional subtitles) with progress, list, copy room link, delete. Key comes from ?key= in the URL.
nginx.conf.exampleReverse proxy: serves /media/ statically (native range), proxies everything else + WebSocket upgrade to uvicorn, client_max_body_size 0 for multi-GB uploads.
turnserver.conf.examplecoturn config (long-term credential, relay port range, external-ip).

Deploy (once)

  1. Server + Python:
    mkdir -p /opt/cinema/media /opt/cinema/static
    python3 -m venv /opt/cinema/venv
    /opt/cinema/venv/bin/pip install fastapi "uvicorn[standard]" python-multipart websockets
    cp reference/app.py /opt/cinema/app.py
    cp reference/player.html reference/admin.html /opt/cinema/static/
    apt-get install -y ffmpeg    # for faststart remux + srt→vtt
    
  2. Admin key (systemd unit):
    # /etc/systemd/system/cinema.service
    [Service]
    Environment=CINEMA_ADMIN_KEY=%GENERATE_A_RANDOM_HEX%   # openssl rand -hex 12
    WorkingDirectory=/opt/cinema
    ExecStart=/opt/cinema/venv/bin/uvicorn app:app --host 0.0.0.0 --port 8088
    Restart=always
    [Install]
    WantedBy=multi-user.target
    
    systemctl enable --now cinema
  3. Domain + TLS. Point a subdomain straight at the host (A record, DNS-only if behind Cloudflare — CF's free plan caps uploads at 100 MB and restricts video, so the media path MUST bypass the proxy). Then certbot --nginx -d cinema.example.com with nginx.conf.example.
  4. TURN (coturn) for the camera call to survive cross-country NAT:
    apt-get install -y coturn
    cp reference/turnserver.conf.example /etc/turnserver.conf   # set your own user:password, realm, external-ip
    sed -i 's/#TURNSERVER_ENABLED=1/TURNSERVER_ENABLED=1/' /etc/default/coturn
    systemctl enable --now coturn
    
    Put the same TURN host/user/password into player.html's {{TURN_*}} placeholders. Verify the relay actually works: turnutils_uclient -y -u USER -w PASS -n 4 YOUR_HOST should report Total lost packets 0.

Use

  • Admin: https://cinema.example.com/admin?key=YOUR_ADMIN_KEY → upload movie (+ .srt) → wait for ready → copy the room link.
  • Send the room link to the other person. Both open it → Start watching together → synced. Press P and move the mouse for the shared pointer. Click the call icon to turn on cameras (earbuds recommended — speakers + open mic = echo).

The buffering fix (this is the #1 real-world problem)

Movies are usually VBR — a file that averages ~2 Mbit/s spikes to ~10 Mbit/s on busy scenes. While a camera call also runs, those spikes outrun a home/mobile connection and the movie stutters. Fix: transcode to a capped constant bitrate before uploading (no spikes, visually identical). On a machine with an NVIDIA GPU:

ffmpeg -i input.mp4 -map 0:v:0 -map 0:a:0 \
  -c:v h264_nvenc -preset p5 -rc vbr -b:v 2500k -maxrate 3000k -bufsize 6000k \
  -profile:v high -pix_fmt yuv420p -c:a aac -b:a 160k -movflags +faststart output.mp4
# CPU-only: swap -c:v h264_nvenc ... for -c:v libx264 -crf 21 -maxrate 3000k -bufsize 6000k

Extract embedded subtitles to upload alongside: ffmpeg -i input.mp4 -map 0:s:0 subs.srt

Do heavy transcodes on a GPU box (minutes), not a CPU-only VPS (hours). The app's on-upload step only does a fast stream-copy faststart remux (-c copy -movflags +faststart) — instant for already-H.264/AAC MP4s — plus .srt.vtt.

Design notes worth keeping

  • Sync is buffer-aware. Don't hard-seek on small drift (causes visible skipping). Only seek when drift > ~3s (someone scrubbed); for smaller drift the behind side nudges playbackRate to 1.04 to glide back. Suppress state broadcasts while buffering, so a stalled peer doesn't drag the other backward.
  • Fullscreen the wrapper, not the <video>. Native video-fullscreen hides sibling elements — so the camera tiles and pointer vanish. Call requestFullscreen() on the container that holds video + tiles + pointer layer.
  • Cap the call bitrate (sender.setParameters maxBitrate ~400 kbps, 360p/20fps) so the call never starves the movie stream.
  • Privacy: room tokens and media filenames are unguessable random hex; admin is key-gated; pages carry noindex. It's private-by-obscurity for two people — fine for personal use, not a public multi-tenant service.
  • Codec: browsers only reliably play MP4 / H.264 / AAC. MKV/HEVC/AC3 must be transcoded (the capped-bitrate command above also fixes codec).

When NOT to use

  • More than a handful of simultaneous viewers, or a public audience → use a real streaming/CDN stack, not this.
  • You don't control a server with a domain → use Syncplay (each person keeps their own file copy) or a hosted watch-party service.

What ships with it: 5 files

35.4 KB alongside SKILL.md, 1 of them executable

Keep looking

Skills are one crate of 328,083. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.