Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 15 additions & 7 deletions .github/workflows/extension-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,29 @@ jobs:

- run: node tools/check-locales.mjs

- run: node tools/build-extension.mjs

- name: Lint the Firefox build
run: npx --yes web-ext@8 lint --source-dir dist/firefox --self-hosted

- id: version
run: echo "value=$(node -p "require('./extension/manifest.json').version")" >> "$GITHUB_OUTPUT"

- name: Package the extension
- name: Package the extensions
run: |
(cd extension && zip -qr ../offmic.zip .)
sha256sum offmic.zip > offmic.zip.sha256
unzip -l offmic.zip
(cd dist/chrome && zip -qr ../../offmic-chrome.zip .)
(cd dist/firefox && zip -qr ../../offmic-firefox.zip .)
sha256sum offmic-chrome.zip offmic-firefox.zip > SHA256SUMS
unzip -l offmic-chrome.zip
unzip -l offmic-firefox.zip

- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
with:
name: extension
path: |
offmic.zip
offmic.zip.sha256
offmic-chrome.zip
offmic-firefox.zip
SHA256SUMS
if-no-files-found: error

release:
Expand All @@ -82,7 +90,7 @@ jobs:
PRERELEASE: ${{ inputs.prerelease }}
working-directory: dist
run: |
files=(offmic.zip offmic.zip.sha256)
files=(offmic-chrome.zip offmic-firefox.zip SHA256SUMS)
if [ "$OFFICIAL" = "true" ]; then
flags=""
[ "$PRERELEASE" = "true" ] && flags="--prerelease"
Expand Down
42 changes: 37 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ OffMic **never controls** the Teams microphone. It watches it.
2. **Capture.** An offscreen document opens the microphone alongside Teams.
3. **Decide.** The outgoing track is enabled only when the connection is up **and** the Teams mic is muted.

Where the capture and the peer connections live depends on the browser. Chrome hosts them in an offscreen document; Firefox refuses microphone access from an extension background page, so it hosts them in the Teams tab itself. The media code is the same file in both cases, only the host changes.

Voice travels over **peer-to-peer WebRTC**, in a mesh. It never goes through a server. The signaling server only introduces peers to each other, just long enough to exchange SDP descriptions and ICE candidates. Once the peers are connected, nothing flows through it anymore.

```
Expand All @@ -54,16 +56,30 @@ Voice travels over **peer-to-peer WebRTC**, in a mesh. It never goes through a s
- **Headphones are mandatory.** If your team's voice comes out of your speakers while your Teams mic is live, the client will hear it. No software can fix that.
- **Teams web only.** A browser extension has no reach into the Teams desktop client.
- **Detection depends on the Teams DOM.** Microsoft can change its UI and break it. English and French interfaces are recognized, with a polling fallback.
- **Chrome for now.** The offscreen document is Chrome specific. A Firefox port is planned.
- **Chrome and Firefox.** Chrome runs the voice channel in an offscreen document, Firefox inside your Teams tab. Same code, one manifest each.
- **Up to 5 or 6 people.** Beyond that, a peer-to-peer mesh costs too much upstream bandwidth.

## Install

### Chrome

OffMic is published on the Chrome Web Store:

**[chromewebstore.google.com/detail/onacjipehnjfchieiakjlofndidcnkod](https://chromewebstore.google.com/detail/onacjipehnjfchieiakjlofndidcnkod)**

Add it to Chrome, open the popup, and click the microphone permission link once. That step matters: an offscreen document has no UI, so it cannot show a permission prompt. Without it, capture fails with `NotAllowedError`.
### Firefox

Firefox 115 or newer. The build is not on addons.mozilla.org yet, so grab `offmic-firefox.zip` from the [latest release](https://github.com/Bysimeit/OffMic/releases), unzip it, then open `about:debugging` → **This Firefox** → **Load Temporary Add-on** and pick the `manifest.json` inside. Being a temporary add-on, it goes away when you restart Firefox.

### On Chrome, one permission step

Open the popup and click the microphone permission link once. That step matters: the offscreen document has no UI, so it cannot show a permission prompt. Without it, capture fails with `NotAllowedError`.

### On Firefox, nothing to grant

Firefox refuses `getUserMedia` from an extension background page outright, whatever permission is stored, because there is no browsing context to attach the capture to. So on Firefox the voice channel runs inside your Teams tab instead, as a content script. It captures under the Teams origin, which already holds the microphone permission for your meeting, so there is no second prompt and nothing extra to click.

The consequence is that the Teams tab **is** the channel. Connect with your meeting open, and if that tab is closed or reloaded the channel stops and the popup says so.

Nothing else to install. The extension already points at a hosted relay, so joining a room is all it takes.

Expand Down Expand Up @@ -111,14 +127,30 @@ That value is only the **default** shown on a fresh Chrome profile. The address

### 2. Load the extension from source

Skip this if you already installed the store build, it works the same against your own relay.
Skip this if you already installed a packaged build, it works the same against your own relay.

`extension/` holds one source tree and two manifests: `manifest.json` for Chrome, `manifest.firefox.json` for Firefox. The build script copies the tree into `dist/`, one folder per browser, with the right manifest in place:

```bash
node tools/build-extension.mjs
```

On Chrome you can also load `extension/` directly, since its `manifest.json` is already the Chrome one.

**Chrome**

1. Open `chrome://extensions`
2. Turn on **Developer mode**
3. **Load unpacked**, then pick the `extension/` folder
3. **Load unpacked**, then pick `dist/chrome/` (or `extension/`)
4. Open the popup and click the microphone permission link

