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
- 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
- Create a URL-safe room name or open an existing room link.
- Choose a unique display name. The first participant becomes the host.
- The host selects a local video file. Every other participant selects their own copy of that file.
- SameFrame hashes each file locally and unlocks the player only when the hash matches the host's selection.
- 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.
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.
SpaceorK: play or pauseJ/L: back or forward 10 seconds←/→: back or forward 5 seconds↑/↓: change volumeM: muteC: toggle captionsF: toggle fullscreenP: toggle picture in picture0–9: jump to 0%–90% of the videoHome/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.
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.mp4Everyone in the room must select the same resulting file so its SHA-256 hash matches.
- React and Vite client
- Express production server
wsWebSocket room transport- In-browser streaming SHA-256 via
hash-wasmin 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.
Requirements: Node.js 20 or newer and npm.
git clone https://github.com/shiv213/sameframe.git
cd sameframe
npm ci
npm run devOpen http://localhost:5173. Use two browser windows to test a room.
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)npm ci
npm run build
npm startThe 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.jsonOnly the latest compact room snapshot is stored. SameFrame does not need Redis or a database for a single-instance deployment.
Contributions are welcome. Read CONTRIBUTING.md before opening a pull request. For vulnerabilities, follow SECURITY.md instead of opening a public issue.
MIT © 2026 Shiv Trivedi
