Skip to content

feat(macos): record system audio from a Core Audio tap with Apple's picker - #740

Draft
EtienneLescot wants to merge 1 commit into
mainfrom
feat/macos-system-audio-tap
Draft

EtienneLescot wants to merge 1 commit into
mainfrom
feat/macos-system-audio-tap

Conversation

@EtienneLescot

@EtienneLescot EtienneLescot commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator

Follows #737. With Apple's system picker, recording no longer needs the Screen Recording grant, except for system audio. This PR removes that last exception.

Why

A take picked in SCContentSharingPicker holds no Screen Recording grant. ScreenCaptureKit still hands such a capture its system-audio buffers, but fills them with zeros: measured on macOS 26.5, about 158 buffers per 3 s, all silent, while the same measuring code with the grant read peak 0.36. So #737 dropped system audio unless Screen Recording was granted, which brought the grant back for anyone who records system audio.

A Core Audio process tap (macOS 14.2+) is gated by a separate, narrower permission, System Audio Recording Only (NSAudioCaptureUsageDescription, already in our Info.plist). It covers the sound the Mac plays and nothing on screen. Measured alongside a picker video stream on 26.5, with no Screen Recording grant: the prompt reads "“App” would like access to record your system audio.", real audio flows after Allow (peak 0.365, ~94 buffers/s), in the same process, with no relaunch.

What changes

Helper

  • SystemAudioTap: a global stereo process tap in a private aggregate device. Each IO cycle becomes a CMSampleBuffer stamped on the host clock, the domain ScreenCaptureKit's audio used (makeAudioSampleBuffer, in OpenScreenCaptureCore, with tests). It therefore goes through the recorder's existing pause and retime path into AudioTrackMixer, which already resamples and reads non-interleaved float.
  • Only picked takes use it. Takes from the app's own picker keep ScreenCaptureKit's audio, which their grant already covers.
  • The tap starts off the take's start path, because its first start blocks on macOS' prompt. It stops accepting audio before the writer is finalised.
  • --request-system-audio raises the prompt on its own and exits once it is answered.

Electron

