Skip to content

feat(audio): warn and disable recording when no input device detected…#1000

Open
Luke-Bilhorn wants to merge 9 commits into
mainfrom
980-no-audio-detected-message-when-input-device-is-not-detected
Open

feat(audio): warn and disable recording when no input device detected…#1000
Luke-Bilhorn wants to merge 9 commits into
mainfrom
980-no-audio-detected-message-when-input-device-is-not-detected

Conversation

@Luke-Bilhorn
Copy link
Copy Markdown
Contributor

… (#980)

Add useAudioInputDevices hook that enumerates audioinput devices and listens for devicechange events so plugging/unplugging a mic updates the UI live. When no device is present, the recorder button is disabled, its tooltip reads "No microphone detected", and an inline warning row appears below the button.

Includes a window.__forceNoAudioInput debug flag for QA on machines with a working mic.

…#980)

Add useAudioInputDevices hook that enumerates audioinput devices and
listens for `devicechange` events so plugging/unplugging a mic updates
the UI live. When no device is present, the recorder button is disabled,
its tooltip reads "No microphone detected", and an inline warning row
appears below the button.

Includes a `window.__forceNoAudioInput` debug flag for QA on machines
with a working mic.
@Luke-Bilhorn Luke-Bilhorn linked an issue May 28, 2026 that may be closed by this pull request
)

Visual polish for the no-microphone state:
- Add an `unavailable` prop to RecorderCircle that's distinct from
  `disabled` so the locked-cell visual (faded primary mic) is preserved
  while the no-mic state gets a solid mid-grey circle with a white
  MicOff (slashed) icon and no pulse animation.
- Replace the inline AlertTriangle hint with a ShadCN Alert box matching
  the existing yellow-warning pattern used in the source importer forms.
  Constrained to `w-fit` so it hugs its text and centers under the mic.

Hook robustness:
- Capture `navigator.mediaDevices` at effect mount so the cleanup path
  removes its listener from the original reference, even if the global
  is swapped out later (tests, hot-reload, polyfills).

Tests:
- 5 unit tests for useAudioInputDevices covering presence, absence,
  live `devicechange` hot-plug, the `__forceNoAudioInput` debug flag,
  and graceful fallback when `enumerateDevices` is unavailable.
- 1 integration test asserting the record button disables, the warning
  renders, and `getUserMedia` is never reached when no device exists.
@Luke-Bilhorn
Copy link
Copy Markdown
Contributor Author

image

@Luke-Bilhorn
Copy link
Copy Markdown
Contributor Author

Claude's instructions for how to test with a working mic (or just ask agent to use forceNoAudioInput lol)

Testing the no-microphone UI with a working mic

  1. Pull this branch

  2. Open any cell → switch to the Audio tab.

  3. Open the Command Palette (Cmd+Shift+P) → run Developer: Open Webview Developer Tools.

  4. In the devtools Console that opens, paste:

    window.__forceNoAudioInput = true;
    navigator.mediaDevices.dispatchEvent(new Event("devicechange"));
  5. The mic button should immediately turn solid grey with a slashed-mic icon, and a yellow alert appears below: "No microphone detected. Connect an input device to record."

To restore the normal state:

window.__forceNoAudioInput = false;
navigator.mediaDevices.dispatchEvent(new Event("devicechange"));

The flag is intentionally left in production code (documented in hooks/useAudioInputDevices.ts) and does nothing unless explicitly set, so it's safe to ship. Useful for ongoing QA on machines with working mics.

@Luke-Bilhorn
Copy link
Copy Markdown
Contributor Author

/build

@github-actions
Copy link
Copy Markdown

Copy link
Copy Markdown
Contributor

@LeviXIII LeviXIII left a comment

Choose a reason for hiding this comment

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

Doesn't seem to work for me. I tried removing permissions (which would be an ideal case for this warning to come up for as well) and disabling using the debug commands. I am working on a mac.

Also, please check the padding difference from the top to the bottom of the warning section. Also the margin between the icon and the text in the warning message as it is also not vertically aligned.

@Luke-Bilhorn
Copy link
Copy Markdown
Contributor Author

image

@Luke-Bilhorn
Copy link
Copy Markdown
Contributor Author

image

Luke-Bilhorn and others added 3 commits June 2, 2026 15:32
…#980)

- Suppress countdown/recording on auto-start when mic unavailable
- Center alert icon/text
- add FORCE_STATE_FOR_REVIEW for testing
- Add hook + integration tests
Attempted but still needing more work
- Detect mic permission-denied and detect no-device mic (separately)
Still haven't gotten either of these to work.
…ication

Chromium's Permissions API only reports browser-level state, not OS-level permission. On macOS, for example, denying microphone access in System Settings still returns "granted" from navigator.permissions.query — so the hook never flipped to permission-denied without an actual record attempt.

Expose `reportRecorderError(err)` from useAudioInputDevices and call it from startActualRecording's catch block. The hook classifies common error names (NotAllowedError, NotFoundError, etc.) and pins availability to the matching state. A successful getUserMedia clears the override so the UI recovers automatically after the user fixes permissions and retries.
@Luke-Bilhorn
Copy link
Copy Markdown
Contributor Author

Matt, I have fixed all of the things we discussed:

  • warning spacing
  • mic countdown issues
  • separate messages for no mic connected vs no mic permission

I have tested it and found that we are now correctly getting the OS-level permission (see last commit message), but I have no way to verify that we will be correctly getting the OS-level state for a device with no mic. I think it's pretty likely we are because it is the same type of thing, but this is information I can't learn from my own device. Even if we can't test this before Thursday, I think we should still merge it because the case about no permission is way more important.

What Claude says:
Chromium's Permissions API only reports browser-level state, not OS-level permission. On macOS, for example, denying microphone access in System Settings still returns "granted" from navigator.permissions.query — so the hook never flipped to permission-denied without an actual record attempt.

Expose reportRecorderError(err) from useAudioInputDevices and call it from startActualRecording's catch block. The hook classifies common error names (NotAllowedError, NotFoundError, etc.) and pins availability to the matching state. A successful getUserMedia clears the override so the UI recovers automatically after the user fixes permissions and retries.

@Luke-Bilhorn
Copy link
Copy Markdown
Contributor Author

Testing

The yellow warning has two variants: "No microphone detected" (no audio input hardware) and "Microphone access denied" (a mic exists but is blocked). Both render the same grey, slashed-mic button.

To force either state without unplugging hardware or revoking permissions, edit line 41 of webviews/codex-webviews/src/CodexCellEditor/hooks/useAudioInputDevices.ts:

const FORCE_STATE_FOR_REVIEW: MicAvailability | null = null;

Replace null; with one of:

"no-device";
"permission-denied";

Then rebuild and reload:

cd webviews/codex-webviews && pnpm run build:CodexCellEditor

Hit Cmd+R in the Extension Development Host window. Set the constant back to null before merging.

Once forced into either state, also verify:

  • Clicking the disabled mic button does nothing (no countdown, no getUserMedia call)
  • If the auto-start-on-cell-open feature is enabled, opening a cell with no mic does NOT start a countdown

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.

No audio detected message when input device is not detected

2 participants