Skip to content

fix(mac): honor selected microphone in native ScreenCaptureKit capture#663

Open
rayl15 wants to merge 3 commits into
webadderallorg:mainfrom
rayl15:fix/mac-microphone-selection
Open

fix(mac): honor selected microphone in native ScreenCaptureKit capture#663
rayl15 wants to merge 3 commits into
webadderallorg:mainfrom
rayl15:fix/mac-microphone-selection

Conversation

@rayl15
Copy link
Copy Markdown

@rayl15 rayl15 commented Jun 6, 2026

Problem

On macOS (native ScreenCaptureKit backend), selecting a specific microphone in Recordly has no effect — the recorder always captures the system default input device. If the default is a poor/far device (e.g. a Bluetooth speakerphone running in HFP/SCO narrowband mode), recordings come out muffled and quiet regardless of what the user picked.

Root cause

The renderer passed the web MediaDevices deviceId (an opaque, per-origin hashed string) plus the web device label to the native helper. The Swift helper then tried to match those against AVCaptureDevice:

  • The web deviceId never equals a Core Audio uniqueID, so that match always failed.
  • The web label rarely equals AVCaptureDevice.localizedName verbatim (and can be empty when the capturing context lacks mic permission), so exact label matching usually failed too.

When both failed, resolveMicrophoneCaptureDeviceID returned nil and ScreenCaptureKit silently fell back to the system default mic.

Fix

  • Helper (ScreenCaptureKitRecorder.swift): add a --list-microphones mode that prints the real Core Audio input devices (uniqueID + localizedName) as JSON. Runs before any screen-capture preflight so enumeration never triggers a Screen Recording permission prompt.
  • Main + preload + types: add a list-native-microphones IPC that runs the helper in that mode and returns the device list.
  • Renderer (useScreenRecorder.ts): at record-start, resolve the selected mic to its real Core Audio uniqueID via that enumeration and pass it as microphoneDeviceId, so ScreenCaptureKit records the chosen device.
  • Resolver: prefer the exact uniqueID, then fall back to tolerant (case-insensitive / containment) label matching as a backstop.

Note on the second commit

fix(build): remove orphaned connected-zoom transition dead code is included because main currently fails tsc (getConnectedRegionTransition and its only-local helpers in zoomRegionUtils.ts are unreferenced, tripping noUnusedLocals), which blocks compiling/building this change. Happy to split it into its own PR if preferred.

Testing

  • npx tsc clean; vitest suites pass.
  • Compiled the helper with swiftc and verified --list-microphones returns the real devices with their uniqueIDs.
  • Verified on an Apple Silicon build that selecting a specific mic now records that device instead of the system default.

Found while investigating #662.

Summary by CodeRabbit

  • New Features

    • Added native microphone enumeration so the app can list available input devices on macOS.
  • Bug Fixes

    • Improved microphone selection for macOS native screen recording to more reliably pick the intended device.
    • Removed the connected zoom/pan transition interpolation to avoid problematic transitions; dominant region behavior preserved.

rayl15 added 2 commits June 6, 2026 22:20
getConnectedRegionTransition and its only-local helpers (easeConnectedPan,
getLinearFocus, lerp) were unreferenced, leaving the cubicBezier import
unused too. tsc --noUnusedLocals failed the build. Drop the dead cluster.
The native recorder received the web MediaDevices deviceId (an opaque
per-origin hash) and the web label, then tried to match them against
AVCaptureDevice uniqueID/localizedName. The hashed id never matches a
Core Audio uniqueID and the web label rarely equals localizedName verbatim,
so resolution returned nil and ScreenCaptureKit silently recorded the
system default mic regardless of the user's selection.

- Add a `--list-microphones` mode to the helper that emits the real Core
  Audio input devices (uniqueID + localizedName) as JSON.
- Add a list-native-microphones IPC + preload binding.
- At native-start, map the selected mic to its real uniqueID via that
  enumeration and pass it as microphoneDeviceId so SCK records the chosen
  device.
- Make the Swift resolver prefer the uniqueID and fall back to tolerant
  (case-insensitive / containment) label matching.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 6, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0f9e3565-c582-4fba-8faf-998dfc72b3ab

📥 Commits

Reviewing files that changed from the base of the PR and between 27bb600 and d0be8ea.

