Skip to content

Fix macOS Sidecar microphone duplication#641

Closed
webadderall wants to merge 2 commits into
mainfrom
fix/macos-sidecar-mic-filter
Closed

Fix macOS Sidecar microphone duplication#641
webadderall wants to merge 2 commits into
mainfrom
fix/macos-sidecar-mic-filter

Conversation

@webadderall
Copy link
Copy Markdown
Collaborator

@webadderall webadderall commented Jun 1, 2026

Summary

  • filter Sidecar and display-audio endpoints out of the renderer microphone list
  • sanitize the macOS ScreenCaptureKit microphone resolver so stale saved Sidecar selections fall back to a real/default mic instead of duplicating audio
  • add focused tests for the microphone-device heuristics

Verification

  • npm --prefix /Users/work/Desktop/recordly test -- src/hooks/useMicrophoneDevices.test.ts src/hooks/useScreenRecorder.test.ts
  • node scripts/build-native-helpers.mjs

Summary by CodeRabbit

  • New Features

    • Microphone selection now heuristically excludes display/output audio devices (monitors, speakers, AirPlay, HDMI, etc.), improving accuracy of available mic options.
  • Bug Fixes

    • Prevents output/display audio endpoints from being chosen as microphone inputs during recording.
  • Tests

    • Added unit tests verifying detection and exclusion of display audio inputs from microphone listings.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 1, 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: 87262931-2a93-4a62-839f-9598adc93950

📥 Commits

Reviewing files that changed from the base of the PR and between b92453a and 474ca5a.

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

📝 Walkthrough

Walkthrough

Adds label-token heuristics to identify and exclude display-route audio devices from microphone selection and applies this filter in web hooks and the native ScreenCaptureKit recorder before resolving microphone device IDs.

Changes

Display Audio Device Filtering

Layer / File(s) Summary
Display audio filtering helpers and tests
src/hooks/useMicrophoneDevices.ts, src/hooks/useMicrophoneDevices.test.ts
Introduces isLikelyDisplayAudioInputLabel for token-based label classification and getAvailableMicrophoneDevices to filter MediaDeviceInfo entries, excluding display-route devices and normalizing to MicrophoneDevice objects, with Vitest coverage for label classification and device filtering.
Microphone hook integration
src/hooks/useMicrophoneDevices.ts
Replaces inline audioinput filtering with getAvailableMicrophoneDevices calls both on initial device enumeration and after microphone permission requests, applying display-audio exclusion throughout the hook lifecycle.
Screen recorder hook integration
src/hooks/useScreenRecorder.ts
Imports and uses getAvailableMicrophoneDevices to filter microphone candidates in both audio snapshot metadata construction and native screen capture startup, resolving the microphone device ID through the filtered list instead of direct enumeration.
Native recorder filtering
electron/native/ScreenCaptureKitRecorder.swift
Adds isLikelyDisplayAudioDevice heuristic to classify AVCaptureDevice names with token rules and updates resolveMicrophoneCaptureDeviceID to filter audio candidates by excluding display-route devices before label/ID matching.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

Checked

Poem

🐰 Speakers and airplay got filtered out today,
Labels parsed and tokens chased away,
Microphones now chosen in a cleaner way,
Native and web in step, hip-hip-hooray! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
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.
Description check ❓ Inconclusive The description covers the purpose and changes, but lacks several required template sections including Type of Change, Related Issues, Testing Guide details, and checklist items. Complete the PR description by adding missing template sections: specify the type of change, link related issues, provide detailed testing instructions, and check the verification checklist items.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: filtering out macOS Sidecar display-audio devices from microphone selection to prevent duplication.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/macos-sidecar-mic-filter

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/hooks/useMicrophoneDevices.ts (1)

73-83: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep the raw labels for the permission check.

getAvailableMicrophoneDevices() now fills empty labels with a fallback name, so needsLabelPermission can never become true here. On browsers that hide device labels until getUserMedia() runs, this will leave the hook stuck with synthetic names and skip the re-enumeration path entirely.

Suggested fix
-				let allDevices = await navigator.mediaDevices.enumerateDevices();
-				let audioInputs = getAvailableMicrophoneDevices(allDevices);
+				let allDevices = await navigator.mediaDevices.enumerateDevices();
+				let audioInputs = getAvailableMicrophoneDevices(allDevices);

-				const needsLabelPermission =
-					audioInputs.length > 0 && audioInputs.every((device) => !device.label.trim());
+				const rawAudioInputs = allDevices.filter(
+					(device): device is MediaDeviceInfo => device.kind === "audioinput",
+				);
+				const needsLabelPermission =
+					rawAudioInputs.length > 0 &&
+					rawAudioInputs.every((device) => !device.label.trim());
🤖 Prompt for 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.

