@devicai/ui 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/components/ChatDrawer/ChatDrawer.js +2 -1
- package/dist/cjs/components/ChatDrawer/ChatDrawer.js.map +1 -1
- package/dist/cjs/components/ChatDrawer/ChatInput.js +4 -1
- package/dist/cjs/components/ChatDrawer/ChatInput.js.map +1 -1
- package/dist/cjs/hooks/useSpeechRecording.js +29 -10
- package/dist/cjs/hooks/useSpeechRecording.js.map +1 -1
- package/dist/esm/components/ChatDrawer/ChatDrawer.js +2 -1
- package/dist/esm/components/ChatDrawer/ChatDrawer.js.map +1 -1
- package/dist/esm/components/ChatDrawer/ChatDrawer.types.d.ts +9 -0
- package/dist/esm/components/ChatDrawer/ChatInput.d.ts +1 -1
- package/dist/esm/components/ChatDrawer/ChatInput.js +4 -1
- package/dist/esm/components/ChatDrawer/ChatInput.js.map +1 -1
- package/dist/esm/hooks/useSpeechRecording.d.ts +15 -2
- package/dist/esm/hooks/useSpeechRecording.js +29 -10
- package/dist/esm/hooks/useSpeechRecording.js.map +1 -1
- package/package.json +1 -1
|
@@ -23,9 +23,22 @@ export interface UseSpeechRecordingOptions {
|
|
|
23
23
|
autoStopSilenceMs?: number;
|
|
24
24
|
/** Duration (ms) of the auto-stop countdown shown before firing. @default 1000 */
|
|
25
25
|
autoStopCountdownMs?: number;
|
|
26
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Silence is **adaptive**: the threshold is this fraction of the loudest
|
|
28
|
+
* speech observed in the recording (e.g. 0.1 = 10% of peak voice). This makes
|
|
29
|
+
* it robust to ambient noise — a quiet room and a loud one calibrate
|
|
30
|
+
* differently. @default 0.1
|
|
31
|
+
*/
|
|
32
|
+
autoStopSilenceRatio?: number;
|
|
33
|
+
/**
|
|
34
|
+
* Absolute floor (0..1) for the adaptive silence threshold, so it never drops
|
|
35
|
+
* so low that background hiss reads as "sound". @default 0.02
|
|
36
|
+
*/
|
|
27
37
|
autoStopSilenceLevel?: number;
|
|
28
|
-
/**
|
|
38
|
+
/**
|
|
39
|
+
* Absolute floor (0..1) a peak must clear to first count as speech (arms the
|
|
40
|
+
* detector / calibrates the reference loudness). @default 0.12
|
|
41
|
+
*/
|
|
29
42
|
autoStopSpeechLevel?: number;
|
|
30
43
|
/**
|
|
31
44
|
* Fired when the silence countdown completes. The consumer decides what
|
|
@@ -26,7 +26,7 @@ function pickMimeType(preferred) {
|
|
|
26
26
|
* cancelling the animation frame on stop/cancel/unmount.
|
|
27
27
|
*/
|
|
28
28
|
function useSpeechRecording(options = {}) {
|
|
29
|
-
const { bars = 5, mimeType, autoStop = true, autoStopSilenceMs = 1000, autoStopCountdownMs = 1000, autoStopSilenceLevel = 0.
|
|
29
|
+
const { bars = 5, mimeType, autoStop = true, autoStopSilenceMs = 1000, autoStopCountdownMs = 1000, autoStopSilenceRatio = 0.1, autoStopSilenceLevel = 0.02, autoStopSpeechLevel = 0.12, onAutoStop, } = options;
|
|
30
30
|
const isSupported = typeof navigator !== 'undefined' &&
|
|
31
31
|
!!navigator.mediaDevices?.getUserMedia &&
|
|
32
32
|
typeof MediaRecorder !== 'undefined';
|
|
@@ -49,6 +49,7 @@ function useSpeechRecording(options = {}) {
|
|
|
49
49
|
// --- Auto-stop (silence detection) state, all kept in refs so the rAF loop
|
|
50
50
|
// reads it without forcing `tick` to re-subscribe. ---
|
|
51
51
|
const hasSpeechRef = useRef(false); // user has spoken at least once
|
|
52
|
+
const loudestRef = useRef(0); // loudest peak seen (reference for adaptive silence)
|
|
52
53
|
const silenceStartRef = useRef(null); // when current silence began
|
|
53
54
|
const autoStopStartRef = useRef(null); // when the countdown started
|
|
54
55
|
const autoStopFiredRef = useRef(false); // guard against re-firing before stop
|
|
@@ -57,6 +58,7 @@ function useSpeechRecording(options = {}) {
|
|
|
57
58
|
autoStop,
|
|
58
59
|
autoStopSilenceMs,
|
|
59
60
|
autoStopCountdownMs,
|
|
61
|
+
autoStopSilenceRatio,
|
|
60
62
|
autoStopSilenceLevel,
|
|
61
63
|
autoStopSpeechLevel,
|
|
62
64
|
onAutoStop,
|
|
@@ -65,6 +67,7 @@ function useSpeechRecording(options = {}) {
|
|
|
65
67
|
autoStop,
|
|
66
68
|
autoStopSilenceMs,
|
|
67
69
|
autoStopCountdownMs,
|
|
70
|
+
autoStopSilenceRatio,
|
|
68
71
|
autoStopSilenceLevel,
|
|
69
72
|
autoStopSpeechLevel,
|
|
70
73
|
onAutoStop,
|
|
@@ -72,8 +75,10 @@ function useSpeechRecording(options = {}) {
|
|
|
72
75
|
// Reset all auto-stop tracking. `keepSpeech` preserves the "user has spoken"
|
|
73
76
|
// flag across a pause/resume so it doesn't re-arm from scratch.
|
|
74
77
|
const resetAutoStop = useCallback((keepSpeech = false) => {
|
|
75
|
-
if (!keepSpeech)
|
|
78
|
+
if (!keepSpeech) {
|
|
76
79
|
hasSpeechRef.current = false;
|
|
80
|
+
loudestRef.current = 0;
|
|
81
|
+
}
|
|
77
82
|
silenceStartRef.current = null;
|
|
78
83
|
autoStopStartRef.current = null;
|
|
79
84
|
autoStopFiredRef.current = false;
|
|
@@ -87,9 +92,26 @@ function useSpeechRecording(options = {}) {
|
|
|
87
92
|
if (!cfg.autoStop || autoStopFiredRef.current)
|
|
88
93
|
return;
|
|
89
94
|
const now = Date.now();
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
// 1) Arm only once a real voice peak (absolute floor) has been heard. This
|
|
96
|
+
// also seeds the reference loudness so ambient noise alone can't arm it.
|
|
97
|
+
if (!hasSpeechRef.current) {
|
|
98
|
+
if (peak >= cfg.autoStopSpeechLevel) {
|
|
99
|
+
hasSpeechRef.current = true;
|
|
100
|
+
loudestRef.current = peak;
|
|
101
|
+
}
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
// 2) Track the loudest voice so the silence threshold scales with how loud
|
|
105
|
+
// the user actually speaks (robust to ambient noise).
|
|
106
|
+
if (peak > loudestRef.current)
|
|
107
|
+
loudestRef.current = peak;
|
|
108
|
+
// 3) Adaptive thresholds: silence = ratio of the loudest voice (with an
|
|
109
|
+
// absolute floor); activity sits above it for hysteresis, so background
|
|
110
|
+
// noise between the two doesn't keep cancelling the countdown.
|
|
111
|
+
const silenceThreshold = Math.max(cfg.autoStopSilenceLevel, cfg.autoStopSilenceRatio * loudestRef.current);
|
|
112
|
+
const activityThreshold = silenceThreshold * 1.8;
|
|
113
|
+
if (peak >= activityThreshold) {
|
|
114
|
+
// Talking again: reset silence and cancel any pending countdown.
|
|
93
115
|
silenceStartRef.current = null;
|
|
94
116
|
if (autoStopStartRef.current !== null) {
|
|
95
117
|
autoStopStartRef.current = null;
|
|
@@ -98,10 +120,7 @@ function useSpeechRecording(options = {}) {
|
|
|
98
120
|
}
|
|
99
121
|
return;
|
|
100
122
|
}
|
|
101
|
-
|
|
102
|
-
if (!hasSpeechRef.current)
|
|
103
|
-
return;
|
|
104
|
-
if (peak <= cfg.autoStopSilenceLevel) {
|
|
123
|
+
if (peak <= silenceThreshold) {
|
|
105
124
|
if (silenceStartRef.current === null)
|
|
106
125
|
silenceStartRef.current = now;
|
|
107
126
|
if (autoStopStartRef.current === null) {
|
|
@@ -127,7 +146,7 @@ function useSpeechRecording(options = {}) {
|
|
|
127
146
|
}
|
|
128
147
|
}
|
|
129
148
|
}
|
|
130
|
-
//
|
|
149
|
+
// Hysteresis deadzone (silence < peak < activity): keep timers running.
|
|
131
150
|
}, []);
|
|
132
151
|
const cleanupAudioGraph = useCallback(() => {
|
|
133
152
|
if (rafRef.current !== null) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSpeechRecording.js","sources":["../../../src/hooks/useSpeechRecording.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\n/**\n * Recording lifecycle state.\n * - `idle`: not recording\n * - `recording`: actively capturing audio\n * - `paused`: capture paused, can be resumed\n */\nexport type SpeechRecordingStatus = 'idle' | 'recording' | 'paused';\n\nexport interface UseSpeechRecordingOptions {\n /** Number of equalizer bars to expose in `levels`. @default 5 */\n bars?: number;\n /** Preferred MediaRecorder mime type. Falls back to a supported one. */\n mimeType?: string;\n /**\n * Auto-stop the recording after a short silence, but only once speech has\n * actually been detected. When it triggers, the countdown runs and then\n * `onAutoStop` is fired. Disable to require a manual confirm. @default true\n */\n autoStop?: boolean;\n /**\n * Continuous silence (ms) that must elapse — after speech was detected —\n * before the auto-stop countdown begins. @default 1000\n */\n autoStopSilenceMs?: number;\n /** Duration (ms) of the auto-stop countdown shown before firing. @default 1000 */\n autoStopCountdownMs?: number;\n /** Normalized peak level (0..1) at/below which audio is treated as silence. @default 0.04 */\n autoStopSilenceLevel?: number;\n /** Normalized peak level (0..1) above which we consider the user has spoken. @default 0.12 */\n autoStopSpeechLevel?: number;\n /**\n * Fired when the silence countdown completes. The consumer decides what\n * \"stop\" means (e.g. confirm + transcribe). Read fresh on every frame, so it\n * does not need to be memoized.\n */\n onAutoStop?: () => void;\n}\n\nexport interface UseSpeechRecordingResult {\n status: SpeechRecordingStatus;\n isRecording: boolean;\n isPaused: boolean;\n /** Whether the browser supports audio recording. */\n isSupported: boolean;\n /** Normalized amplitude per bar (0..1), updated in real time for the equalizer. */\n levels: number[];\n /** Elapsed recording time in milliseconds (excludes paused time). */\n durationMs: number;\n /** Last error message, if any (e.g. permission denied). */\n error: string | null;\n /** True while the silence-triggered auto-stop countdown is running. */\n isAutoStopping: boolean;\n /** Auto-stop countdown progress, 1 (full) → 0 (empty), for an inverted ring. */\n autoStopProgress: number;\n /** Request mic access and start recording. */\n start: () => Promise<void>;\n /** Pause an active recording. */\n pause: () => void;\n /** Resume a paused recording. */\n resume: () => void;\n /** Stop and return the recorded audio as a Blob (null if nothing captured). */\n stop: () => Promise<Blob | null>;\n /** Stop and discard the recording without producing a Blob. */\n cancel: () => void;\n}\n\nconst PREFERRED_MIME_TYPES = [\n 'audio/webm;codecs=opus',\n 'audio/webm',\n 'audio/ogg;codecs=opus',\n 'audio/mp4',\n];\n\nfunction pickMimeType(preferred?: string): string | undefined {\n if (typeof MediaRecorder === 'undefined') return undefined;\n const candidates = preferred\n ? [preferred, ...PREFERRED_MIME_TYPES]\n : PREFERRED_MIME_TYPES;\n for (const type of candidates) {\n if (MediaRecorder.isTypeSupported(type)) return type;\n }\n return undefined;\n}\n\n/**\n * Encapsulates microphone capture via MediaRecorder plus a Web Audio analyser\n * that exposes live amplitude levels for an equalizer-style animation.\n *\n * The hook owns all teardown: stopping tracks, closing the AudioContext and\n * cancelling the animation frame on stop/cancel/unmount.\n */\nexport function useSpeechRecording(\n options: UseSpeechRecordingOptions = {},\n): UseSpeechRecordingResult {\n const {\n bars = 5,\n mimeType,\n autoStop = true,\n autoStopSilenceMs = 1000,\n autoStopCountdownMs = 1000,\n autoStopSilenceLevel = 0.04,\n autoStopSpeechLevel = 0.12,\n onAutoStop,\n } = options;\n\n const isSupported =\n typeof navigator !== 'undefined' &&\n !!navigator.mediaDevices?.getUserMedia &&\n typeof MediaRecorder !== 'undefined';\n\n const [status, setStatus] = useState<SpeechRecordingStatus>('idle');\n const [levels, setLevels] = useState<number[]>(() => new Array(bars).fill(0));\n const [durationMs, setDurationMs] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [isAutoStopping, setIsAutoStopping] = useState(false);\n const [autoStopProgress, setAutoStopProgress] = useState(1);\n\n const mediaRecorderRef = useRef<MediaRecorder | null>(null);\n const streamRef = useRef<MediaStream | null>(null);\n const chunksRef = useRef<Blob[]>([]);\n const audioContextRef = useRef<AudioContext | null>(null);\n const analyserRef = useRef<AnalyserNode | null>(null);\n const rafRef = useRef<number | null>(null);\n const startedAtRef = useRef<number>(0);\n const accumulatedRef = useRef<number>(0);\n const stopResolveRef = useRef<((blob: Blob | null) => void) | null>(null);\n const discardRef = useRef<boolean>(false);\n\n // --- Auto-stop (silence detection) state, all kept in refs so the rAF loop\n // reads it without forcing `tick` to re-subscribe. ---\n const hasSpeechRef = useRef(false); // user has spoken at least once\n const silenceStartRef = useRef<number | null>(null); // when current silence began\n const autoStopStartRef = useRef<number | null>(null); // when the countdown started\n const autoStopFiredRef = useRef(false); // guard against re-firing before stop\n // Latest-config ref so the loop always sees fresh thresholds and callback.\n const autoStopCfgRef = useRef({\n autoStop,\n autoStopSilenceMs,\n autoStopCountdownMs,\n autoStopSilenceLevel,\n autoStopSpeechLevel,\n onAutoStop,\n });\n autoStopCfgRef.current = {\n autoStop,\n autoStopSilenceMs,\n autoStopCountdownMs,\n autoStopSilenceLevel,\n autoStopSpeechLevel,\n onAutoStop,\n };\n\n // Reset all auto-stop tracking. `keepSpeech` preserves the \"user has spoken\"\n // flag across a pause/resume so it doesn't re-arm from scratch.\n const resetAutoStop = useCallback((keepSpeech = false) => {\n if (!keepSpeech) hasSpeechRef.current = false;\n silenceStartRef.current = null;\n autoStopStartRef.current = null;\n autoStopFiredRef.current = false;\n setIsAutoStopping(false);\n setAutoStopProgress(1);\n }, []);\n\n // Evaluate the current peak amplitude against the silence/speech thresholds\n // and drive the auto-stop countdown. Called once per animation frame.\n const evaluateAutoStop = useCallback((peak: number) => {\n const cfg = autoStopCfgRef.current;\n if (!cfg.autoStop || autoStopFiredRef.current) return;\n const now = Date.now();\n\n if (peak >= cfg.autoStopSpeechLevel) {\n // Speaking: remember it and cancel any pending silence/countdown.\n hasSpeechRef.current = true;\n silenceStartRef.current = null;\n if (autoStopStartRef.current !== null) {\n autoStopStartRef.current = null;\n setIsAutoStopping(false);\n setAutoStopProgress(1);\n }\n return;\n }\n\n // Only ever arm once the user has actually spoken.\n if (!hasSpeechRef.current) return;\n\n if (peak <= cfg.autoStopSilenceLevel) {\n if (silenceStartRef.current === null) silenceStartRef.current = now;\n\n if (autoStopStartRef.current === null) {\n // Waiting out the silence window before the countdown begins.\n if (now - silenceStartRef.current >= cfg.autoStopSilenceMs) {\n autoStopStartRef.current = now;\n setIsAutoStopping(true);\n setAutoStopProgress(1);\n }\n } else {\n // Countdown running: drain the ring 1 → 0, then fire.\n const elapsed = now - autoStopStartRef.current;\n const progress = Math.max(0, 1 - elapsed / cfg.autoStopCountdownMs);\n setAutoStopProgress(progress);\n if (elapsed >= cfg.autoStopCountdownMs) {\n autoStopFiredRef.current = true;\n autoStopStartRef.current = null;\n silenceStartRef.current = null;\n setIsAutoStopping(false);\n setAutoStopProgress(1);\n cfg.onAutoStop?.();\n }\n }\n }\n // Deadzone (silence < peak < speech): keep the timers running as-is.\n }, []);\n\n const cleanupAudioGraph = useCallback(() => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n if (audioContextRef.current) {\n audioContextRef.current.close().catch(() => undefined);\n audioContextRef.current = null;\n }\n analyserRef.current = null;\n if (streamRef.current) {\n streamRef.current.getTracks().forEach((t) => t.stop());\n streamRef.current = null;\n }\n }, []);\n\n // Drives both the equalizer levels and the duration counter.\n const tick = useCallback(() => {\n const analyser = analyserRef.current;\n if (analyser) {\n const data = new Uint8Array(analyser.frequencyBinCount);\n analyser.getByteFrequencyData(data);\n const bucketSize = Math.floor(data.length / bars) || 1;\n const next: number[] = [];\n let peak = 0;\n for (let i = 0; i < bars; i++) {\n let sum = 0;\n for (let j = 0; j < bucketSize; j++) {\n sum += data[i * bucketSize + j] ?? 0;\n }\n // Normalize 0..255 -> 0..1 with a small floor so bars stay visible.\n const level = Math.min(1, sum / bucketSize / 255);\n next.push(level);\n if (level > peak) peak = level;\n }\n setLevels(next);\n evaluateAutoStop(peak);\n }\n setDurationMs(accumulatedRef.current + (Date.now() - startedAtRef.current));\n rafRef.current = requestAnimationFrame(tick);\n }, [bars, evaluateAutoStop]);\n\n const start = useCallback(async () => {\n if (!isSupported) {\n setError('Audio recording is not supported in this browser');\n return;\n }\n if (mediaRecorderRef.current) return;\n\n setError(null);\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n streamRef.current = stream;\n\n const selectedType = pickMimeType(mimeType);\n const recorder = selectedType\n ? new MediaRecorder(stream, { mimeType: selectedType })\n : new MediaRecorder(stream);\n mediaRecorderRef.current = recorder;\n chunksRef.current = [];\n discardRef.current = false;\n resetAutoStop();\n\n recorder.ondataavailable = (e: BlobEvent) => {\n if (e.data && e.data.size > 0) chunksRef.current.push(e.data);\n };\n recorder.onstop = () => {\n const type = recorder.mimeType || selectedType || 'audio/webm';\n const blob = discardRef.current\n ? null\n : new Blob(chunksRef.current, { type });\n chunksRef.current = [];\n cleanupAudioGraph();\n mediaRecorderRef.current = null;\n setStatus('idle');\n setLevels(new Array(bars).fill(0));\n setDurationMs(0);\n accumulatedRef.current = 0;\n resetAutoStop();\n const resolve = stopResolveRef.current;\n stopResolveRef.current = null;\n resolve?.(blob);\n };\n\n // Web Audio analyser for the equalizer animation.\n const AudioCtx =\n window.AudioContext ||\n (window as unknown as { webkitAudioContext: typeof AudioContext })\n .webkitAudioContext;\n const audioContext = new AudioCtx();\n const source = audioContext.createMediaStreamSource(stream);\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = 64;\n source.connect(analyser);\n audioContextRef.current = audioContext;\n analyserRef.current = analyser;\n\n accumulatedRef.current = 0;\n startedAtRef.current = Date.now();\n recorder.start(100);\n setStatus('recording');\n rafRef.current = requestAnimationFrame(tick);\n } catch (e) {\n cleanupAudioGraph();\n mediaRecorderRef.current = null;\n const message =\n (e as Error)?.name === 'NotAllowedError'\n ? 'Microphone permission denied'\n : `Could not start recording: ${(e as Error)?.message || 'unknown error'}`;\n setError(message);\n setStatus('idle');\n }\n }, [isSupported, mimeType, bars, tick, cleanupAudioGraph, resetAutoStop]);\n\n const pause = useCallback(() => {\n const recorder = mediaRecorderRef.current;\n if (recorder && recorder.state === 'recording') {\n recorder.pause();\n accumulatedRef.current += Date.now() - startedAtRef.current;\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n setLevels(new Array(bars).fill(0));\n // Drop any in-flight silence/countdown but remember that speech happened.\n resetAutoStop(true);\n setStatus('paused');\n }\n }, [bars, resetAutoStop]);\n\n const resume = useCallback(() => {\n const recorder = mediaRecorderRef.current;\n if (recorder && recorder.state === 'paused') {\n recorder.resume();\n startedAtRef.current = Date.now();\n // Fresh silence timing on resume so it doesn't fire immediately.\n resetAutoStop(true);\n rafRef.current = requestAnimationFrame(tick);\n setStatus('recording');\n }\n }, [tick, resetAutoStop]);\n\n const stop = useCallback((): Promise<Blob | null> => {\n const recorder = mediaRecorderRef.current;\n if (!recorder) return Promise.resolve(null);\n discardRef.current = false;\n return new Promise<Blob | null>((resolve) => {\n stopResolveRef.current = resolve;\n recorder.stop();\n });\n }, []);\n\n const cancel = useCallback(() => {\n const recorder = mediaRecorderRef.current;\n if (!recorder) {\n cleanupAudioGraph();\n setStatus('idle');\n setLevels(new Array(bars).fill(0));\n setDurationMs(0);\n resetAutoStop();\n return;\n }\n discardRef.current = true;\n recorder.stop();\n }, [bars, cleanupAudioGraph, resetAutoStop]);\n\n // Teardown on unmount.\n useEffect(() => {\n return () => {\n const recorder = mediaRecorderRef.current;\n if (recorder && recorder.state !== 'inactive') {\n discardRef.current = true;\n try {\n recorder.stop();\n } catch {\n // ignore\n }\n }\n cleanupAudioGraph();\n };\n }, [cleanupAudioGraph]);\n\n return {\n status,\n isRecording: status === 'recording',\n isPaused: status === 'paused',\n isSupported,\n levels,\n durationMs,\n error,\n isAutoStopping,\n autoStopProgress,\n start,\n pause,\n resume,\n stop,\n cancel,\n };\n}\n"],"names":[],"mappings":";;AAoEA,MAAM,oBAAoB,GAAG;IAC3B,wBAAwB;IACxB,YAAY;IACZ,uBAAuB;IACvB,WAAW;CACZ;AAED,SAAS,YAAY,CAAC,SAAkB,EAAA;IACtC,IAAI,OAAO,aAAa,KAAK,WAAW;AAAE,QAAA,OAAO,SAAS;IAC1D,MAAM,UAAU,GAAG;AACjB,UAAE,CAAC,SAAS,EAAE,GAAG,oBAAoB;UACnC,oBAAoB;AACxB,IAAA,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE;AAC7B,QAAA,IAAI,aAAa,CAAC,eAAe,CAAC,IAAI,CAAC;AAAE,YAAA,OAAO,IAAI;IACtD;AACA,IAAA,OAAO,SAAS;AAClB;AAEA;;;;;;AAMG;AACG,SAAU,kBAAkB,CAChC,OAAA,GAAqC,EAAE,EAAA;AAEvC,IAAA,MAAM,EACJ,IAAI,GAAG,CAAC,EACR,QAAQ,EACR,QAAQ,GAAG,IAAI,EACf,iBAAiB,GAAG,IAAI,EACxB,mBAAmB,GAAG,IAAI,EAC1B,oBAAoB,GAAG,IAAI,EAC3B,mBAAmB,GAAG,IAAI,EAC1B,UAAU,GACX,GAAG,OAAO;AAEX,IAAA,MAAM,WAAW,GACf,OAAO,SAAS,KAAK,WAAW;AAChC,QAAA,CAAC,CAAC,SAAS,CAAC,YAAY,EAAE,YAAY;QACtC,OAAO,aAAa,KAAK,WAAW;IAEtC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAwB,MAAM,CAAC;IACnE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,GAAG,QAAQ,CAAW,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC7E,MAAM,CAAC,UAAU,EAAE,aAAa,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;IAC/C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAgB,IAAI,CAAC;IACvD,MAAM,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC;IAC3D,MAAM,CAAC,gBAAgB,EAAE,mBAAmB,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC;AAE3D,IAAA,MAAM,gBAAgB,GAAG,MAAM,CAAuB,IAAI,CAAC;AAC3D,IAAA,MAAM,SAAS,GAAG,MAAM,CAAqB,IAAI,CAAC;AAClD,IAAA,MAAM,SAAS,GAAG,MAAM,CAAS,EAAE,CAAC;AACpC,IAAA,MAAM,eAAe,GAAG,MAAM,CAAsB,IAAI,CAAC;AACzD,IAAA,MAAM,WAAW,GAAG,MAAM,CAAsB,IAAI,CAAC;AACrD,IAAA,MAAM,MAAM,GAAG,MAAM,CAAgB,IAAI,CAAC;AAC1C,IAAA,MAAM,YAAY,GAAG,MAAM,CAAS,CAAC,CAAC;AACtC,IAAA,MAAM,cAAc,GAAG,MAAM,CAAS,CAAC,CAAC;AACxC,IAAA,MAAM,cAAc,GAAG,MAAM,CAAuC,IAAI,CAAC;AACzE,IAAA,MAAM,UAAU,GAAG,MAAM,CAAU,KAAK,CAAC;;;IAIzC,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;IACnC,MAAM,eAAe,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IACpD,MAAM,gBAAgB,GAAG,MAAM,CAAgB,IAAI,CAAC,CAAC;IACrD,MAAM,gBAAgB,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;;IAEvC,MAAM,cAAc,GAAG,MAAM,CAAC;QAC5B,QAAQ;QACR,iBAAiB;QACjB,mBAAmB;QACnB,oBAAoB;QACpB,mBAAmB;QACnB,UAAU;AACX,KAAA,CAAC;IACF,cAAc,CAAC,OAAO,GAAG;QACvB,QAAQ;QACR,iBAAiB;QACjB,mBAAmB;QACnB,oBAAoB;QACpB,mBAAmB;QACnB,UAAU;KACX;;;IAID,MAAM,aAAa,GAAG,WAAW,CAAC,CAAC,UAAU,GAAG,KAAK,KAAI;AACvD,QAAA,IAAI,CAAC,UAAU;AAAE,YAAA,YAAY,CAAC,OAAO,GAAG,KAAK;AAC7C,QAAA,eAAe,CAAC,OAAO,GAAG,IAAI;AAC9B,QAAA,gBAAgB,CAAC,OAAO,GAAG,IAAI;AAC/B,QAAA,gBAAgB,CAAC,OAAO,GAAG,KAAK;QAChC,iBAAiB,CAAC,KAAK,CAAC;QACxB,mBAAmB,CAAC,CAAC,CAAC;IACxB,CAAC,EAAE,EAAE,CAAC;;;AAIN,IAAA,MAAM,gBAAgB,GAAG,WAAW,CAAC,CAAC,IAAY,KAAI;AACpD,QAAA,MAAM,GAAG,GAAG,cAAc,CAAC,OAAO;AAClC,QAAA,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,gBAAgB,CAAC,OAAO;YAAE;AAC/C,QAAA,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;AAEtB,QAAA,IAAI,IAAI,IAAI,GAAG,CAAC,mBAAmB,EAAE;;AAEnC,YAAA,YAAY,CAAC,OAAO,GAAG,IAAI;AAC3B,YAAA,eAAe,CAAC,OAAO,GAAG,IAAI;AAC9B,YAAA,IAAI,gBAAgB,CAAC,OAAO,KAAK,IAAI,EAAE;AACrC,gBAAA,gBAAgB,CAAC,OAAO,GAAG,IAAI;gBAC/B,iBAAiB,CAAC,KAAK,CAAC;gBACxB,mBAAmB,CAAC,CAAC,CAAC;YACxB;YACA;QACF;;QAGA,IAAI,CAAC,YAAY,CAAC,OAAO;YAAE;AAE3B,QAAA,IAAI,IAAI,IAAI,GAAG,CAAC,oBAAoB,EAAE;AACpC,YAAA,IAAI,eAAe,CAAC,OAAO,KAAK,IAAI;AAAE,gBAAA,eAAe,CAAC,OAAO,GAAG,GAAG;AAEnE,YAAA,IAAI,gBAAgB,CAAC,OAAO,KAAK,IAAI,EAAE;;gBAErC,IAAI,GAAG,GAAG,eAAe,CAAC,OAAO,IAAI,GAAG,CAAC,iBAAiB,EAAE;AAC1D,oBAAA,gBAAgB,CAAC,OAAO,GAAG,GAAG;oBAC9B,iBAAiB,CAAC,IAAI,CAAC;oBACvB,mBAAmB,CAAC,CAAC,CAAC;gBACxB;YACF;iBAAO;;AAEL,gBAAA,MAAM,OAAO,GAAG,GAAG,GAAG,gBAAgB,CAAC,OAAO;AAC9C,gBAAA,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,OAAO,GAAG,GAAG,CAAC,mBAAmB,CAAC;gBACnE,mBAAmB,CAAC,QAAQ,CAAC;AAC7B,gBAAA,IAAI,OAAO,IAAI,GAAG,CAAC,mBAAmB,EAAE;AACtC,oBAAA,gBAAgB,CAAC,OAAO,GAAG,IAAI;AAC/B,oBAAA,gBAAgB,CAAC,OAAO,GAAG,IAAI;AAC/B,oBAAA,eAAe,CAAC,OAAO,GAAG,IAAI;oBAC9B,iBAAiB,CAAC,KAAK,CAAC;oBACxB,mBAAmB,CAAC,CAAC,CAAC;AACtB,oBAAA,GAAG,CAAC,UAAU,IAAI;gBACpB;YACF;QACF;;IAEF,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,MAAM,iBAAiB,GAAG,WAAW,CAAC,MAAK;AACzC,QAAA,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,EAAE;AAC3B,YAAA,oBAAoB,CAAC,MAAM,CAAC,OAAO,CAAC;AACpC,YAAA,MAAM,CAAC,OAAO,GAAG,IAAI;QACvB;AACA,QAAA,IAAI,eAAe,CAAC,OAAO,EAAE;AAC3B,YAAA,eAAe,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,MAAM,SAAS,CAAC;AACtD,YAAA,eAAe,CAAC,OAAO,GAAG,IAAI;QAChC;AACA,QAAA,WAAW,CAAC,OAAO,GAAG,IAAI;AAC1B,QAAA,IAAI,SAAS,CAAC,OAAO,EAAE;AACrB,YAAA,SAAS,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;AACtD,YAAA,SAAS,CAAC,OAAO,GAAG,IAAI;QAC1B;IACF,CAAC,EAAE,EAAE,CAAC;;AAGN,IAAA,MAAM,IAAI,GAAG,WAAW,CAAC,MAAK;AAC5B,QAAA,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO;QACpC,IAAI,QAAQ,EAAE;YACZ,MAAM,IAAI,GAAG,IAAI,UAAU,CAAC,QAAQ,CAAC,iBAAiB,CAAC;AACvD,YAAA,QAAQ,CAAC,oBAAoB,CAAC,IAAI,CAAC;AACnC,YAAA,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC;YACtD,MAAM,IAAI,GAAa,EAAE;YACzB,IAAI,IAAI,GAAG,CAAC;AACZ,YAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,EAAE;gBAC7B,IAAI,GAAG,GAAG,CAAC;AACX,gBAAA,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,EAAE,CAAC,EAAE,EAAE;oBACnC,GAAG,IAAI,IAAI,CAAC,CAAC,GAAG,UAAU,GAAG,CAAC,CAAC,IAAI,CAAC;gBACtC;;AAEA,gBAAA,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,UAAU,GAAG,GAAG,CAAC;AACjD,gBAAA,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;gBAChB,IAAI,KAAK,GAAG,IAAI;oBAAE,IAAI,GAAG,KAAK;YAChC;YACA,SAAS,CAAC,IAAI,CAAC;YACf,gBAAgB,CAAC,IAAI,CAAC;QACxB;AACA,QAAA,aAAa,CAAC,cAAc,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC,OAAO,CAAC,CAAC;AAC3E,QAAA,MAAM,CAAC,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC;AAC9C,IAAA,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,CAAC,CAAC;AAE5B,IAAA,MAAM,KAAK,GAAG,WAAW,CAAC,YAAW;QACnC,IAAI,CAAC,WAAW,EAAE;YAChB,QAAQ,CAAC,kDAAkD,CAAC;YAC5D;QACF;QACA,IAAI,gBAAgB,CAAC,OAAO;YAAE;QAE9B,QAAQ,CAAC,IAAI,CAAC;AACd,QAAA,IAAI;AACF,YAAA,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,YAAY,CAAC,YAAY,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AACzE,YAAA,SAAS,CAAC,OAAO,GAAG,MAAM;AAE1B,YAAA,MAAM,YAAY,GAAG,YAAY,CAAC,QAAQ,CAAC;YAC3C,MAAM,QAAQ,GAAG;kBACb,IAAI,aAAa,CAAC,MAAM,EAAE,EAAE,QAAQ,EAAE,YAAY,EAAE;AACtD,kBAAE,IAAI,aAAa,CAAC,MAAM,CAAC;AAC7B,YAAA,gBAAgB,CAAC,OAAO,GAAG,QAAQ;AACnC,YAAA,SAAS,CAAC,OAAO,GAAG,EAAE;AACtB,YAAA,UAAU,CAAC,OAAO,GAAG,KAAK;AAC1B,YAAA,aAAa,EAAE;AAEf,YAAA,QAAQ,CAAC,eAAe,GAAG,CAAC,CAAY,KAAI;gBAC1C,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC;oBAAE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;AAC/D,YAAA,CAAC;AACD,YAAA,QAAQ,CAAC,MAAM,GAAG,MAAK;gBACrB,MAAM,IAAI,GAAG,QAAQ,CAAC,QAAQ,IAAI,YAAY,IAAI,YAAY;AAC9D,gBAAA,MAAM,IAAI,GAAG,UAAU,CAAC;AACtB,sBAAE;AACF,sBAAE,IAAI,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC;AACzC,gBAAA,SAAS,CAAC,OAAO,GAAG,EAAE;AACtB,gBAAA,iBAAiB,EAAE;AACnB,gBAAA,gBAAgB,CAAC,OAAO,GAAG,IAAI;gBAC/B,SAAS,CAAC,MAAM,CAAC;AACjB,gBAAA,SAAS,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;gBAClC,aAAa,CAAC,CAAC,CAAC;AAChB,gBAAA,cAAc,CAAC,OAAO,GAAG,CAAC;AAC1B,gBAAA,aAAa,EAAE;AACf,gBAAA,MAAM,OAAO,GAAG,cAAc,CAAC,OAAO;AACtC,gBAAA,cAAc,CAAC,OAAO,GAAG,IAAI;AAC7B,gBAAA,OAAO,GAAG,IAAI,CAAC;AACjB,YAAA,CAAC;;AAGD,YAAA,MAAM,QAAQ,GACZ,MAAM,CAAC,YAAY;gBAClB;AACE,qBAAA,kBAAkB;AACvB,YAAA,MAAM,YAAY,GAAG,IAAI,QAAQ,EAAE;YACnC,MAAM,MAAM,GAAG,YAAY,CAAC,uBAAuB,CAAC,MAAM,CAAC;AAC3D,YAAA,MAAM,QAAQ,GAAG,YAAY,CAAC,cAAc,EAAE;AAC9C,YAAA,QAAQ,CAAC,OAAO,GAAG,EAAE;AACrB,YAAA,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC;AACxB,YAAA,eAAe,CAAC,OAAO,GAAG,YAAY;AACtC,YAAA,WAAW,CAAC,OAAO,GAAG,QAAQ;AAE9B,YAAA,cAAc,CAAC,OAAO,GAAG,CAAC;AAC1B,YAAA,YAAY,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE;AACjC,YAAA,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC;YACnB,SAAS,CAAC,WAAW,CAAC;AACtB,YAAA,MAAM,CAAC,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC;QAC9C;QAAE,OAAO,CAAC,EAAE;AACV,YAAA,iBAAiB,EAAE;AACnB,YAAA,gBAAgB,CAAC,OAAO,GAAG,IAAI;AAC/B,YAAA,MAAM,OAAO,GACV,CAAW,EAAE,IAAI,KAAK;AACrB,kBAAE;kBACA,8BAA+B,CAAW,EAAE,OAAO,IAAI,eAAe,EAAE;YAC9E,QAAQ,CAAC,OAAO,CAAC;YACjB,SAAS,CAAC,MAAM,CAAC;QACnB;AACF,IAAA,CAAC,EAAE,CAAC,WAAW,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,iBAAiB,EAAE,aAAa,CAAC,CAAC;AAEzE,IAAA,MAAM,KAAK,GAAG,WAAW,CAAC,MAAK;AAC7B,QAAA,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO;QACzC,IAAI,QAAQ,IAAI,QAAQ,CAAC,KAAK,KAAK,WAAW,EAAE;YAC9C,QAAQ,CAAC,KAAK,EAAE;YAChB,cAAc,CAAC,OAAO,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,YAAY,CAAC,OAAO;AAC3D,YAAA,IAAI,MAAM,CAAC,OAAO,KAAK,IAAI,EAAE;AAC3B,gBAAA,oBAAoB,CAAC,MAAM,CAAC,OAAO,CAAC;AACpC,gBAAA,MAAM,CAAC,OAAO,GAAG,IAAI;YACvB;AACA,YAAA,SAAS,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;;YAElC,aAAa,CAAC,IAAI,CAAC;YACnB,SAAS,CAAC,QAAQ,CAAC;QACrB;AACF,IAAA,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;AAEzB,IAAA,MAAM,MAAM,GAAG,WAAW,CAAC,MAAK;AAC9B,QAAA,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO;QACzC,IAAI,QAAQ,IAAI,QAAQ,CAAC,KAAK,KAAK,QAAQ,EAAE;YAC3C,QAAQ,CAAC,MAAM,EAAE;AACjB,YAAA,YAAY,CAAC,OAAO,GAAG,IAAI,CAAC,GAAG,EAAE;;YAEjC,aAAa,CAAC,IAAI,CAAC;AACnB,YAAA,MAAM,CAAC,OAAO,GAAG,qBAAqB,CAAC,IAAI,CAAC;YAC5C,SAAS,CAAC,WAAW,CAAC;QACxB;AACF,IAAA,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC;AAEzB,IAAA,MAAM,IAAI,GAAG,WAAW,CAAC,MAA2B;AAClD,QAAA,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO;AACzC,QAAA,IAAI,CAAC,QAAQ;AAAE,YAAA,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;AAC3C,QAAA,UAAU,CAAC,OAAO,GAAG,KAAK;AAC1B,QAAA,OAAO,IAAI,OAAO,CAAc,CAAC,OAAO,KAAI;AAC1C,YAAA,cAAc,CAAC,OAAO,GAAG,OAAO;YAChC,QAAQ,CAAC,IAAI,EAAE;AACjB,QAAA,CAAC,CAAC;IACJ,CAAC,EAAE,EAAE,CAAC;AAEN,IAAA,MAAM,MAAM,GAAG,WAAW,CAAC,MAAK;AAC9B,QAAA,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO;QACzC,IAAI,CAAC,QAAQ,EAAE;AACb,YAAA,iBAAiB,EAAE;YACnB,SAAS,CAAC,MAAM,CAAC;AACjB,YAAA,SAAS,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClC,aAAa,CAAC,CAAC,CAAC;AAChB,YAAA,aAAa,EAAE;YACf;QACF;AACA,QAAA,UAAU,CAAC,OAAO,GAAG,IAAI;QACzB,QAAQ,CAAC,IAAI,EAAE;IACjB,CAAC,EAAE,CAAC,IAAI,EAAE,iBAAiB,EAAE,aAAa,CAAC,CAAC;;IAG5C,SAAS,CAAC,MAAK;AACb,QAAA,OAAO,MAAK;AACV,YAAA,MAAM,QAAQ,GAAG,gBAAgB,CAAC,OAAO;YACzC,IAAI,QAAQ,IAAI,QAAQ,CAAC,KAAK,KAAK,UAAU,EAAE;AAC7C,gBAAA,UAAU,CAAC,OAAO,GAAG,IAAI;AACzB,gBAAA,IAAI;oBACF,QAAQ,CAAC,IAAI,EAAE;gBACjB;AAAE,gBAAA,MAAM;;gBAER;YACF;AACA,YAAA,iBAAiB,EAAE;AACrB,QAAA,CAAC;AACH,IAAA,CAAC,EAAE,CAAC,iBAAiB,CAAC,CAAC;IAEvB,OAAO;QACL,MAAM;QACN,WAAW,EAAE,MAAM,KAAK,WAAW;QACnC,QAAQ,EAAE,MAAM,KAAK,QAAQ;QAC7B,WAAW;QACX,MAAM;QACN,UAAU;QACV,KAAK;QACL,cAAc;QACd,gBAAgB;QAChB,KAAK;QACL,KAAK;QACL,MAAM;QACN,IAAI;QACJ,MAAM;KACP;AACH;;;;"}
|
|
1
|
+
{"version":3,"file":"useSpeechRecording.js","sources":["../../../src/hooks/useSpeechRecording.ts"],"sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\n/**\n * Recording lifecycle state.\n * - `idle`: not recording\n * - `recording`: actively capturing audio\n * - `paused`: capture paused, can be resumed\n */\nexport type SpeechRecordingStatus = 'idle' | 'recording' | 'paused';\n\nexport interface UseSpeechRecordingOptions {\n /** Number of equalizer bars to expose in `levels`. @default 5 */\n bars?: number;\n /** Preferred MediaRecorder mime type. Falls back to a supported one. */\n mimeType?: string;\n /**\n * Auto-stop the recording after a short silence, but only once speech has\n * actually been detected. When it triggers, the countdown runs and then\n * `onAutoStop` is fired. Disable to require a manual confirm. @default true\n */\n autoStop?: boolean;\n /**\n * Continuous silence (ms) that must elapse — after speech was detected —\n * before the auto-stop countdown begins. @default 1000\n */\n autoStopSilenceMs?: number;\n /** Duration (ms) of the auto-stop countdown shown before firing. @default 1000 */\n autoStopCountdownMs?: number;\n /**\n * Silence is **adaptive**: the threshold is this fraction of the loudest\n * speech observed in the recording (e.g. 0.1 = 10% of peak voice). This makes\n * it robust to ambient noise — a quiet room and a loud one calibrate\n * differently. @default 0.1\n */\n autoStopSilenceRatio?: number;\n /**\n * Absolute floor (0..1) for the adaptive silence threshold, so it never drops\n * so low that background hiss reads as \"sound\". @default 0.02\n */\n autoStopSilenceLevel?: number;\n /**\n * Absolute floor (0..1) a peak must clear to first count as speech (arms the\n * detector / calibrates the reference loudness). @default 0.12\n */\n autoStopSpeechLevel?: number;\n /**\n * Fired when the silence countdown completes. The consumer decides what\n * \"stop\" means (e.g. confirm + transcribe). Read fresh on every frame, so it\n * does not need to be memoized.\n */\n onAutoStop?: () => void;\n}\n\nexport interface UseSpeechRecordingResult {\n status: SpeechRecordingStatus;\n isRecording: boolean;\n isPaused: boolean;\n /** Whether the browser supports audio recording. */\n isSupported: boolean;\n /** Normalized amplitude per bar (0..1), updated in real time for the equalizer. */\n levels: number[];\n /** Elapsed recording time in milliseconds (excludes paused time). */\n durationMs: number;\n /** Last error message, if any (e.g. permission denied). */\n error: string | null;\n /** True while the silence-triggered auto-stop countdown is running. */\n isAutoStopping: boolean;\n /** Auto-stop countdown progress, 1 (full) → 0 (empty), for an inverted ring. */\n autoStopProgress: number;\n /** Request mic access and start recording. */\n start: () => Promise<void>;\n /** Pause an active recording. */\n pause: () => void;\n /** Resume a paused recording. */\n resume: () => void;\n /** Stop and return the recorded audio as a Blob (null if nothing captured). */\n stop: () => Promise<Blob | null>;\n /** Stop and discard the recording without producing a Blob. */\n cancel: () => void;\n}\n\nconst PREFERRED_MIME_TYPES = [\n 'audio/webm;codecs=opus',\n 'audio/webm',\n 'audio/ogg;codecs=opus',\n 'audio/mp4',\n];\n\nfunction pickMimeType(preferred?: string): string | undefined {\n if (typeof MediaRecorder === 'undefined') return undefined;\n const candidates = preferred\n ? [preferred, ...PREFERRED_MIME_TYPES]\n : PREFERRED_MIME_TYPES;\n for (const type of candidates) {\n if (MediaRecorder.isTypeSupported(type)) return type;\n }\n return undefined;\n}\n\n/**\n * Encapsulates microphone capture via MediaRecorder plus a Web Audio analyser\n * that exposes live amplitude levels for an equalizer-style animation.\n *\n * The hook owns all teardown: stopping tracks, closing the AudioContext and\n * cancelling the animation frame on stop/cancel/unmount.\n */\nexport function useSpeechRecording(\n options: UseSpeechRecordingOptions = {},\n): UseSpeechRecordingResult {\n const {\n bars = 5,\n mimeType,\n autoStop = true,\n autoStopSilenceMs = 1000,\n autoStopCountdownMs = 1000,\n autoStopSilenceRatio = 0.1,\n autoStopSilenceLevel = 0.02,\n autoStopSpeechLevel = 0.12,\n onAutoStop,\n } = options;\n\n const isSupported =\n typeof navigator !== 'undefined' &&\n !!navigator.mediaDevices?.getUserMedia &&\n typeof MediaRecorder !== 'undefined';\n\n const [status, setStatus] = useState<SpeechRecordingStatus>('idle');\n const [levels, setLevels] = useState<number[]>(() => new Array(bars).fill(0));\n const [durationMs, setDurationMs] = useState(0);\n const [error, setError] = useState<string | null>(null);\n const [isAutoStopping, setIsAutoStopping] = useState(false);\n const [autoStopProgress, setAutoStopProgress] = useState(1);\n\n const mediaRecorderRef = useRef<MediaRecorder | null>(null);\n const streamRef = useRef<MediaStream | null>(null);\n const chunksRef = useRef<Blob[]>([]);\n const audioContextRef = useRef<AudioContext | null>(null);\n const analyserRef = useRef<AnalyserNode | null>(null);\n const rafRef = useRef<number | null>(null);\n const startedAtRef = useRef<number>(0);\n const accumulatedRef = useRef<number>(0);\n const stopResolveRef = useRef<((blob: Blob | null) => void) | null>(null);\n const discardRef = useRef<boolean>(false);\n\n // --- Auto-stop (silence detection) state, all kept in refs so the rAF loop\n // reads it without forcing `tick` to re-subscribe. ---\n const hasSpeechRef = useRef(false); // user has spoken at least once\n const loudestRef = useRef(0); // loudest peak seen (reference for adaptive silence)\n const silenceStartRef = useRef<number | null>(null); // when current silence began\n const autoStopStartRef = useRef<number | null>(null); // when the countdown started\n const autoStopFiredRef = useRef(false); // guard against re-firing before stop\n // Latest-config ref so the loop always sees fresh thresholds and callback.\n const autoStopCfgRef = useRef({\n autoStop,\n autoStopSilenceMs,\n autoStopCountdownMs,\n autoStopSilenceRatio,\n autoStopSilenceLevel,\n autoStopSpeechLevel,\n onAutoStop,\n });\n autoStopCfgRef.current = {\n autoStop,\n autoStopSilenceMs,\n autoStopCountdownMs,\n autoStopSilenceRatio,\n autoStopSilenceLevel,\n autoStopSpeechLevel,\n onAutoStop,\n };\n\n // Reset all auto-stop tracking. `keepSpeech` preserves the \"user has spoken\"\n // flag across a pause/resume so it doesn't re-arm from scratch.\n const resetAutoStop = useCallback((keepSpeech = false) => {\n if (!keepSpeech) {\n hasSpeechRef.current = false;\n loudestRef.current = 0;\n }\n silenceStartRef.current = null;\n autoStopStartRef.current = null;\n autoStopFiredRef.current = false;\n setIsAutoStopping(false);\n setAutoStopProgress(1);\n }, []);\n\n // Evaluate the current peak amplitude against the silence/speech thresholds\n // and drive the auto-stop countdown. Called once per animation frame.\n const evaluateAutoStop = useCallback((peak: number) => {\n const cfg = autoStopCfgRef.current;\n if (!cfg.autoStop || autoStopFiredRef.current) return;\n const now = Date.now();\n\n // 1) Arm only once a real voice peak (absolute floor) has been heard. This\n // also seeds the reference loudness so ambient noise alone can't arm it.\n if (!hasSpeechRef.current) {\n if (peak >= cfg.autoStopSpeechLevel) {\n hasSpeechRef.current = true;\n loudestRef.current = peak;\n }\n return;\n }\n\n // 2) Track the loudest voice so the silence threshold scales with how loud\n // the user actually speaks (robust to ambient noise).\n if (peak > loudestRef.current) loudestRef.current = peak;\n\n // 3) Adaptive thresholds: silence = ratio of the loudest voice (with an\n // absolute floor); activity sits above it for hysteresis, so background\n // noise between the two doesn't keep cancelling the countdown.\n const silenceThreshold = Math.max(\n cfg.autoStopSilenceLevel,\n cfg.autoStopSilenceRatio * loudestRef.current,\n );\n const activityThreshold = silenceThreshold * 1.8;\n\n if (peak >= activityThreshold) {\n // Talking again: reset silence and cancel any pending countdown.\n silenceStartRef.current = null;\n if (autoStopStartRef.current !== null) {\n autoStopStartRef.current = null;\n setIsAutoStopping(false);\n setAutoStopProgress(1);\n }\n return;\n }\n\n if (peak <= silenceThreshold) {\n if (silenceStartRef.current === null) silenceStartRef.current = now;\n\n if (autoStopStartRef.current === null) {\n // Waiting out the silence window before the countdown begins.\n if (now - silenceStartRef.current >= cfg.autoStopSilenceMs) {\n autoStopStartRef.current = now;\n setIsAutoStopping(true);\n setAutoStopProgress(1);\n }\n } else {\n // Countdown running: drain the ring 1 → 0, then fire.\n const elapsed = now - autoStopStartRef.current;\n const progress = Math.max(0, 1 - elapsed / cfg.autoStopCountdownMs);\n setAutoStopProgress(progress);\n if (elapsed >= cfg.autoStopCountdownMs) {\n autoStopFiredRef.current = true;\n autoStopStartRef.current = null;\n silenceStartRef.current = null;\n setIsAutoStopping(false);\n setAutoStopProgress(1);\n cfg.onAutoStop?.();\n }\n }\n }\n // Hysteresis deadzone (silence < peak < activity): keep timers running.\n }, []);\n\n const cleanupAudioGraph = useCallback(() => {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n if (audioContextRef.current) {\n audioContextRef.current.close().catch(() => undefined);\n audioContextRef.current = null;\n }\n analyserRef.current = null;\n if (streamRef.current) {\n streamRef.current.getTracks().forEach((t) => t.stop());\n streamRef.current = null;\n }\n }, []);\n\n // Drives both the equalizer levels and the duration counter.\n const tick = useCallback(() => {\n const analyser = analyserRef.current;\n if (analyser) {\n const data = new Uint8Array(analyser.frequencyBinCount);\n analyser.getByteFrequencyData(data);\n const bucketSize = Math.floor(data.length / bars) || 1;\n const next: number[] = [];\n let peak = 0;\n for (let i = 0; i < bars; i++) {\n let sum = 0;\n for (let j = 0; j < bucketSize; j++) {\n sum += data[i * bucketSize + j] ?? 0;\n }\n // Normalize 0..255 -> 0..1 with a small floor so bars stay visible.\n const level = Math.min(1, sum / bucketSize / 255);\n next.push(level);\n if (level > peak) peak = level;\n }\n setLevels(next);\n evaluateAutoStop(peak);\n }\n setDurationMs(accumulatedRef.current + (Date.now() - startedAtRef.current));\n rafRef.current = requestAnimationFrame(tick);\n }, [bars, evaluateAutoStop]);\n\n const start = useCallback(async () => {\n if (!isSupported) {\n setError('Audio recording is not supported in this browser');\n return;\n }\n if (mediaRecorderRef.current) return;\n\n setError(null);\n try {\n const stream = await navigator.mediaDevices.getUserMedia({ audio: true });\n streamRef.current = stream;\n\n const selectedType = pickMimeType(mimeType);\n const recorder = selectedType\n ? new MediaRecorder(stream, { mimeType: selectedType })\n : new MediaRecorder(stream);\n mediaRecorderRef.current = recorder;\n chunksRef.current = [];\n discardRef.current = false;\n resetAutoStop();\n\n recorder.ondataavailable = (e: BlobEvent) => {\n if (e.data && e.data.size > 0) chunksRef.current.push(e.data);\n };\n recorder.onstop = () => {\n const type = recorder.mimeType || selectedType || 'audio/webm';\n const blob = discardRef.current\n ? null\n : new Blob(chunksRef.current, { type });\n chunksRef.current = [];\n cleanupAudioGraph();\n mediaRecorderRef.current = null;\n setStatus('idle');\n setLevels(new Array(bars).fill(0));\n setDurationMs(0);\n accumulatedRef.current = 0;\n resetAutoStop();\n const resolve = stopResolveRef.current;\n stopResolveRef.current = null;\n resolve?.(blob);\n };\n\n // Web Audio analyser for the equalizer animation.\n const AudioCtx =\n window.AudioContext ||\n (window as unknown as { webkitAudioContext: typeof AudioContext })\n .webkitAudioContext;\n const audioContext = new AudioCtx();\n const source = audioContext.createMediaStreamSource(stream);\n const analyser = audioContext.createAnalyser();\n analyser.fftSize = 64;\n source.connect(analyser);\n audioContextRef.current = audioContext;\n analyserRef.current = analyser;\n\n accumulatedRef.current = 0;\n startedAtRef.current = Date.now();\n recorder.start(100);\n setStatus('recording');\n rafRef.current = requestAnimationFrame(tick);\n } catch (e) {\n cleanupAudioGraph();\n mediaRecorderRef.current = null;\n const message =\n (e as Error)?.name === 'NotAllowedError'\n ? 'Microphone permission denied'\n : `Could not start recording: ${(e as Error)?.message || 'unknown error'}`;\n setError(message);\n setStatus('idle');\n }\n }, [isSupported, mimeType, bars, tick, cleanupAudioGraph, resetAutoStop]);\n\n const pause = useCallback(() => {\n const recorder = mediaRecorderRef.current;\n if (recorder && recorder.state === 'recording') {\n recorder.pause();\n accumulatedRef.current += Date.now() - startedAtRef.current;\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current);\n rafRef.current = null;\n }\n setLevels(new Array(bars).fill(0));\n // Drop any in-flight silence/countdown but remember that speech happened.\n resetAutoStop(true);\n setStatus('paused');\n }\n }, [bars, resetAutoStop]);\n\n const resume = useCallback(() => {\n const recorder = mediaRecorderRef.current;\n if (recorder && recorder.state === 'paused') {\n recorder.resume();\n startedAtRef.current = Date.now();\n // Fresh silence timing on resume so it doesn't fire immediately.\n resetAutoStop(true);\n rafRef.current = requestAnimationFrame(tick);\n setStatus('recording');\n }\n }, [tick, resetAutoStop]);\n\n const stop = useCallback((): Promise<Blob | null> => {\n const recorder = mediaRecorderRef.current;\n if (!recorder) return Promise.resolve(null);\n discardRef.current = false;\n return new Promise<Blob | null>((resolve) => {\n stopResolveRef.current = resolve;\n recorder.stop();\n });\n }, []);\n\n const cancel = useCallback(() => {\n const recorder = mediaRecorderRef.current;\n if (!recorder) {\n cleanupAudioGraph();\n setStatus('idle');\n setLevels(new Array(bars).fill(0));\n setDurationMs(0);\n resetAutoStop();\n return;\n }\n discardRef.current = true;\n recorder.stop();\n }, [bars, cleanupAudioGraph, resetAutoStop]);\n\n // Teardown on unmount.\n useEffect(() => {\n return () => {\n const recorder = mediaRecorderRef.current;\n if (recorder && recorder.state !== 'inactive') {\n discardRef.current = true;\n try {\n recorder.stop();\n } catch {\n // ignore\n }\n }\n cleanupAudioGraph();\n };\n }, [cleanupAudioGraph]);\n\n return {\n status,\n isRecording: status === 'recording',\n isPaused: status === 'paused',\n isSupported,\n levels,\n durationMs,\n error,\n isAutoStopping,\n autoStopProgress,\n start,\n pause,\n resume,\n stop,\n cancel,\n };\n}\n"],"names":[],"mappings":"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"}
|