#52738·electron

macOS: audio: 'loopbackWithMute' in setDisplayMediaRequestHandler never captures data with useSystemPicker: false

Author: anildigitalCreated Aug 9, 2026Updated Sep 16, 2026
Labelsplatform/macOSbug :beetle:has-repro-gist43-x-y
### Preflight Checklist - [x] I have read the Contributing Guidelines for this project. - [x] I agree to follow the Code of Conduct that this project adheres to. - [x] I have searched the issue tracker for a bug report that matches the one I want to file, without success. ### Electron Version 43.3.0 ### What operating system(s) are you using? macOS ### Operating System Version macOS Tahoe 26.5.2 (arm64 / Apple Silicon) ### What arch are you using? arm64 (including Apple Silicon) ### Last Known Working Electron version Unknown — have not found a version where this works via a custom picker. ### Does the issue also appear in Chromium / Google Chrome? I don't know how to test ### Expected Behavior When `session.setDisplayMediaRequestHandler` is used with `useSystemPicker: false` (i.e. the app supplies its own picker UI and resolves the callback with a `desktopCapturer` source plus `audio: 'loopback'` or `audio: 'loopbackWithMute'`), the resulting audio `MediaStreamTrack` should actually capture system audio, the same way it does when `useSystemPicker: true` is used instead. ### Actual Behavior The audio track is created (`kind: "audio"`, `label: "System audio"`), so the API call itself succeeds, but the track's `readyState` is `"ended"` from the very first observation, and no audio data is ever captured — an `AnalyserNode` attached to the track reports `peakAmplitude: 0` at every sample, forever. The video track from the same `getDisplayMedia()` call works fine (`readyState: "live"`, frames render normally) — only the audio side fails. Swapping the same request handler to `useSystemPicker: true` (letting Electron/macOS own the whole picker UI) makes system-audio capture work correctly and also correctly triggers the macOS "would like to record system audio" TCC permission prompt. With `useSystemPicker: false`, that TCC prompt never appears at all — which is consistent with the underlying native audio-capture session never actually starting, even though the JS-level API returns a track as if it had. This makes `audio: 'loopback'`/`'loopbackWithMute'` unusable for any app that wants to keep its own custom screen-share picker UI while also offering a "share system audio" option — the only way to get working system audio is to hand the entire picker over to `useSystemPicker: true`, which replaces the app's own UI. Reproduces identically on an ad-hoc `electron .` run and on a `Developer ID`-signed, hardened-runtime, notarization-skipped, `/Applications`-installed packaged build — so this isn't a code-signing or Gatekeeper/TCC-staleness issue. ### Testcase Gist URL https://gist.github.com/anildigital/9a195654a687524fb544db62894466fe ### Additional Information Steps to reproduce with the gist: 1. `npm install` in the gist's directory, then `npm start` (or `electron .`). 2. The renderer calls `getDisplayMedia({ video: true, audio: true })`. 3. Watch the on-screen log: the audio track appears with `readyState=ended` immediately, and the peak-amplitude readout stays at `0.0000` indefinitely, while the video track stays `live`. 4. For comparison, change `useSystemPicker: false` to `useSystemPicker: true` in `main.js` and repeat — the native macOS picker appears, the TCC "record your system audio" prompt fires, and after granting it, real audio is captured (non-zero peak amplitude). In-depth investigation

We believe the root cause is that macOS's system-audio capture (the ScreenCaptureKit-backed Core Audio Tap, gated behind the separate "System Audio Recording Only" TCC permission introduced in newer macOS versions) is only ever started by Electron when Electron's own native picker (`useSystemPicker: true`) drives the capture end-to-end. When an app supplies its own `desktopCapturer`-selected source through a custom picker instead, the `'loopback'`/`'loopbackWithMute'` audio request appears to be accepted and a track handle is handed back, but the corresponding native capture session is never actually wired up — no TCC prompt is ever shown for the app through this path, and `getUserMedia`/`getDisplayMedia` never delivers audio samples. We were unable to find any workaround from the app side (no combination of Chromium command-line switches changed the result), and confirmed the failure is independent of code signing, notarization, and running from `/Applications` vs. an ad-hoc build directory.