Google Meet on your browser, the way it was meant to be.
Google Meet still can’t do the simple things Discord has had for years. BetterMeet fills those gaps, starting with the one people ask for most: a volume slider for each person in the call.
Right-click anyone’s tile, or their name in the people list, and you get a slider from 0% to 200% for that person alone: turn down the one with the loud mic, boost the one who whispers, or mute someone just for yourself. Nobody else in the call is affected and nobody is notified. Tiles you muted show a small “Muted for you” badge.
There is also a mixer button at the bottom-left of the call with every participant in one list.
Volumes are saved by name, so the same people sound right in the next meeting too. You can see and forget them from the extension popup.
Meet’s own full screen keeps its toolbar, tiles and menus on top of what you are watching. With BetterMeet, choosing full screen on a tile shows only that video on a black stage and nothing else, like a video player. Move the mouse and the controls appear at the bottom-right: that person’s volume and a button to go back. Escape works too.
BetterMeet is not on the Chrome Web Store yet. To run it from source:
npm install
npm run dev # Chrome
npm run dev:firefox # Firefox
To produce a build:
npm run build # output in .output/chrome-mv3
npm run zip # packaged extension
Then load the unpacked build from chrome://extensions with developer mode enabled. Each feature can be turned on or off from the extension popup.
Meet’s audio plumbing changes between versions and between accounts: some builds play remote voices through <audio> elements, some through Web Audio, some through a worklet with no element anywhere. BetterMeet does not depend on any of that.
It takes the audio one step earlier, at the only place every version has in common: the WebRTC connection itself. A script hooked before Meet loads keeps a reference to every incoming audio stream. For each one it takes a clone, plays the clone itself through its own gain node, and switches off the copy Meet holds. From then on that voice is BetterMeet’s to scale, and Meet’s own player is out of the loop whatever it happens to be. When a build decodes audio in its own worklet instead, BetterMeet places the gain right after that worklet.
Nothing is switched over until it is proven to work. Every stream is listened to silently from the start; only once real sound has been measured on our own copy is Meet’s copy allowed to be muted. If a stream ever falls silent on our side while the connection says audio is arriving, the takeover is undone within two seconds and the call goes back to normal. A version BetterMeet cannot reach loses the volume slider, never the audio.
Working out who is in each stream is a separate problem, because the service rotates speakers through a small number of streams. One table answers it. Every 150 ms each voice that is making sound is scored against each tile showing Meet’s speaking indicator: agreement adds, disagreement subtracts, and a pair is bound once it is clearly ahead of the alternatives. Three shortcuts feed the same table: the contributing source id, when the build reports one, identifies a voice across rotations; a lone unmuted person with a lone live stream is bound immediately, which is what makes 1:1 and small calls work with no learning; and a voice that keeps playing while every speaking tile is already accounted for is a screen share, and binds to the presentation tile. Because it is one table and not a chain of rules, a wrong guess is corrected by the evidence that contradicts it.
The output device you picked in Meet is mirrored. Setting everyone back to 100% hands playback straight back to Meet.
If something doesn’t work in a call, the Self-test section of the extension popup shows what BetterMeet found there and can copy it as a report.
Built with WXT, React 19 and TypeScript, the same base as BetterIG. The UI is injected through a shadow root and uses a quiet, monochrome design in the spirit of Apple and Notion, so it stays out of the way. State lives in browser.storage.local and nowhere else.
entrypoints/core.content.ts page-world audio core: owns the WebRTC voices
entrypoints/content/ in-call UI (menu, mixer, full screen, welcome tour)
entrypoints/popup/ extension popup (features, saved volumes, self-test)
entrypoints/background.ts opens Meet on first install
components/ slider, menu, mixer panel, full screen, hint, toggle
utils/coreProtocol.ts messages between the core and the UI
utils/core.ts client for the core
utils/identify.ts voice to participant scoring
utils/meet.ts Meet DOM reading (tiles, names, mic)
utils/speaking.ts speaking indicator detection per tile
utils/badges.ts "Muted for you" badges on tiles
utils/fullscreen.ts picks the video for real full screen
utils/mixer.ts puts it together and drives the UI
utils/diagnostics.ts self-test data for the popup
utils/store.ts flags and saved volumes
BetterMeet has no servers and sends nothing off your device. Participant names are stored locally only so their volume can be restored.
BetterMeet is not affiliated with, sponsored, or endorsed by Google. Google Meet is a trademark of Google LLC.