Volume Control adds a simple per-site volume control to your browser. It can lower volume, boost HTML5 audio and video above the normal browser limit, and optionally play stereo audio as mono. The extension is useful for quiet videos, uneven site volume, embedded players, and pages that do not provide enough audio control on their own.
Settings can be remembered per site, and you can exclude sites where you do not want the extension to run. Volume Control supports HTML5 video and audio only; it does not support Flash.
- Volume Control cannot run on browser system pages such as
chrome://,edge://,about:, extension pages, or other protected browser UI. - DRM-protected or otherwise restricted media may block volume boosting, mono routing, or audio graph access.
- Some cross-origin media can only use fallback volume control when browser security rules prevent WebAudio routing. In that fallback mode, volume can still be lowered, but boosting and mono may be unavailable.
- Sites with unusual, heavily customized, or late-changing WebAudio graphs may not be fully controllable in every playback path.
Alt+Shift+Up: Increase volume by 1 dB.Alt+Shift+Down: Decrease volume by 1 dB.Alt+Shift+0: Reset volume to 0 dB.Alt+Shift+M: Toggle mono audio.Unassigned due to 4 hotkey limit, edit in firefox/chrome settings [chrome://extensions/shortcuts]: Activate the extension.Unassigned due to 4 hotkey limit, edit in firefox/chrome settings [chrome://extensions/shortcuts]: Toggle mute.
Browser shortcut settings can be used to remap or disable these defaults. Pin the extension icon to the toolbar to see native badge feedback while adjusting volume.
Volume Control does not collect, transmit, sell, share, or store any personal information outside your browser.
The extension does not use analytics, telemetry, tracking pixels, remote logging, accounts, advertising IDs, or any external server for data collection. Your volume settings, mono setting, excluded sites, remembered sites, whitelist or blacklist mode, and debug preference are stored only in your browser's local extension storage.
The extension reads page audio/video elements locally in your browser only so it can apply the volume and mono settings you choose. This processing happens on your device. No browsing history, page content, audio content, media titles, URLs, or settings are sent to the developer or to any third party.
Volume Control asks for the browser permissions needed to control audio reliably across modern websites:
storage: Saves your volume settings, mono setting, remembered site settings, exclusion list, whitelist/blacklist mode, and debug preference locally in your browser.activeTab: Lets the popup identify and update the current tab after you interact with the extension, without requesting broader tab access.<all_urls>host permission: Allows the content scripts to run on websites where audio or video may exist. This is needed because users can play HTML5 media on almost any site, and the extension has to access page-local media elements and WebAudio connections to change their volume.document_startcontent script timing: Installs the page audio hooks before sites createAudio,AudioContext, media elements, or WebAudio destination connections. Loading later can miss audio graphs that are created during early page startup.all_framescontent script access: Lets the extension work with audio/video inside embedded frames, such as video players, social embeds, and media hosted from another domain. Without frame access, only top-level page media would be controllable.file:///*content script match: Allows the extension to work on local media files when the browser permits extension access to file URLs.
AMO/Chrome Web Store review note: the broad host access, early document_start injection, and all_frames access are used only to detect and route page-local HTML5 media and WebAudio before playback begins. Volume Control does not collect browsing history, inspect page content for analytics, inject ads, or send page URLs, media metadata, audio content, or settings to any server.
Version 6.4 β Patch Notes
- New Dedicated mute channel (independent of the volume slider)
- New Native element.muted for fallback-only media (Bluetooth-friendly)
- New "MUTE" browser-action badge
- New Muted-state slider dim + tooltip
- New Mute checkbox in Remembered Settings
- Fixed effectiveGain() regression that silenced audio when extension was disabled
- Optimized Skip redundant enforceBoostLimit in setMute response path
Version 6.3 β Patch Notes
πΆ Bluetooth fixes (7) β context suspension/closing, unrouting at unity gain
π Volume spike fixes (6) β smoother ramps, skip redundant reconnects, improved transition ordering
π Critical regressions fixed (2) β replay break, onstatechange crash
π New features (2) β heartbeat + graceful degradation, bridge version negotiation
β‘ Performance (6) β debouncing, caching, WeakRef, skipβredundantβsync
π Robustness (5) β boost limit, raceβcondition fixes, improved callApi error handling
βΏ Accessibility (2) β focus management, ARIA liveβregion updates
π§Ή Code cleanup (12) β extracted helpers, removed duplicates, deadβcode removal
Version 6.2 β Patch Notes
- Added browser hotkeys for volume up/down, reset, and mono toggle
- Added native toolbarβbadge volume feedback for hotkeys and popup adjustments
- Hotkey changes now update remembered settings when the current site is already remembered
Version 6.1 β Patch Notes
- Removed an unused JS library
- Reduced Bluetooth idle power usage by disposing audio sessions more cleanly on stop
Version 6.0 β Patch Notes
- Added Manifest V3 pageβworld audio integration for stricter CSP sites and appβstyle audio
- Improved detection for dynamic audio/video elements and detached
Audionodes - Reduced Bluetooth idle popping by avoiding generic pageβinteraction resumes and lazyβloading audio hooks
- Improved rememberedβsite settings on appβstyle pages and subdomains
- Restored boosting for app pages that create WebAudio connections before volume is adjusted
- Added direct Howler masterβgain routing for sites that hide their audio graph internals
- Added crossβorigin media guard/fallback so boosted CDN audio keeps playing when browsers block routed gain
- Added automated Firefox + Chrome package builds with separate SVG/PNG manifest icons
- Removed an unused thirdβparty DOM watcher dependency
- Build zips now use AMOβcompatible forwardβslash archive paths
- Updated project license notice to include Chaython Meredith
Planned features: Added to Chrome Web Store. Looking for donations, to buy chrome store developer license
| File | World / Context | Has window? |
Has chrome.*? |
Can Patch Page JS? | Purpose | Why It Must Be Separate |
|---|---|---|---|---|---|---|
shared.js |
Loaded into multiple contexts (MAIN + ISOLATED) | β | β (guarded) | β | Pure utility library β dB conversion, media element helpers, domain parsing, bridge constants, error helpers | The only file that can appear in multiple contexts; guards all chrome.* calls so it doesn't crash in MAIN world |
page-audio-hook.js |
MAIN world content script | β
Page's window |
β | β Yes | Patches AudioNode.prototype.connect, HTMLMediaElement.prototype.volume, HTMLMediaElement.prototype.play, window.Audio, document.createElement to insert gain nodes into the page's audio graph |
Must run in MAIN world β prototype patches only affect code in the same JS realm; extension APIs are stripped from MAIN world for security |
cs.js |
ISOLATED world content script | β
Clean window |
β | β | Content script bridge β reads/writes chrome.storage, handles messages from popup/background, syncs state to page-audio-hook.js via window.postMessage, manages fallback volume for cross-origin/DRM media |
Must run in ISOLATED world to access chrome.storage and chrome.runtime APIs; communicates with MAIN world via postMessage |
background.js |
Service worker (background) | β No DOM | β | β | Handles keyboard shortcuts (Alt+Shift+Up/Down/0/M), shows native volume feedback badge, manages per-site remembered settings |
Must be a service worker β runs globally (not per-tab), has no DOM access, gets killed when idle; can't be merged with page-context scripts |
popup.js |
Popup page (popup.html) |
β Own DOM | β | β | Popup UI logic β volume slider, mono toggle, remember-site checkbox, enable/disable switch, debounced storage writes, focus management for accessibility | Runs in popup.html's isolated DOM; separate from options page because popup logic and options logic have no overlapping DOM concerns |
options.js |
Options page (options.html) |
β Own DOM | β | β | Options UI logic β blocklist/whitelist management, remembered-sites editor, debug mode toggle, live storage sync | Runs in options.html's isolated DOM; separate from popup because it manages different UI with different lifecycle (stays open vs. closes on action) |
manifest.json |
Extension manifest | β | β | β | Declares permissions, content scripts (with world specification), background service worker, action popup, options page, keyboard commands, Firefox compatibility | Defines which scripts load in which world; the only place where the MAIN/ISOLATED split is configured |
popup.html |
Popup document | β | β | β | Popup markup β volume slider, mono/remember/active toggles, settings button, exclusion message, error display | Required entry point for browser.action.default_popup |
popup.css |
Popup styles | β | β | β | Popup styling β slider, switches, layout, dark mode support | Keeps presentation separate from popup logic |
options.html |
Options document | β | β | β | Options markup β whitelist mode toggle, blocklist editor, remembered-sites editor, debug mode toggle | Required entry point for options_ui.open_in_tab |
ico.svg |
Extension icon | β | β | β | Toolbar icon (96Γ96 SVG) | Referenced by manifest.json icons and action.default_icon |
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Page's JavaScript (MAIN world) β
β β
β page-audio-hook.js β
β β’ Patches AudioNode.prototype.connect β
β β’ Patches HTMLMediaElement.prototype.volume β
β β’ Patches HTMLMediaElement.prototype.play β
β β’ Has NO access to chrome.* APIs β
β β
ββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββββ
β
β window.postMessage (bridge)
β
ββββββββββββββββββββΌβββββββββββββββββββββββββββββββββββ
β Content Script (ISOLATED world) β
β β
β cs.js β
β β’ Reads/writes chrome.storage β
β β’ Handles messages from popup/background β
β β’ Syncs state to page-audio-hook.js β
β β’ CANNOT patch page prototypes β
β β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
This split is non-negotiable. Chrome's MV3 security model strips chrome.* from MAIN world scripts, and ISOLATED world scripts can't modify page prototypes. The two scripts communicate via window.postMessage β the only bridge between worlds.
shared.js is not a context β it's a library loaded into multiple contexts:
// manifest.json β shared.js appears in BOTH content script entries
{
"js": ["shared.js", "page-audio-hook.js"], // MAIN world
"world": "MAIN"
},
{
"js": ["shared.js", "cs.js"] // ISOLATED world (default)
}It guards all chrome.* calls with optional chaining (if (!browserApi?.storage) return ...) so it doesn't crash when loaded in MAIN world where browser/chrome are undefined.
5 execution contexts β 5 files (background, page-audio-hook, cs, popup, options) 1 shared library β shared.js (loaded into 3 of the 5 contexts)
This is the minimum possible file count given the WebExtension API's security constraints.
Create Firefox and Chrome zip packages:
.\scripts\build.ps1The script writes clean packages to dist/, using ico.svg for Firefox and chrome.png for Chrome. The bundled zips exclude repo files and README.md.
Other Useful Extensions: https://github.com/Chaython/TogglePIP (Allow a site to run PIP consistently with an [Left Alt]+[P] toggle.) https://github.com/Chaython/NTP (A NTP extension that offers custom shapes, colors, search providers....)