In `@src/hooks/useMicrophoneDevices.ts` around lines 73 - 83, The permission-check
uses getAvailableMicrophoneDevices which replaces empty labels, so
needsLabelPermission will never detect hidden labels; instead, compute
needsLabelPermission directly from the raw enumerateDevices() result by
filtering devices with kind === 'audioinput' and checking device.label.trim() on
that raw array before calling getAvailableMicrophoneDevices; keep the existing
flow using hasRequestedMicrophoneLabels and permissionStream and only call
getUserMedia() / re-enumerate if the raw-label check indicates permission is
needed.
src/hooks/useScreenRecorder.ts (1)

1523-1544: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reuse the sanitized mic id in the fallback path too.

This only fixes the ID for startNativeScreenRecording(). If microphoneFallbackRequired is returned later, the browser fallback still calls createProcessedMicrophoneConstraints(microphoneDeviceId, ...) with the stale saved selection, so a filtered Sidecar/display device can still break microphone capture on that branch.

Suggested fix
-				let nativeMicrophoneDeviceId = microphoneDeviceId;
+				let resolvedMicrophoneDeviceId = microphoneDeviceId;
 				if (microphoneEnabled) {
 					try {
 						const devices = await navigator.mediaDevices.enumerateDevices();
 						const availableMicrophones = getAvailableMicrophoneDevices(devices);
 						const mic = availableMicrophones.find(
 							(device) => device.deviceId === microphoneDeviceId,
 						);
-						nativeMicrophoneDeviceId = mic?.deviceId;
+						resolvedMicrophoneDeviceId = mic?.deviceId;
 						micLabel = mic?.label || undefined;
 					} catch {
 						// Fall through — native process will use the default mic
 					}
 				}
@@
-						microphoneDeviceId: nativeMicrophoneDeviceId,
+						microphoneDeviceId: resolvedMicrophoneDeviceId,
 						microphoneLabel: micLabel,
 					},
 				);

And then use resolvedMicrophoneDeviceId when creating the browser fallback constraints as well.

🤖 Prompt for 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.

In `@src/hooks/useScreenRecorder.ts` around lines 1523 - 1544, The sanitized
microphone device id obtained for native recording isn't reused for the browser
fallback path; update the logic in useScreenRecorder.ts so the
resolved/sanitized id (e.g., nativeMicrophoneDeviceId or a new
resolvedMicrophoneDeviceId) that you set before calling
window.electronAPI.startNativeScreenRecording is also passed into
createProcessedMicrophoneConstraints(...) when handling
microphoneFallbackRequired, and use the same micLabel variable for consistency;
ensure createProcessedMicrophoneConstraints and the microphoneFallbackRequired
branch reference that resolved id instead of the original microphoneDeviceId.
🤖 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.

Outside diff comments:
In `@src/hooks/useMicrophoneDevices.ts`:
- Around line 73-83: The permission-check uses getAvailableMicrophoneDevices
which replaces empty labels, so needsLabelPermission will never detect hidden
labels; instead, compute needsLabelPermission directly from the raw
enumerateDevices() result by filtering devices with kind === 'audioinput' and
checking device.label.trim() on that raw array before calling
getAvailableMicrophoneDevices; keep the existing flow using
hasRequestedMicrophoneLabels and permissionStream and only call getUserMedia() /
re-enumerate if the raw-label check indicates permission is needed.

In `@src/hooks/useScreenRecorder.ts`:
- Around line 1523-1544: The sanitized microphone device id obtained for native
recording isn't reused for the browser fallback path; update the logic in
useScreenRecorder.ts so the resolved/sanitized id (e.g.,
nativeMicrophoneDeviceId or a new resolvedMicrophoneDeviceId) that you set
before calling window.electronAPI.startNativeScreenRecording is also passed into
createProcessedMicrophoneConstraints(...) when handling
microphoneFallbackRequired, and use the same micLabel variable for consistency;
ensure createProcessedMicrophoneConstraints and the microphoneFallbackRequired
branch reference that resolved id instead of the original microphoneDeviceId.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4153d067-8fa4-4389-897f-fdbe3542c0c6

📥 Commits

Reviewing files that changed from the base of the PR and between 1f9912b and b92453a.

📒 Files selected for processing (6)
  • electron/native/ScreenCaptureKitRecorder.swift
  • electron/native/bin/darwin-arm64/recordly-screencapturekit-helper
  • electron/native/bin/darwin-x64/recordly-screencapturekit-helper
  • src/hooks/useMicrophoneDevices.test.ts
  • src/hooks/useMicrophoneDevices.ts
  • src/hooks/useScreenRecorder.ts

@webadderall
Copy link
Copy Markdown
Collaborator Author

Closing this for now while we re-evaluate the root cause. The current patch targets bad microphone device selection, but the more likely issue is simultaneous active audio capture paths on macOS.

@webadderall webadderall closed this Jun 1, 2026
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