Framehold is a Chrome extension that plays local video files inside a real browser tab: pick a file from disk or drop it in, add subtitles if you want, and watch with standard HTML5-style controls. Nothing is uploaded—decode and playback happen in your browser.
This repo includes a static landing page: index.html. In the GitHub repo go to Settings → Pages, set Source to Deploy from a branch, choose main and / (root), then save. The site will be served at:
https://hashim1999164.github.io/framehold/
- Product page (GitHub Pages)
- Primary use case: Google Meet (and similar)
- What Framehold is / isn’t
- Technical specifications
- Features
- Project structure
- Setup & build (includes GitHub Releases zip)
- Keyboard shortcuts
- Limitations
- Privacy & security
- Contributing
- License
- Name
A common frustration: you want to watch a locally stored movie (e.g. an MP4 on your drive) with someone else over Google Meet (or another app that lets you share a Chrome tab).
- The system video player is not a tab, so you can’t select it in “Share a Chrome tab.”
- You need a normal tab that plays your file so Meet can capture video (and, when offered, tab audio).
Typical flow with Framehold:
- Install the extension and click the Framehold toolbar icon → a player tab opens (
player.html). - Use Open video or drag-and-drop your file onto the page.
- In Meet: Present → A Chrome tab → choose the Framehold tab.
- If Chrome shows it, turn on Share tab audio so sound is included in the call.
That’s the main intended purpose: a simple, minimal, working path from “file on disk” to “shareable tab” for co-watching. Everything else (subtitles, PiP, extra options) is optional on top of that.
| Is | A dedicated extension page that uses <video>, URL.createObjectURL, and optional FFmpeg.wasm for remux—not a content script on arbitrary sites. |
| Is | Suited for macOS and Windows (any desktop Chrome that supports MV3 + the same web APIs). |
| Isn’t | A streaming service, transcoding server, or cloud pipeline. |
| Isn’t | A guarantee that every MKV or exotic codec will play; Chrome still decides what it can decode after remux. |
| Area | Details |
|---|---|
| Platform | Chrome extension, Manifest V3 |
| Entry | Toolbar action → opens player.html in a new tab (via background.js service worker) |
| Permissions | storage — persisted UI preferences, optional saved volume, optional resume position, options panel state |
| Host permissions | None (no <all_urls> or broad network access for the player) |
| CSP (extension pages) | script-src 'self' 'wasm-unsafe-eval' — required for WebAssembly (FFmpeg fallback) |
| Playback | Native <video> + Blob / object URLs from File; optional FFmpeg.wasm remux to MP4 (-c copy) when the user chooses it after a decode error |
| Subtitles | Parsed in-page: SRT, WebVTT; custom overlay + requestAnimationFrame sync |
| Large dependencies | @ffmpeg/ffmpeg, @ffmpeg/core, @ffmpeg/util — copied into ffmpeg/ by npm postinstall (~tens of MB including .wasm) |
Supported inputs (non-exhaustive): common containers/extensions such as MP4, M4V, WebM, MKV, MOV, plus others recognized by the file picker / video/* where Chrome allows.
Browsers: Chromium-based Chrome (primary). Other Chromium browsers with MV3 + same APIs may work but are not the focus.
- Open video file picker; drag-and-drop; open folder (File System Access API when available, or directory input) for auto-matched subtitles by basename.
- Manual subtitle file pick; SRT and VTT parsing; delay, size, color, position, font, outline, caption background opacity.
- Play / pause, seek, volume, mute, playback speed, loop, optional autoplay on open.
- Skip buttons and configurable skip step (keyboard arrows / J / L).
- Time display: elapsed/total or elapsed + time remaining.
- Picture-in-picture when the browser exposes it.
- Screen Wake Lock (optional) while playing, when supported.
- Fullscreen applies to the video container only; minimal bottom bar on pointer movement, auto-hides shortly after the pointer stops.
- CSS filters: brightness, contrast, saturation, hue, blur, grayscale, sepia; rotation and flip; object-fit (contain / cover / stretch).
- Themes: system / dark / light.
- Optional stats overlay (resolution,
readyState, rate, buffered ranges, dropped frames when reported). - “More options” collapsible panel; settings persisted with
chrome.storage.local. - Keyboard shortcuts (see below); Shortcuts dialog in the UI.
- On MediaError, user may remux with FFmpeg (WASM) (stream copy to MP4). Still local; can be heavy on CPU/RAM for large files.
chromeVideoPlayer/
├── index.html # Product / marketing page (GitHub Pages)
├── manifest.json # MV3 manifest, icons, CSP
├── background.js # Opens player tab on action click
├── player.html # Player shell + options + dialogs
├── styles/
│ └── player.css
├── js/
│ ├── app.js # UI wiring, prefs, fullscreen, keyboard
│ ├── file-handling.js # Pickers, folder scan, basename matching
│ ├── subtitle-manager.js# SRT/VTT parse, overlay sync
│ ├── playback-engine.js # Object URLs, video error surface
│ └── ffmpeg-fallback.js # Lazy FFmpeg.wasm remux
├── ffmpeg/ # Vendored FFmpeg WASM (from npm postinstall)
│ ├── pkg-core/
│ ├── pkg-ffmpeg/esm/
│ └── pkg-util/esm/
├── icons/ # toolbar / store icons (PNG + optional source)
├── scripts/
│ ├── vendor-ffmpeg.mjs # Copies FFmpeg packages into ffmpeg/
│ └── package-extension.sh # Builds dist/framehold-<version>.zip for releases
├── package.json
├── LICENSE # MIT
└── README.md
- Open Releases and download
framehold-<version>.zip(e.g.framehold-1.0.0.zipfor v1.0.0). - Unzip; use the inner
frameholdfolder (the one that containsmanifest.json). - Chrome →
chrome://extensions→ Developer mode → Load unpacked → select that folder. - Click the Framehold toolbar icon to open the player.
The release zip is built from git and includes ffmpeg/—no npm install needed.
-
Clone the repository.
-
Run
npm install(vendors WASM intoffmpeg/). -
Load unpacked the repo root (same as above).
-
To build a release zip locally:
npm run package
Output:
dist/framehold-<version>.zip(frommanifest.jsonversion). Maintainers attach this file when publishing a GitHub Release and tagv<version>(e.g.v1.0.0).
Note: If you skip npm install, the FFmpeg remux path will be missing files under ffmpeg/ until you run it.
| Key | Action |
|---|---|
| Space | Play / pause |
| F | Toggle fullscreen (video area) |
| M | Mute |
| C | Toggle subtitles |
| P | Picture-in-picture (if supported) |
| ⌘/Ctrl + O | Open video |
| ← / → | Seek by skip step |
| J / L | Skip back / forward |
| ↑ / ↓ | Volume |
| [ / ] | Slower / faster (speed presets) |
| 0–9 | Jump to 0%–90% of duration |
| , / . | Small backward / forward step (pauses) |
| R | Reset video look (filters & transform) |
| ? | Open shortcuts help |
- Codecs are ultimately limited by Chrome and the container; MKV may still fail after remux if streams aren’t playable in the browser.
- FFmpeg.wasm remux is in-memory and can be slow or memory-heavy on very large files.
- Same-folder subtitles without picking a folder: the web platform doesn’t expose sibling files from a single-file pick; use Open folder or drop video + subtitle together.
- Tab audio in Meet depends on Chrome + Meet; if audio is blocked, check Meet/Chrome present options and OS privacy settings.
- No analytics or remote playback pipeline in this project.
- Video and subtitle bytes stay in the browser;
storageholds only settings (and optional volume / resume metadata as implemented). - Optional “Allow access to file URLs” for extensions is not required for the normal picker / drag-and-drop flow (see in-app Privacy text).
Contributions are welcome. If Framehold is useful to you—or you want to improve the Meet workflow, subtitles, performance, or docs—forks, issues, and pull requests are appreciated.
Ideas for PRs:
- Bug fixes, accessibility, or UI polish.
- Clearer Meet / tab-share documentation or screenshots.
- Safer handling of edge cases (huge files, odd subtitles, rare codecs).
- Tests or small refactors that don’t bloat the extension size without need.
- License changes only when there’s a strong reason—the project uses MIT (see
LICENSE).
Before a PR:
- Fork the repo and create a branch for your change.
- Describe what and why in the PR (plain English is fine).
- Manually smoke-test: load unpacked, open a local file, subtitles, fullscreen, and (if you touch it) remux.
MIT — Copyright (c) 2026 Hashim1999164.
Framehold — your frames (video), held where you want them: on your device, in a tab you can share.