A Chrome extension for presenting from a coffee-table setup: keep working on your laptop screen while a thin external display, facing the audience, shows a live mirror of the tab you're in — no cables, no screen-share overlays, no letting them see your other tabs.
Click the toolbar icon on the tab you're working in. It pops that tab into a separate, chrome-free window streaming a live, real-time copy. Drag that window onto the second display.
The extension is not on the Chrome Web Store — it loads unpacked, which Chrome supports natively. Two ways to get the files:
- Go to Releases
and download
audience-mirror-ext-vX.Y.Z.zip. - Unzip it. You'll get a folder containing
manifest.json. - Open
chrome://extensionsin Chrome. - Turn on Developer mode (top right).
- Click Load unpacked and select the unzipped folder.
Keep the unzipped folder where it is — Chrome loads the extension from that path on every startup. Deleting or moving it disables the extension.
git clone https://github.com/clue2solve/audience-mirror-ext.gitThen follow steps 3–5 above, selecting the extension/ folder inside the
clone.
Either way, pin the icon to your toolbar for one-click access (puzzle-piece icon → pin).
To update, download the newer release and replace the folder's contents,
then click the reload arrow on the extension's card in
chrome://extensions.
Works on Chrome and Chromium-based browsers (Edge, Brave, Arc) recent
enough to support Manifest V3 service workers and chrome.tabCapture.
- Go to the tab you want the audience to see, on your laptop screen.
- Click the Audience Mirror toolbar icon (or
Cmd+Shift+Mon Mac,Ctrl+Shift+Melsewhere). - With two displays connected, it automatically:
- resizes your current window to match the external display's aspect ratio (as large as fits on your screen)
- opens the mirror window sized to exactly cover the external display
- Drag the mirror window fully onto the external display if it doesn't
land there on its own, then click Fullscreen (or press
F). - Keep working in your original tab — the mirror follows automatically, including navigation to new URLs within that tab.
Because both windows now share the same aspect ratio, the mirror should fill the display edge-to-edge with no letterboxing. Which display counts as "external" is whichever one your current window isn't on — with only one display connected, the resize step is skipped and it falls back to a plain 960×600 window. With three or more displays connected it picks the first one that isn't yours, which may not be the right guess.
If you still see black bars or don't want any part of the frame cropped,
click Fill (or press C) to crop instead of letterbox — but with
matched aspect ratios you shouldn't need it.
Clicking the icon again while the mirror is open just refocuses the existing mirror window instead of opening a second one.
| Key | Where | Action |
|---|---|---|
Cmd+Shift+M / Ctrl+Shift+M |
Any tab | Open or focus the mirror for the current tab |
F |
Mirror window | Toggle fullscreen |
C |
Mirror window | Toggle Fit (letterbox) / Fill (crop) |
The Cmd+Shift+M binding can be changed at chrome://extensions/shortcuts.
chrome.tabCapture.getMediaStreamId() hands a stream id from the
background service worker to the mirror window, which turns it into a
MediaStream via getUserMedia and plays it in a <video> element. It's
tab-level capture, so it follows in-tab navigation automatically — no
custom event-relay code needed for clicks, typing, or scrolling.
Permissions requested, and why:
| Permission | Why |
|---|---|
tabCapture |
Produce the live video stream of the source tab |
activeTab |
Identify the tab you clicked the icon on |
storage |
Remember your Fit/Fill preference |
system.display |
Read display geometry to size both windows |
Nothing leaves your machine — there is no network code, no analytics, and no remote endpoint anywhere in this extension.
- Video only — no tab audio is captured, so there's no echo risk on a shared table.
- To mirror a different tab, close the mirror window and click the icon again from that tab.
- Capture stops automatically if the source tab closes; the mirror window shows "Source tab closed" rather than freezing on a stale frame.
- Chrome refuses to capture
chrome://pages and the Web Store, so the mirror stays blank on those tabs.
extension/ the loadable extension
manifest.json MV3 manifest, permissions, keyboard shortcut
background.js service worker: window layout + stream handoff
mirror.html/js/css the mirror window
icons/ toolbar and store icons
make_icons.py regenerates the PNG icons
.github/workflows/release.yml tag → zipped GitHub Release
There is no build step — the source in extension/ is what Chrome loads.
To hack on it, edit the files and hit reload on the extension card in
chrome://extensions.
Issues and pull requests are welcome. Keep changes scoped to the existing
vanilla-JS, no-dependency setup, and add a CHANGELOG.md entry with
user-visible behavior changes.
-
Bump
versioninextension/manifest.jsonand add aCHANGELOG.mdentry. -
Commit, then tag and push:
git tag v1.3.0 git push origin main --tags
Pushing a tag matching v*.*.* triggers
.github/workflows/release.yml, which zips
the extension/ folder and publishes it as a GitHub Release with
auto-generated notes.
MIT — see LICENSE.