- Status: Accepted
- Date: 2026-04-11
- Deciders: Project owner, architect
- Supersedes: —
- Superseded by: —
- Applies to: Phase 1–4 (MVP). Phase 4+ may revisit based on user demand for native desktop meeting app support.
The host client (the staff machine, see ADR-0001) must capture a single
mixed audio stream containing all speakers in a meeting and stream it to
the Go Gateway over WebRTC. The stream is consumed by the C++ engine for
transcription and speaker diarization. Because Aegis uses single-channel
diarization (see ARCHITECTURE.md §4 Data Flow, step 5: "we capture a
single mixed audio track (simplifying hardware), the AI isolates
speakers"), the capture mechanism only needs to deliver "all voices in one
stream" — it does not need per-speaker channel separation.
This decouples capture mechanics from speaker identification: any capture method that yields one mixed PCM stream is sufficient. This is the insight that unlocks pure-web for the MVP.
This ADR documents how capture happens in the MVP and why the native shell (Tauri, see ADR-0002) is architecturally reserved but not built in Phase 3.
- D1. Ship MVP fast. Phase 1–4 must deliver a working product without building a Rust toolchain and Tauri shell in parallel.
- D2. Cover the common meeting scenarios on day 1 — physical meeting rooms and remote meetings via web-based meeting clients (Zoom Web, Google Meet, Teams Web).
- D3. Preserve the "clone and run" ethos. No user-side audio driver installation, no virtual audio device setup, no per-platform binary in the MVP.
- D4. Keep the Tauri option alive for Phase 4+. If a material fraction of users insist on using native meeting apps (Zoom Desktop, Teams Desktop), the Phase 3 frontend must wrap into Tauri without refactor (see ADR-0002).
- D5. Single-channel diarization removes the multi-track argument. We do not need per-speaker audio tracks, so there is no technical reason the capture layer must be native.
A common misconception is that browsers cannot capture system audio. This is partially true but misses the full picture.
Modern browsers expose three audio capture surfaces:
getUserMedia({audio: true})— selected microphone device. Universally supported across Chrome, Edge, Firefox, Safari.getDisplayMedia({video: true, audio: true})— screen / tab sharing including an audio track. On Chrome and Edge, the user can select a specific browser tab and check "Share tab audio", capturing the audio output of that tab. This is the same mechanism Google Meet uses for "Present a tab with audio." Firefox support is partial; Safari support is incomplete.Web Audio API— withMediaStreamAudioSourceNodeandMediaStreamAudioDestinationNode, the browser can mix multiple MediaStream inputs into a single output MediaStream in real time, without leaving the browser process.
The practical implication: if the counterparty runs their meeting in a
browser tab (Zoom Web, Google Meet, Teams Web), the staff host can
capture that tab's audio via getDisplayMedia — no native code, no
virtual audio device, no Tauri required.
The only scenario this does NOT cover is when the counterparty uses a native desktop meeting app whose audio output goes directly to OS-level sinks that browsers cannot reach. For that scenario, users must either:
- Use the web version of the meeting app instead (documented recommended workaround).
- Install a virtual audio device (BlackHole on macOS, VoiceMeeter on Windows) that re-exposes system audio as a virtual microphone.
- Wait for Phase 4+ when Tauri provides native system audio access.
Capture via the three browser APIs, combined per scenario:
- Physical conference room:
getUserMedia({audio: ...})on the laptop microphone. - Remote meeting, counterparty in browser:
getDisplayMedia({video, audio: true})capturing a browser tab (Zoom Web, Meet, Teams Web); discard the video track, keep audio. - Mix mic + tab audio: optional toggle. Web Audio API mixes a
getUserMediamic stream and agetDisplayMediatab stream into one outputMediaStreamDestination.
Send the resulting MediaStream to the Go Gateway via RTCPeerConnection.addTrack().
Ship Phase 3 as a Tauri app with native CoreAudio / WASAPI capture alongside the web frontend. Covers native meeting apps on day 1.
Pure web, but only getUserMedia — no getDisplayMedia. Tell users to
install BlackHole / VoiceMeeter and route system audio into a virtual
microphone.
We choose Option A.
MVP Phase 3 ships as pure web with a layered capture strategy:
- UI mode selector at meeting start: "Physical room (microphone)" or "Remote meeting (capture browser tab)".
- Physical room path:
getUserMedia({audio: { echoCancellation: true, noiseSuppression: true, autoGainControl: true }}). - Remote tab path:
getDisplayMedia({video: true, audio: true}); discard the video track immediately; keep the audio track. - Optional mix: "Include my microphone too" toggle. If on, also
getUserMediaand combine viaAudioContext.createMediaStreamSource()→ singleMediaStreamDestination. - Abstraction boundary: all four of the above are encapsulated in the
AudioCaptureProviderinterface required by ADR-0002 Constraint 2, so the Phase 4 Tauri backend can replace the implementation without touching any call site.
- D1: fastest path to MVP. Single React + Vite codebase in TypeScript; no Rust toolchain on the critical path.
- D2: covers physical-room AND remote-via-web-meeting-client scenarios — the two most common target user journeys.
- D3: zero user-side installation beyond "open a Chrome tab." No kernel extension, no audio driver, no per-platform binary.
- D4: the
AudioCaptureProviderabstraction keeps the Tauri door open for Phase 4 without costing any Phase 3 velocity. - D5: single-channel diarization means we do not need the multi-track capture machinery that would otherwise push toward native code.
- Violates D1. Adds Rust toolchain, Tauri build pipeline, CoreAudio and WASAPI glue, cross-platform binary packaging — multi-week work that delays MVP delivery with no proportional benefit.
- Optimizes for a scenario (native desktop meeting app) that has not been validated as a real MVP customer need. Premature optimization.
- Phase 3 → Phase 4 reuse (per ADR-0002) means no rework penalty for deferring Tauri — the web frontend will be wrappable when we do build the shell.
- Violates D3 significantly. Installing kernel-level audio drivers (BlackHole, VoiceMeeter) is the antithesis of "clone and run." A tech-savvy individual can do it; the target enterprise staff user typically cannot without IT support.
- Violates D2 for remote meetings where we already have a better
answer (
getDisplayMediatab audio) that requires no installation. - Reserved as a documented fallback for power users who need to capture native-app audio before Phase 4 ships. Not the default flow.
- Staff opens Aegis in Chrome or Edge.
- Staff opens Zoom Web / Google Meet / Teams Web in a separate tab.
- Staff presses "New Meeting" in Aegis. UI asks: "Where is the audio coming from?" with options "Another browser tab" and "My microphone."
- Staff picks "Another browser tab" → Aegis calls
getDisplayMedia({video: true, audio: true}). - Chrome shows its native tab picker. Staff selects the meeting tab and checks "Share tab audio."
- Aegis receives a
MediaStreamwith one audio track and one video track; it immediately discards the video track and keeps only audio. - The audio track is passed to
RTCPeerConnection.addTrack(). WebRTC negotiates with Go Gateway. PCM flows. - C++ engine receives PCM, performs diarization, emits speaker-labeled transcript segments. Go Gateway broadcasts to viewers (per ADR-0004).
For physical-room meetings, steps 2–6 collapse to a single getUserMedia
call on the laptop microphone.
- Browser support is Chrome / Edge only (MVP).
getDisplayMediatab-audio capture is fully supported on Chrome 74+ and Edge Chromium. Firefox has partial support and its tab-audio path is unreliable. Safari support is incomplete. MVP supported browsers: Chrome and Edge. This must be documented inREADME.md. Viewer role works on any modern browser regardless. - HTTPS / secure context requirement. Both
getUserMediaandgetDisplayMediarequire a secure context.- In Cloud mode: Go Gateway serves the frontend over TLS (ACM cert via
the
aegis-landing-zone-awsinfra repository). - In Local mode: the frontend is loaded from
http://localhost:PORT.localhostis exempt from the secure-context requirement by browser specification, so capture works without a self-signed cert for the host device. LAN viewers (see ADR-0007) do not needgetUserMediaand so are not blocked by the absence of TLS.
- In Cloud mode: Go Gateway serves the frontend over TLS (ACM cert via
the
- Per-session user prompt.
getDisplayMediaprompts the user to pick a tab at every meeting start. Friction: ~5 seconds per meeting. UI must make clear that only audio is captured, not screen contents:"We use this prompt to capture the audio from your meeting tab. Screen contents are discarded and never sent anywhere."
- Acoustic echo on physical-room laptop mic. When laptop speakers
play remote audio and the same laptop mic captures the room, there is
acoustic echo. Mitigations:
getUserMedia's built-inechoCancellation: truehandles moderate echo.- Recommend headphones for the staff in all-remote scenarios (will be documented as a usage tip, not enforced).
- Sample rate. Browsers typically deliver 48 kHz;
whisper.cppis tuned for 16 kHz input. MVP choice: let the C++ engine downsample on ingest. This keeps the browser layer simple and avoids per-browser AudioContext behavior quirks. - Discarding
getDisplayMediavideo track user anxiety. Even with clear UI copy, some users will worry that screen contents are being transmitted. Mitigation: (a) in-product copy, (b) an in-product "Privacy" link that explains exactly what is captured and what is not.
- Phase 3 ships as a single React + Vite project. No Rust, no Tauri, no cross-platform binaries.
- Covers the two most common user journeys on day 1 (physical room, remote via web meeting client).
- Aligns with ADR-0002's deferred Tauri decision and preserves the Phase 4 migration path.
- Zero user-side installation.
- Keeps the MVP threat surface small: no native code running with elevated OS audio privileges.
- Users on native Zoom Desktop / Teams Desktop are not supported on day 1. Workaround: ask them to use the web client of those services. Documented as a known limitation; revisit in Phase 4 based on user feedback.
- Chrome / Edge only for host role in MVP. Firefox and Safari users cannot create meetings until browser parity improves or Tauri ships. Viewer role is unaffected.
- Per-session permission friction. Users must pick a tab and check "Share tab audio" every meeting. Acceptable but not frictionless.
- Echo mitigation is best-effort. We rely on the browser's AEC, which is good but not perfect. Users in noisy rooms may see degraded transcription quality.
- ADR-0001 Session Join Mechanism for Meeting Viewers
- ADR-0002 Desktop Shell Technology — Tauri over Qt / Electron
(Constraint 2:
AudioCaptureProviderabstraction) - ADR-0004 Stateless Broadcast Relay
- ADR-0007 Local Mode LAN Topology
ARCHITECTURE.md§4 Data Flow (single-channel diarization)ARCHITECTURE.md§6 AI Models & Hardware Resource OptimizationREADME.md— "Clone it, build it, it just works" ethos