Step 4 matters: an offscreen document has no UI, so it cannot show a permission prompt. You have to grant access once from the dedicated page, otherwise capture fails with `NotAllowedError`.
**Firefox**

1. Open `about:debugging` → **This Firefox**
2. **Load Temporary Add-on**, then pick `dist/firefox/manifest.json`
3. Open the popup and click the microphone permission link

The last step matters on both: the audio runs in a context with no UI, so it cannot show a permission prompt. You have to grant access once from the dedicated page, otherwise capture fails with `NotAllowedError`.

### 3. Run the signaling server

Expand Down
3 changes: 3 additions & 0 deletions extension/_locales/de/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@
"errOffscreenFailed": {
"message": "Interner Fehler: Das Mikrofon-Dokument konnte nicht starten. Lade die Erweiterung neu."
},
"errNoTeamsTab": {
"message": "OffMic braucht den Tab deiner Teams-Besprechung. Öffne ihn und verbinde dich erneut."
},
"errNoResponse": {
"message": "Keine Antwort der Erweiterung. Lade sie unter chrome://extensions neu und versuche es erneut."
}
Expand Down
3 changes: 3 additions & 0 deletions extension/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@
"errOffscreenFailed": {
"message": "Internal error: the microphone document could not start. Reload the extension."
},
"errNoTeamsTab": {
"message": "OffMic needs your Teams meeting tab to be open. Open it, then connect again."
},
"errNoResponse": {
"message": "No response from the extension. Reload it from chrome://extensions, then try again."
}
Expand Down
3 changes: 3 additions & 0 deletions extension/_locales/es/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@
"errOffscreenFailed": {
"message": "Error interno: el documento de micrófono no pudo iniciarse. Vuelve a cargar la extensión."
},
"errNoTeamsTab": {
"message": "OffMic necesita que la pestaña de tu reunión de Teams esté abierta. Ábrela y vuelve a conectar."
},
"errNoResponse": {
"message": "La extensión no responde. Vuelve a cargarla desde chrome://extensions e inténtalo de nuevo."
}
Expand Down
3 changes: 3 additions & 0 deletions extension/_locales/fr/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@
"errOffscreenFailed": {
"message": "Erreur interne : le document micro n'a pas pu démarrer. Recharge l'extension."
},
"errNoTeamsTab": {
"message": "OffMic a besoin que l'onglet de ta réunion Teams soit ouvert. Ouvre-le, puis reconnecte-toi."
},
"errNoResponse": {
"message": "Pas de réponse de l'extension. Recharge-la depuis chrome://extensions, puis réessaie."
}
Expand Down
3 changes: 3 additions & 0 deletions extension/_locales/nl/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,9 @@
"errOffscreenFailed": {
"message": "Interne fout: het microfoondocument kon niet starten. Herlaad de extensie."
},
"errNoTeamsTab": {
"message": "OffMic heeft het tabblad van je Teams-vergadering nodig. Open het en verbind opnieuw."
},
"errNoResponse": {
"message": "Geen antwoord van de extensie. Herlaad ze via chrome://extensions en probeer opnieuw."
}
Expand Down
Loading
Loading