📒 Files selected for processing (1)
  • src/hooks/useScreenRecorder.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/hooks/useScreenRecorder.ts

📝 Walkthrough

Walkthrough

Adds macOS native microphone enumeration (typings, preload bridge, IPC handler, Swift CLI) and integrates resolved device IDs into native screen recording startup. Separately, removes connected zoom/pan interpolation helpers and stops producing zoom transition objects.

Changes

Native Microphone Enumeration and Selection

Layer / File(s) Summary
API contract and types
electron/electron-env.d.ts
listNativeMicrophones method added to window.electronAPI with promise return { success, devices: [{ id, name }], error? }.
Preload and IPC handler
electron/preload.ts, electron/ipc/register/recording.ts
Preload exposes listNativeMicrophones; main-process handler list-native-microphones invokes the native helper on darwin or returns empty devices on other platforms.
Swift helper: enumeration & ID resolution
electron/native/ScreenCaptureKitRecorder.swift
Adds --list-microphones CLI mode to enumerate AVCaptureDevice audio inputs and output JSON {id,name}; reorders microphone-device resolution to prefer exact uniqueID then multi-step label matching (exact, case-insensitive, containment).
Renderer integration for microphone selection
src/hooks/useScreenRecorder.ts
Calls listNativeMicrophones, normalizes/matches device names to the selected mic label, and passes the resolved microphoneDeviceId into startNativeScreenRecording for macOS native capture.

Zoom Transition Cleanup

Layer / File(s) Summary
Remove connected zoom/pan interpolation
src/components/video-editor/videoPlayback/zoomRegionUtils.ts
Removed cubicBezier import and deleted lerp, easeConnectedPan, getLinearFocus, and getConnectedRegionTransition; findDominantRegion now always returns transition: null while preserving hold-region selection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

"I'm a rabbit with a tiny mic,
I hop through devices, quick and slick.
I list the names in JSON rows,
Match labels true where the best one grows.
Old zoomy hops now quietly stick."

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is comprehensive, explaining the problem, root cause, and multi-part fix with testing verification. However, it does not follow the provided PR template structure with sections like Type of Change, Related Issue(s), Screenshots/Video, Testing Guide, or Checklist. Restructure the description to follow the template: add Type of Change checkbox, explicitly link issue #662, include Testing Guide section, and complete the Checklist items.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main fix: enabling macOS native ScreenCaptureKit capture to honor the user's selected microphone instead of defaulting to system input.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hooks/useScreenRecorder.ts`:
- Around line 1456-1487: The native macOS recorder must never receive a web-only
microphoneDeviceId; update the logic around
useNativeMacScreenCapture/microphoneEnabled so that before calling
window.electronAPI.startNativeScreenRecording you (a) attempt to resolve a
native ID via listNativeMicrophones using micLabel and, if micLabel is absent,
try to map from the web microphoneDeviceId to a native id as a fallback, and (b)
if no native id (nativeMicId) is found, pass null/undefined (not the original
web microphoneDeviceId) for the microphoneDeviceId argument to
startNativeScreenRecording so the native side won’t receive an incompatible web
ID; touch the code that sets nativeMicId and the call to
startNativeScreenRecording to implement this behavior (referencing
useNativeMacScreenCapture, micLabel, nativeMicId, microphoneDeviceId,
listNativeMicrophones, and startNativeScreenRecording).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1388da0c-fa89-4968-b3c7-0cc0e88eea57

📥 Commits

Reviewing files that changed from the base of the PR and between 7ca2de2 and 27bb600.

📒 Files selected for processing (6)
  • electron/electron-env.d.ts
  • electron/ipc/register/recording.ts
  • electron/native/ScreenCaptureKitRecorder.swift
  • electron/preload.ts
  • src/components/video-editor/videoPlayback/zoomRegionUtils.ts
  • src/hooks/useScreenRecorder.ts

Comment thread src/hooks/useScreenRecorder.ts
@rayl15 rayl15 marked this pull request as draft June 6, 2026 17:21
@rayl15 rayl15 marked this pull request as ready for review June 6, 2026 17:32
On the native macOS path the web MediaDevices deviceId can't match a Core
Audio uniqueID, so falling back to it when native resolution fails just
reintroduces the silent default-mic fallback. Pass only the resolved
uniqueID on mac (undefined when unresolved) and let the helper's label
matching be the backstop; keep the web id for the non-mac path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant