Skip to content

itsZapp/Mono-Mixdown

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Mono Audio Mixdown

A browser extension that forces stereo audio to mono, so single-channel ("broken") audio plays in both ears.

Manifest V3 Firefox Chrome Opera License No Tracking


What this is

Some videos, podcasts, and livestreams ship with audio mixed to only one channel. If you have hearing loss in one ear, you listen with one earbud, or you just find single-sided audio uncomfortable, that content is partially or completely silent. This extension fixes it by routing every <video> and <audio> element on a page through a small Web Audio graph that sums L and R into a mono signal, then writes that signal to both output channels.

It runs only on the sites you choose, stores its settings locally, and sends nothing to any server.

Browser support

Browser Supported Install
Firefox 115 and later Add-ons site or temporary load
Chrome Recent versions Web Store or unpacked load
Opera Chromium-based versions Chrome Web Store via "Install Chrome Extensions" addon, or unpacked load
Edge, Brave, Vivaldi Not officially tested, but should work Unpacked load

Features

  • Per-site override (force on, force off, or follow the global default).
  • Global default for sites without an override.
  • Adjustable sum gain (default 1.0 for full-volume accessibility playback, lower if proper stereo clips after summing).
  • Smooth crossfade when toggling, so there are no clicks or pops.
  • Catches dynamically inserted media (single-page apps, late-mounting players).
  • Status indicator in the popup showing whether the extension is active and how many media elements it's handling on the current page.
  • Zero data collection. Settings live in chrome.storage.local and never leave the browser.

Install

From an extension store

  • Firefox
  • Chrome coming soon!
  • Opera coming soon!

Manual install (Chrome, Opera, Edge, Brave, Vivaldi)

  1. Download or clone this repository.
  2. Open chrome://extensions (Opera: opera://extensions).
  3. Turn on Developer mode in the top right.
  4. Click Load unpacked and select the project folder.

Tip

On Opera, the Chrome Web Store version of this extension can also be installed by first adding the official "Install Chrome Extensions" addon from the Opera addons site.

Manual install (Firefox)

Temporary install (cleared when Firefox restarts, useful for testing):

  1. Open about:debugging#/runtime/this-firefox.
  2. Click Load Temporary Add-on.
  3. Select manifest.json from the project folder.

Permanent install (requires a signed XPI):

  1. Use web-ext to package the folder.
  2. Submit to addons.mozilla.org for signing, or self-host with an update_url.

Usage

Click the toolbar icon. The popup shows three controls:

  1. This site. Sets the override for the current origin. Choose "Use default", "Force on", or "Force off".
  2. Default for all sites. A single switch that applies everywhere no override is set.
  3. Sum gain. A slider from 0.30 to 1.00.

Note

Default sum gain is 1.0, not the conventional 0.7. The reason: at 1.0, audio that exists only on one channel plays at full volume in both ears, which is the accessibility goal. Lower the gain only if you mostly listen to legitimately stereo content and want clipping protection.

If a tab was already open when you installed the extension, reload it once so the content script can attach to the existing media elements.

How it works

Audio graph diagram (click to expand)
                       splitter      sumGain         merger
                       +-------+    +--------+      +--------+
                   +-->|   L   |--->|        |---0->| out L  |
                   |   |   R   |--->|  L+R   |---0->| out R  |--+
                   |   +-------+    +--------+      +--------+  |
                   |                                            |
                   |                                            v
   +------------+  |                                       +---------+
   | <video> /  |--+                                  +--->| monoOut |---+
   | <audio>    |                                     |    +---------+   |
   +------------+----------------------------------------+              v
                                                         |        +-------------+
                                                         |   +--->| destination |
                                                         |   |    +-------------+
                                                         v   |
                                                    +--------+
                                                    |bypassOut|
                                                    +--------+

Two parallel paths run from each media element:

  • Mono path. A ChannelSplitter separates L and R, both feed into a single GainNode (which sums them), and a ChannelMerger writes the summed signal to both output channels.
  • Bypass path. The original stereo source connects straight through.

A pair of gain nodes (monoOut and bypassOut) crossfade between the two paths over 50 ms when the user toggles the effect, so the transition is inaudible.

Why a parallel bypass path instead of disconnecting nodes?

Disconnecting and reconnecting Web Audio nodes on every toggle would cause clicks and risks race conditions with the audio thread. Keeping both paths permanently connected and crossfading their gains is cheap, glitch-free, and makes the toggle feel instant.

Why document_start and all_frames: true?

Many sites mount media elements before DOMContentLoaded, especially video players that use Media Source Extensions. Running at document_start and watching the DOM with a MutationObserver ensures the extension catches every element regardless of when it appears. all_frames: true covers embedded iframes, which is where most video players live.

Project layout

mono-mixdown/
├── manifest.json     Manifest V3, cross-browser
├── content.js        Builds the audio graph, syncs with storage
├── popup.html        Toolbar popup UI
├── popup.css         Popup styling
├── popup.js          Popup logic and content script ping
├── icon.png          128x128 icon
└── README.md         This file

There is no background service worker. Settings flow through chrome.storage.local, and content scripts react directly to storage.onChanged.

Privacy

Category Status
Personal data collected None
Telemetry / analytics None
Remote servers contacted None
Data stored Local settings only (chrome.storage.local)
Accounts required None

This is reflected in manifest.json via data_collection_permissions: { required: ["none"] }, which is the value Mozilla requires for extensions that don't collect or transmit personal data.

Limitations

Important

A few hard limits exist that no browser extension can work around. These are listed here so users know what to expect, not as bugs.

  • DRM-protected playback (Netflix, Spotify Web Player, Apple Music, and similar services). Audio is decoded inside a protected pipeline that browser extensions cannot reach. The effect will not apply.
  • Cross-origin media without CORS headers. When a site loads audio or video from another domain without the right headers, the browser blocks Web Audio from processing it. Major platforms (YouTube, Twitch, Vimeo, most podcast players) are unaffected. For sites that hotlink raw media files, force the effect off for that site.
  • Browser internal pages. chrome://, about:, the Web Store, and the Mozilla Add-ons site cannot run extensions, by design.
  • Local files. On Chromium browsers, file:// access is off by default. To enable it, open the extension's details page and turn on "Allow access to file URLs".

Build and package

The extension is plain JavaScript with no build step. To package it for distribution:

# Chromium (.zip)
zip -r mono-mixdown.zip . -x "*.git*" "*.DS_Store" "node_modules/*" "*.md"

# Firefox (.xpi via web-ext)
npm install -g web-ext
web-ext build

The output ZIP/XPI can be uploaded to the Chrome Web Store, the Opera addons site, or addons.mozilla.org.

Contributing

Issues and pull requests are welcome. A few areas where help would be useful:

  • Testing on browsers that aren't in the support table.
  • Reporting sites where the extension misbehaves (with the URL and any console errors).
  • Translation of popup strings.
  • Per-element controls for users who want different behavior on different videos within the same page.

License

MIT. See LICENSE for details.

Acknowledgements

Built on the Web Audio API, which makes this kind of routing about a hundred lines of code. Thanks to the spec authors and the browser teams that implement it consistently across vendors.

About

Forces stereo audio to mono so single-channel ('broken') audio plays in both ears. Per-site toggle + global default.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors