Voice-controlled video looping for Chrome.
Loop any section, adjust speed, and bookmark moments hands-free — or use the draggable loop bar and customizable presets for precise control with your mouse.
Say "loop last 30 at 75" — and SetLoop loops the last 30 seconds at 75% speed. Say "loop stop" when you're done. Works on any site with HTML5 video — YouTube, Vimeo, Coursera, and more. Perfect for any tutorial videos.
Or load unpacked:
- Clone this repo and run
node build.js - Open
chrome://extensionsand toggle on Developer mode - Click Load unpacked and select the
dist/folder
| Command | What it does |
|---|---|
loop last 30 at 75 |
Loop the last 30s at 75% speed |
loop last 20 at 50 ramp |
Start at 50%, increase speed each loop |
loop stop |
End the loop, restore speed |
wider / tighter |
Adjust the loop start ±2s |
slower / faster |
Adjust speed ±25% |
speed 63 |
Set an exact speed (any %) |
back 10 / forward 10 |
Skip seconds |
bookmark |
Save the current timestamp |
mic off |
Turn off the microphone |
Strict parsing (on by default): commands require a
loopprefix — sayloop stop,loop slower— so ambient speech from the video can't trigger actions by accident. Turn it off in the popup to use bare commands likestopandslower. Or use Push-to-Talk (hold`), which never misfires.
- Always On — mic stays on; speak commands anytime.
- Push-to-Talk — hold the
`key, speak, release. Most reliable in noisy rooms.
| Shortcut | Action |
|---|---|
Alt+V |
Toggle voice on/off |
Alt+B |
Bookmark the current time |
Hold ` |
Push-to-Talk (when in PTT mode) |
SetLoop uses Chrome's built-in SpeechRecognition API. While the mic is active, Chrome transmits audio to Google's speech service and returns a transcript. SetLoop reads that transcript, matches a command, and acts — it never stores or forwards the audio itself.
- No accounts, analytics, tracking, or telemetry
- No host permissions
- Bookmarks and preferences stay on your device via
chrome.storage.local
Full policy: PRIVACY.md
| Permission | Why |
|---|---|
activeTab |
Access the current tab when you activate SetLoop |
storage |
Save bookmarks and preferences locally |
scripting |
Inject the overlay/control script on the current tab |
- Manifest V3, zero runtime dependencies, no build-time compilation — vanilla JS
- CSP:
script-src 'self'; object-src 'self' SpeechRecognitionruns in the content script (page origin), so user-gesture propagation is clean and mic permission follows the standard per-site prompt
node build.js
Output lands in dist/. Load that folder as an unpacked extension.
Development workflow
For local development, load the repo root (not dist/) as the unpacked extension. The source has DEBUG = true, so console logs ([SetLoop] …) appear in DevTools — invaluable for diagnosing SpeechRecognition lifecycle issues. Only load one copy at a time (root or dist, never both).
chrome://extensions → Load unpacked → select repo root
build.js strips DEBUG to false when producing dist/ for shipping. Never ship the root folder; always ship dist/.
SetLoop/
├── manifest.json, *.js, *.html, *.css ← extension source
├── build.js ← build script (zero deps)
├── icons/ ← extension + website icons
├── docs/ ← README demo GIF + screenshots
├── site/ ← Cloudflare Pages root (setloop.app)
│ ├── index.html ← landing page
│ ├── privacy/index.html ← auto-mirrored from root privacy.html
│ └── icons/ ← auto-mirrored from root icons/
├── dist/ ← extension build output (gitignored)
└── .github/workflows/build.yml ← CI: builds extension on every push
build.js produces dist/ for the Chrome Web Store and syncs privacy.html + icons/ into site/, so setloop.app/privacy always matches the in-extension privacy page.
© 2026 Harrison Daniel. All rights reserved.
This code is source-available, not open source. It's published here for portfolio and reference viewing only. You're welcome to read it, but copying, reuse, modification, or redistribution isn't permitted without written permission. See LICENSE for details.