Skip to content
Sign in
VoiceAudio device testMenu

Audio device testPro

The audio check before a call: pick a microphone and watch it pass on your voice, pick a speaker and hear a chime through it. Too quiet, no sound and too loud each say what to change. The setup flow version of the free voice controls.

ProPick a microphone and watch it pass on speech, play a chime through the speaker, then join.

Installation

$ pnpm dlx shadcn@latest add @chatcn-pro/audio-device-test

Usage

tsx
import { AudioDeviceTest } from "@/components/ui/chatcn/pro/audio-device-test"

<AudioDeviceTest
  microphones={devices.microphones}
  microphone={micId}
  onMicrophoneChange={setMicId}
  speakers={devices.speakers}
  speaker={speakerId}
  onSpeakerChange={setSpeakerId}
  inputLevel={meter.getLevel}
  onContinue={joinCall}
/>

Devices and the level are plain props, in the shape the free useChatcnVoiceDevices, useChatcnMicrophone and useChatcnAudioLevel hooks return, so you can feed it real devices or a simulation. The microphone passes once speech stays above the mark for about a quarter of a second, so a click or a cough does not count. After a few seconds of a weak signal it says too quiet, of a flat line no sound (usually a mute switch or a system setting), and sustained full scale is too loud.

Play test tone plays a quiet two note chime through the chosen output with AudioContext.setSinkId, falling back to an audio element's setSinkId, then asks whether you heard it. Where the browser cannot pick outputs the speaker row says so and plays through the system output. The audio context starts inside the click, as browsers require. Pass onTestTone to play your own sound.

Examples

Every state

ProPermission, every microphone verdict, an unplugged headset, no devices, the speaker question, no output picking and disabled.

Your real devices

ProThe same check on your real microphone and speakers, through the free voice hooks.

Props

PropTypeNotes
microphones, speakersChatcnAudioDevice[]From useChatcnVoiceDevices
microphone, onMicrophoneChangestring, (id) => voidA chosen device that disappears is named as disconnected
speaker, onSpeakerChangestring, (id) => voidA new speaker resets the speaker check
inputLevelnumber | () => number0 to 1. A getter drives the meter without re-rendering
micStatus"needs-permission" | "requesting" | "active" | "error"Default active. The first two show the inline permission gate
micError"denied" | "system-blocked" | "not-found" | "in-use" | "unsupported"Shown with its fix when micStatus is error
onRequestMicrophone() => voidFrom the gate's Allow and Try again
canPickSpeakerbooleanDetected when undefined
check"waiting" | "heard" | "quiet" | "silent" | "clipping"Force the verdict. Measured from inputLevel otherwise
speakerCheck, defaultSpeakerCheck, onSpeakerCheckChange"idle" | "playing" | "asked" | "yes" | "no"Controlled or uncontrolled
onTestTone(speakerId) => Promise | undefinedPlay your own sound instead of the chime
onContinue, continueLabel() => void, stringDefault "Join call". Enabled once the microphone is heard
requireCheckbooleanDefault true. Off enables continue at once
onSkip, skipLabel() => void, stringDefault "Skip"
disabledbooleanDisables every control and stops the meter
title, descriptionReactNodeHeading and one line under it. null hides the line

Keyboard

KeyAction
TabMoves through the selects, Play test tone, Yes and No, then the footer
Enter, SpaceOpens a select or activates the focused button
Arrow keysMove through devices in an open select

Accessibility

  • The selects take their names from the visible Microphone and Speaker labels.
  • The meter is hidden from assistive technology; the verdict is announced instead, once per change, through a polite live region.
  • Verdicts never rely on color: a check or warning glyph and a word sit beside the green or amber.
  • Join call stays disabled until the check passes, with the reason written next to it.
  • Every control grows to 44 px on coarse pointers.

Motion

  • The meter is written to the DOM each frame as one clip on the lit layer, with a peak cell that holds for 700 ms and then falls. It keeps moving under reduced motion: it is the reading, not decoration.
  • Verdicts and the play button label swap with a 150 ms crossfade; the speaker question rises in over 180 ms.