useMicrophone

React hook for capturing microphone audio

useMicrophone wraps getUserMedia, the Web Audio API, and MediaRecorder into a single hook. It opens and closes the microphone stream, exposes a throttled audio level (0–1, RMS) suitable for a VU meter, and records the active stream to a Blob with a hook-managed object URL.

The live-stream controls (start / stop) and the recording controls (startRecording / stopRecording / pauseRecording / resumeRecording) are independent: you can show a level meter without recording, and start or stop a recording without dropping the microphone.

When to Use

  • Building voice-note or dictation UIs where the user sees a live input level before and while recording
  • Adding “speak now” prompts or microphone calibration screens that need a VU meter but no capture
  • Recording short audio clips to a Blob for upload or local playback

Notes

  • SSR-safe: Returns isSupported: false and no-op controls during server-side rendering. No navigator.mediaDevices access occurs on the server.
  • start() before startRecording(): Recording captures an already-open stream. Call start() first; calling startRecording() without an active stream sets error instead of recording.
  • HTTPS required: In production the microphone requires a secure context (HTTPS). The browser prompts for permission on the first start().
  • Object URL lifecycle: audioUrl is created and revoked by the hook — it is replaced on the next recording and revoked on unmount, so consumers do not need to call URL.revokeObjectURL themselves.
  • Mime type: The recording format is auto-selected from the formats the browser supports (audio/webm;codecs=opus, audio/webm, audio/mp4, audio/ogg;codecs=opus). The resolved value is exposed as mimeType.
  • Related hooks: Use useMediaDevices to enumerate microphones and pass a deviceId, or useSpeechRecognition for speech-to-text.

Usage

Live Editor
function Demo() {
  const {
    isSupported,
    isActive,
    level,
    isRecording,
    isPaused,
    audioUrl,
    mimeType,
    error,
    start,
    stop,
    startRecording,
    stopRecording,
    pauseRecording,
    resumeRecording,
  } = useMicrophone();

  if (!isSupported) {
    return <div>Microphone is not supported in this browser</div>;
  }

  const levelPercent = Math.round(level * 100);

  return (
    <div>
      <div style={{ marginBottom: '16px' }}>
        <p>
          <strong>Microphone:</strong> {isActive ? 'Open' : 'Closed'}
          {' · '}
          <strong>Recording:</strong>{' '}
          {isRecording ? (isPaused ? 'Paused' : 'Recording...') : 'Idle'}
        </p>

        <div
          style={{
            height: '16px',
            width: '100%',
            borderRadius: '4px',
            backgroundColor: 'var(--ifm-color-emphasis-200)',
            overflow: 'hidden',
          }}
        >
          <div
            style={{
              height: '100%',
              width: `${levelPercent}%`,
              backgroundColor:
                levelPercent > 70
                  ? 'var(--ifm-color-danger)'
                  : 'var(--ifm-color-success)',
              transition: 'width 80ms linear',
            }}
          />
        </div>
        <p style={{ fontSize: '13px', color: 'var(--ifm-color-content-secondary)' }}>
          Input level: {levelPercent}%
        </p>
      </div>

      <div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap', marginBottom: '12px' }}>
        <button onClick={() => start()} disabled={isActive}>
          Open mic
        </button>
        <button onClick={() => stop()} disabled={!isActive}>
          Close mic
        </button>
        <button onClick={() => startRecording()} disabled={!isActive || isRecording}>
          Record
        </button>
        <button onClick={() => stopRecording()} disabled={!isRecording}>
          Stop recording
        </button>
        <button onClick={() => pauseRecording()} disabled={!isRecording || isPaused}>
          Pause
        </button>
        <button onClick={() => resumeRecording()} disabled={!isPaused}>
          Resume
        </button>
      </div>

      {audioUrl && (
        <div style={{ marginBottom: '12px' }}>
          <p style={{ fontSize: '13px', color: 'var(--ifm-color-content-secondary)' }}>
            Recorded clip ({mimeType || 'default format'}):
          </p>
          <audio src={audioUrl} controls />
        </div>
      )}

      {error && (
        <p style={{ color: 'var(--ifm-color-danger)' }}>
          <strong>Error:</strong> {error.message}
        </p>
      )}
    </div>
  );
}
Result

Common Use Cases

  • Voice notes: Open the mic so the user sees a live level, then record to a Blob and play it back or upload it.
  • Level metering: Drive a VU meter or waveform from level without recording anything.
  • Microphone calibration: Let users confirm the right device is picked up before they start.

API

UseMicrophoneOptions

PropertyDescriptionTypeDefaultValue
deviceIdSpecific microphone deviceId; re-acquires the stream when changed while activestring-
constraintsExtra MediaTrackConstraints merged with the defaults; deviceId above takes precedenceMediaTrackConstraints-
levelIntervalThrottle interval (ms) for level state updatesnumber100
mimeTypePreferred MediaRecorder mime type; falls back to auto-selection if unsupportedstring-
autoStartAutomatically open the microphone on mountbooleanfalse

useMicrophone

Returns

UseMicrophoneReturn: An object exposing the microphone stream, audio level, recording controls, and lifecycle methods

Arguments

ArgumentDescriptionTypeDefaultValue
optionsOptional configurationUseMicrophoneOptions | undefined-