Skip to content

Repository files navigation

SameFrame

SameFrame is a minimal, local-first watch room for video files everyone already has. It synchronizes playback without uploading or transferring the video itself.

Live app: sameframe.io

SameFrame interface

Features

  • Shareable rooms at sameframe.io/{room-id}
  • Automatic host assignment and host transfer
  • Immediate room reconnection with session resumption
  • A 15-second host recovery window before automatic host transfer
  • Optional unguessable private-room links
  • Local SHA-256 file validation before playback unlocks
  • Background-worker hashing so large files do not freeze the interface
  • Synchronized play, pause, and seeking
  • Clock-offset measurement, ordered playback revisions, and automatic drift correction
  • Synchronized playback speeds from 0.5× to 2×
  • Host-only or everyone-can-control playback modes
  • Host-controlled video replacement
  • Per-viewer captions from browser-exposed tracks, local SRT or VTT files, or direct links
  • Automatic caption positioning with local size, color, and background preferences
  • Subtitle timing offsets and UTF-8, UTF-16, and Windows-1252 subtitle support
  • Filename-based web search for subtitle files
  • Auto-hiding controls, ±10-second seek, picture-in-picture, fullscreen, and keyboard shortcuts
  • Join, leave, play, and pause activity notifications
  • Participant list with host transfer and removal controls
  • Screen wake lock during playback and installable PWA support
  • A clear-saved-data control for local room, media, and subtitle records
  • No accounts, chat, video uploads, or host-to-peer file transfer
  • No analytics, telemetry, or behavioral tracking

How it works

  1. Create a URL-safe room name or open an existing room link.
  2. Choose a unique display name. The first participant becomes the host.
  3. The host selects a local video file. Every other participant selects their own copy of that file.
  4. SameFrame hashes each file locally and unlocks the player only when the hash matches the host's selection.
  5. Playback events and timing travel through the room's WebSocket connection.

Room IDs contain lowercase letters, numbers, and single hyphens. Display names must be unique within a room.

Choose Create a private room to generate an unguessable room ID and key. The key stays in the URL fragment and must be present in the complete shared link.

Privacy model

Video bytes never leave a participant's device. SameFrame sends the selected filename, SHA-256 hash, file size, duration, participant readiness, playback timing, and control permissions to the server.

The server keeps one compact JSON snapshot of active room metadata and session identities so a deployment or process restart does not destroy an active room. It never stores video bytes or subtitle text. Disconnected participants expire after two minutes, and empty rooms expire after six hours. Explicitly leaving the last active session deletes the room immediately. Set ROOM_STATE_PATH to a persistent-volume path in production.

The filename and hash are room metadata, not secrets. Do not use a sensitive filename or rely on SameFrame for access control.

Caption choices are local to each viewer. Local SRT and VTT files stay in the browser. Direct caption links are fetched by the viewer's browser and require the source server to allow cross-origin requests.

SameFrame stores the current room, display name, and a random room-session ID in local storage. This lets the browser reconnect immediately and resume the same participant after a brief network interruption. Explicitly leaving clears the saved identity.

On browsers that support the File System Access API, SameFrame stores permission to the selected local video in IndexedDB. Returning with the same room and display name restores the file automatically when permission remains granted, while the saved hash and metadata avoid hashing an unchanged file again. The video is still read from its original local location and is never copied to SameFrame or uploaded.

If persistent file handles are unavailable, or a video was added through drag and drop, the browser requires one local reselect after a full refresh. SameFrame then checks the filename, size, and modification time and reuses the saved hash when the file is unchanged.

Selected built-in captions, local SRT/VTT text, linked subtitle text, and the active caption state are also stored locally per room, display name, and video hash. Linked subtitles restore from the saved local text instead of depending on the source URL during every reconnect.

The How SameFrame works modal has a control that clears SameFrame's saved room sessions, file references, hashes, subtitle text, and subtitle preferences from the current browser without touching unrelated site data.

Player shortcuts

  • Space or K: play or pause
  • J / L: back or forward 10 seconds
  • / : back or forward 5 seconds
  • / : change volume
  • M: mute
  • C: toggle captions
  • F: toggle fullscreen
  • P: toggle picture in picture
  • 09: jump to 0%–90% of the video
  • Home / End: jump to the beginning or end
  • < / >: decrease or increase playback speed
  • , / .: previous or next frame while paused

Shortcuts work anywhere on the room screen unless focus is inside a button, link, or form control. Browser and hardware media controls register through the Media Session API for play, pause, stop, seeking, position, playback speed, metadata, and picture in picture where supported.

Media compatibility

SameFrame plays the selected file through the browser, so the browser must support its container and codecs. The app detects common unsupported Matroska audio formats and shows a local warning instead of silently presenting a muted video.

For example, Chromium browsers generally cannot decode Dolby Digital Plus (E-AC-3) audio inside an MKV. A compatible MP4 can be created while copying the video stream unchanged and converting only the audio:

ffmpeg -i input.mkv -map 0:v:0 -map 0:a -c:v copy -tag:v hvc1 -c:a aac -b:a 512k -movflags +faststart sameframe.mp4

Everyone in the room must select the same resulting file so its SHA-256 hash matches.

Architecture

  • React and Vite client
  • Express production server
  • ws WebSocket room transport
  • In-browser streaming SHA-256 via hash-wasm in a Web Worker
  • A debounced, atomic JSON room snapshot with no database

The production server serves the built client and WebSocket endpoint from one process. SameFrame is designed for one lightweight server instance. A multi-instance deployment would require a shared WebSocket adapter.

Local development

Requirements: Node.js 20 or newer and npm.

git clone https://github.com/shiv213/sameframe.git
cd sameframe
npm ci
npm run dev

Open http://localhost:5173. Use two browser windows to test a room.

Commands

npm run dev    # Vite client and WebSocket server
npm test       # Node test suite
npm run test:e2e # Two-session Chromium tests
npm run build  # Production client build
npm run check  # Tests and production build
npm start      # Serve the built app on PORT (default 8787)

Production

npm ci
npm run build
npm start

The deployment must support WebSocket upgrades at /ws. Set PORT if the platform does not inject it automatically, and terminate TLS at the platform or reverse proxy so production clients use wss://.

For restart-safe rooms, attach a small persistent volume and set:

ROOM_STATE_PATH=/data/rooms.json

Only the latest compact room snapshot is stored. SameFrame does not need Redis or a database for a single-instance deployment.

Contributing

Contributions are welcome. Read CONTRIBUTING.md before opening a pull request. For vulnerabilities, follow SECURITY.md instead of opening a public issue.

License

MIT © 2026 Shiv Trivedi

About

A local-first synchronized watch room for video files everyone already has.

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages