Reliable play, seek, speed and volume controls for any HTML5 video on the web — that site overlays can't block.
Install from Chrome Web Store • Features • Build from Source • Contributing
Ever tried to scrub through a video on a social media site, only to find invisible layers that pause, mute, or open the post when you click? Or a site that hides the native controls entirely?
OnePlay puts its own control bar on top of the video, in an isolated Shadow DOM layer. It doesn't touch the site's player, so it keeps working when the site changes its layout or class names.
![]() Overlay-proof control bar Works on feeds, Shorts, Reels and preview cards. |
![]() Quick settings popup Themes, position, layout and per-site overrides. |
- 🎛️ Full playback control: play/pause, seek, volume, mute, fullscreen, and speed from 0.5× to 3×.
- 🛡️ Overlay-proof: the controller sits at the top z-index layer with its own pointer handling, so transparent tap-to-mute/pause layers can't block it.
- 🧩 Doesn't depend on the site: it mounts in a Shadow DOM at the document root and never injects into the host player, so site redesigns don't break it.
- 📐 Layouts & positions
- Full: scrub bar, time, volume, speed, fullscreen.
- Minimal pill: compact
-5s / Play / +5swith a slim progress bar. - Automatic 2-row mode on narrow videos.
- Dock inside or outside, at the top or bottom of the video.
- 🎨 4 built-in themes: Classic Dark, Midnight, Glass, Ocean Blue, plus a theme builder.
- ⌨️ Custom keyboard shortcuts: single keys or combinations (
Ctrl+Shift+P,Alt+M…). Shortcuts turn off automatically while you type in inputs. - 🎯 Smart video selection: prefers videos that are playing with sound, and supports several controllers at once when multiple videos are ≥70% visible.
- ⬇️ Download & stream recording (right-click the bar):
- Direct download for regular video URLs.
- Real-time recording of streamed (MSE / HLS / DASH) videos via
captureStream()+MediaRecorder, with a live● RECbadge. - Disabled on YouTube, per Chrome Web Store policy. DRM-protected streams can't be captured.
- 🔋 Lightweight: event-driven (
MutationObserver,IntersectionObserver,ResizeObserver), with no polling loops. - 🔒 Private: runs 100% locally. No analytics, no tracking, no accounts.
All shortcuts can be changed in the popup or the options page.
| Key | Action |
|---|---|
| Space | Play / Pause |
| ← / J | Seek backward (default 5s) |
| → / L | Seek forward (default 5s) |
| ↑ / ↓ | Volume ±5% |
| M | Mute / Unmute |
| F | Fullscreen |
Requirements: Node.js 18+ and npm 9+.
git clone https://github.com/Pouria7/OnePlay-Video-Control.git
cd OnePlay-Video-Control
npm install
npm run buildThen load it in Chrome:
- Open
chrome://extensions/. - Turn on Developer mode (top-right).
- Click Load unpacked and pick the generated
dist/folder.
npm run dev # rebuild on file changes
npm test # run the test suite (Vitest)Host page
├── Site DOM (React/Vue/…, obfuscated classes, invisible overlays)
│ └── <video>
└── OnePlay layer (independent)
└── <div class="ufvc-controller-root">
└── ShadowRoot
├── Isolated CSS reset
├── Control bar (fixed, z-index: 2147483647)
└── Context menu / REC indicator
OnePlay tracks the target video's bounding box and places its own UI over it. Because the UI lives outside the site's DOM tree, the site's overflow: hidden, reflows and class changes don't affect it.
Domain-specific behavior lives in small site adapters (Instagram, YouTube, plus a generic fallback), separate from the core engine. Settings are resolved in this order: per-site user settings → adapter defaults → global user settings → built-in defaults.
For the full design, see ARCHITECTURE.md.
Create src/content/adapters/<site>-adapter.ts:
import { SiteAdapter } from './site-adapter';
import { SiteSettings } from '../../storage/types';
import { domainMatches } from '../../utils/url';
export class TikTokAdapter implements SiteAdapter {
readonly id = 'tiktok';
readonly name = 'TikTok Adapter';
matches(url: URL): boolean {
return domainMatches(url.hostname, 'tiktok.com');
}
getConfigOverrides(): Partial<SiteSettings> {
return { minWidth: 200, minHeight: 300, visibilityThreshold: 0.6, allowMultiple: false };
}
}Then register it in src/content/adapters/adapter-manager.ts:
this.register(new TikTokAdapter());Add an entry to BUILTIN_THEMES in src/themes/default-themes.ts, then add a matching <option> in src/popup/popup.html. Themes are plain CSS variables (--ufvc-bg, --ufvc-text, --ufvc-accent, --ufvc-radius, --ufvc-blur, …).
Contributions are welcome!
- Fork the repo and create a branch:
git checkout -b feature/my-feature - Make your changes and run
npm test - Open a Pull Request
Bug reports and site-compatibility issues are very helpful. Please include the site URL and steps to reproduce.
OnePlay doesn't collect, store, or send any personal data. The only network request is an occasional check of a public version.json file for update notices. Read the full Privacy Policy.
- 🐛 Issues: GitHub Issues
- ✉️ Email: oneabout@outlook.com
- ⭐ If OnePlay is useful to you, please star the repo and leave a review on the Chrome Web Store!