Permissions window

  • In picker mode it gets a real System audio row (optional).
  • This grant has no public read, and a refused tap simply records silence. So after asking, the row reads requested and offers the matching System Settings pane (Privacy_AudioCapture).
  • The help text names that section exactly as macOS does in each of the 15 locales (taken from the system's own strings).

Verified

  • swift build, swift test (48, including the new buffer tests), tsc (app and tests), biome, i18n-check, vitest full suite (3181 passed)
  • Tap and picker stream together, measured in a standalone test app on macOS 26.5 (numbers above)
  • In-app end-to-end on the packaged build: HUD toggle → prompt, then a picked recording with a sound playing, checked in the file with ffmpeg; pause/resume with the tap. This is why the PR is a draft.
  • macOS 15.2–15.x: not available here

Known limits

  • The app cannot tell whether system audio was allowed or refused. If it was refused, recordings carry silent system audio; the permissions window explains where to change it.
  • The tap records all system audio: it excludes no process, as ScreenCaptureKit's excludesCurrentProcessAudio only ever excluded the helper, which plays nothing.

Summary by CodeRabbit

  • New Features
    • macOS recordings using the system picker can now capture system audio on supported versions.
    • The app requests system-audio permission when needed and shows guidance for granting it in System Settings.
  • Bug Fixes
    • Removed the warning shown during recording when system audio could not be captured.

…icker

A take picked in Apple's system picker holds no Screen Recording grant,
and ScreenCaptureKit hands such a capture its system audio as silence
(measured on macOS 26.5). So #737 dropped system audio unless Screen
Recording was granted, which brought back the grant the picker removes.

Picked takes now record system audio from a Core Audio process tap
instead, under its own, narrower 'System Audio Recording Only' grant.
Measured alongside a picker stream on 26.5: real audio, in the same
process, with no relaunch and no Screen Recording grant.

- SystemAudioTap: a global stereo process tap in a private aggregate
  device. Its IO cycles become CMSampleBuffers stamped on the host clock
  (makeAudioSampleBuffer, in OpenScreenCaptureCore with tests), so they
  take the recorder's existing pause/retime path into AudioTrackMixer.
- The tap starts off the take's start path, since the first start blocks
  on macOS' prompt, and stops accepting audio before the writer is
  finalised. Takes from the app's own picker keep ScreenCaptureKit audio.
- `--request-system-audio` raises the prompt on its own. The HUD's
  system-audio toggle calls it the first time it is turned on, so the
  prompt never lands on a take that is already counting down.
- The permissions window gets a real 'System audio' row there. Its
  answer has no public read, so after asking it reads 'requested' and
  offers the matching System Settings pane (Privacy_AudioCapture), with
  the section named as macOS names it in each of the 15 locales.
@coderabbitai

coderabbitai Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: e3ecb27e-00d5-4f6d-990e-315124edcfc1

📥 Commits

Reviewing files that changed from the base of the PR and between 847571f and 956d356.

📒 Files selected for processing (29)
  • electron/ipc/handlers.ts
  • electron/ipc/recordingPrefs.test.ts
  • electron/ipc/recordingPrefs.ts
  • electron/native/screencapturekit/Sources/OpenScreenCaptureCore/AudioBufferListSampleBuffer.swift
  • electron/native/screencapturekit/Sources/OpenScreenScreenCaptureKitHelper/ScreenCaptureRecorder.swift
  • electron/native/screencapturekit/Sources/OpenScreenScreenCaptureKitHelper/SystemAudioTap.swift
  • electron/native/screencapturekit/Tests/OpenScreenCaptureCoreTests/AudioBufferListSampleBufferTests.swift
  • electron/permissions/index.ts
  • electron/permissions/macPermissions.test.ts
  • electron/permissions/macPermissions.ts
  • src/components/permissions/PermissionsWindow.test.tsx
  • src/components/permissions/PermissionsWindow.tsx
  • src/hooks/useScreenRecorder.ts
  • src/i18n/locales/ar/launch.json
  • src/i18n/locales/cs/launch.json
  • src/i18n/locales/de/launch.json
  • src/i18n/locales/en/launch.json
  • src/i18n/locales/es/launch.json
  • src/i18n/locales/fr/launch.json
  • src/i18n/locales/it/launch.json
  • src/i18n/locales/ja-JP/launch.json
  • src/i18n/locales/ko-KR/launch.json
  • src/i18n/locales/pt-BR/launch.json
  • src/i18n/locales/ru/launch.json
  • src/i18n/locales/tr/launch.json
  • src/i18n/locales/vi/launch.json
  • src/i18n/locales/zh-CN/launch.json
  • src/i18n/locales/zh-TW/launch.json
  • src/lib/nativeMacRecording.ts
💤 Files with no reviewable changes (1)
  • src/hooks/useScreenRecorder.ts

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.


📝 Walkthrough

Walkthrough

Adds macOS system-audio permission tracking and prompt handling for Apple-picker recordings, then captures picker audio through a Core Audio process tap and sends it to the existing audio mixer. The permissions window and localized help show system-audio states. Recording responses no longer include systemAudioUnavailable.

Changes

System Audio Capture

Layer / File(s) Summary
Audio buffer conversion and tap
electron/native/screencapturekit/Sources/OpenScreenCaptureCore/AudioBufferListSampleBuffer.swift, electron/native/screencapturekit/Sources/OpenScreenScreenCaptureKitHelper/SystemAudioTap.swift, electron/native/screencapturekit/Tests/OpenScreenCaptureCoreTests/AudioBufferListSampleBufferTests.swift
Adds Core Audio buffer conversion with host-time timestamps, a process tap that forwards sample buffers, and tests for timestamps, copied channel data, and empty buffers.
Recorder integration and helper command
electron/native/screencapturekit/Sources/OpenScreenScreenCaptureKitHelper/ScreenCaptureRecorder.swift, electron/ipc/handlers.ts, src/hooks/useScreenRecorder.ts, src/lib/nativeMacRecording.ts
For Apple-picker recordings on macOS 14.2 or newer, the recorder uses the tap instead of ScreenCaptureKit audio and sends tapped samples through its existing pause and retime path. The helper adds a permission-request command. The recording response type and caller no longer use systemAudioUnavailable.
Permission state and request flow
electron/permissions/macPermissions.ts, electron/permissions/index.ts, electron/permissions/macPermissions.test.ts, electron/ipc/recordingPrefs.ts, electron/ipc/recordingPrefs.test.ts, electron/ipc/handlers.ts
Adds systemAudio permission states, request tracking, and System Settings routing. The permission layer launches the helper to request access. When system audio changes to enabled during system-picker use, the IPC callback requests access once.
Permissions window and localized help
src/components/permissions/PermissionsWindow.tsx, src/components/permissions/PermissionsWindow.test.tsx, src/i18n/locales/*/launch.json
Shows a system-audio row when screen permission is not required. The row displays prompt or requested-state help and routes actions to the request or System Settings. Adds translations for both help states.

Priority: ➖ Normal

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant RecordingPrefs
  participant registerIpcHandlers
  participant MacPermissions
  participant CaptureHelper
  participant SystemAudioTap
  RecordingPrefs->>registerIpcHandlers: publish enabled preference change
  registerIpcHandlers->>MacPermissions: askForSystemAudioOnce()
  MacPermissions->>CaptureHelper: launch with --request-system-audio
  CaptureHelper->>SystemAudioTap: start tap to request access
Loading

Suggested reviewers: my-denia

Merge Risk: ⚪ Minimal · up to 956d3

No concrete issue in the supplied evidence blocks merging; complete the usual checks before release.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 42 functions across 13 files. (15 skipped… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: recording system audio through a Core Audio tap for Apple's picker.
Description check ✅ Passed The description provides a detailed summary, motivation, implementation details, testing results, known limits, and macOS impact. It does not use all template headings or explicitly select type and re…
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 42 functions across 13 files. (15 skipped: 15 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@EtienneLescot

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 24, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant