(
options: UseAudioRecorderOptions<(recording: AudioRecording) => unknown> = {},
)
| 57 | options?: UseAudioRecorderOptions<undefined>, |
| 58 | ): UseAudioRecorderReturn<AudioRecording> |
| 59 | export function useAudioRecorder( |
| 60 | options: UseAudioRecorderOptions<(recording: AudioRecording) => unknown> = {}, |
| 61 | ): UseAudioRecorderReturn<unknown> { |
| 62 | const [isRecording, setIsRecording] = useState(false) |
| 63 | const [recording, setRecording] = useState<unknown>(null) |
| 64 | // Read the freshest callbacks at fire time without recreating the recorder. |
| 65 | const optionsRef = useRef(options) |
| 66 | optionsRef.current = options |
| 67 | |
| 68 | const recorder = useMemo( |
| 69 | () => |
| 70 | new AudioRecorder({ |
| 71 | ...(options.audio !== undefined && { audio: options.audio }), |
| 72 | ...(options.mimeType !== undefined && { mimeType: options.mimeType }), |
| 73 | onError: (err) => optionsRef.current.onError?.(err), |
| 74 | }), |
| 75 | // Recorder config (audio/mimeType) is captured once at mount, matching the |
| 76 | // other hooks' create-once pattern. |
| 77 | [], |
| 78 | ) |
| 79 | |
| 80 | useEffect(() => { |
| 81 | const unsubscribe = recorder.subscribe((state) => { |
| 82 | setIsRecording(state === 'recording') |
| 83 | }) |
| 84 | return () => { |
| 85 | unsubscribe() |
| 86 | recorder.cancel() |
| 87 | } |
| 88 | }, [recorder]) |
| 89 | |
| 90 | const start = useCallback(() => recorder.start(), [recorder]) |
| 91 | const stop = useCallback(async () => { |
| 92 | const recording = await recorder.stop() |
| 93 | const transformed = await optionsRef.current.onComplete?.(recording) |
| 94 | // Only `undefined` (returning nothing) falls back to the raw recording, so |
| 95 | // a transform that returns null is preserved — matching the inferred type, |
| 96 | // which excludes only undefined/void/null from the transform's return. |
| 97 | const output = transformed === undefined ? recording : transformed |
| 98 | setRecording(() => output) |
| 99 | return output |
| 100 | }, [recorder]) |
| 101 | const cancel = useCallback(() => recorder.cancel(), [recorder]) |
| 102 | |
| 103 | return { |
| 104 | recording, |
| 105 | isRecording, |
| 106 | // recording is client-only; if SSR'd, gate UI on a mounted flag. |
| 107 | isSupported: AudioRecorder.isSupported(), |
| 108 | start, |
| 109 | stop, |
| 110 | cancel, |
| 111 | } |
| 112 | } |
no test coverage detected