feat(treatment): add voice detail entry #65
@@ -219,6 +219,14 @@ export function useVoiceCapture({
|
|||||||
// minutes of dictation because a timer expired would be the worst failure.
|
// minutes of dictation because a timer expired would be the worst failure.
|
||||||
if (maxMs != null && elapsed >= maxMs) stop();
|
if (maxMs != null && elapsed >= maxMs) stop();
|
||||||
}, LEVEL_POLL_MS);
|
}, LEVEL_POLL_MS);
|
||||||
|
} catch {
|
||||||
|
// `new MediaRecorder(...)` and `recorder.start()` both throw on some browsers,
|
||||||
|
// and by then the stream is already live. Without this the promise rejects
|
||||||
|
// unhandled, the UI sits at 'idle' with nothing shown, and the browser's
|
||||||
|
// recording indicator stays lit until the workspace unmounts.
|
||||||
|
teardown();
|
||||||
|
setPhase('idle');
|
||||||
|
onError(clientError('VOICE_MIC_DENIED'));
|
||||||
} finally {
|
} finally {
|
||||||
startingRef.current = false;
|
startingRef.current = false;
|
||||||
}
|
}
|
||||||
@@ -262,12 +270,23 @@ function attachLevelMeter(
|
|||||||
source.connect(analyser);
|
source.connect(analyser);
|
||||||
|
|
||||||
const data = new Uint8Array(analyser.frequencyBinCount);
|
const data = new Uint8Array(analyser.frequencyBinCount);
|
||||||
const tick = () => {
|
// Sample every frame so a transient is not missed, but publish at LEVEL_POLL_MS.
|
||||||
|
// This hook lives in TreatmentWorkspace, so an unthrottled setLevel re-renders the
|
||||||
|
// details editor, the FDI chart and the lab panel on every animation frame — about
|
||||||
|
// 7,200 whole-tree renders across a two-minute recording.
|
||||||
|
let peakSinceEmit = 0;
|
||||||
|
let lastEmit = 0;
|
||||||
|
const tick = (now: number) => {
|
||||||
if (contextRef.current !== context || context.state === 'closed') return;
|
if (contextRef.current !== context || context.state === 'closed') return;
|
||||||
analyser.getByteTimeDomainData(data);
|
analyser.getByteTimeDomainData(data);
|
||||||
let peak = 0;
|
let peak = 0;
|
||||||
for (const sample of data) peak = Math.max(peak, Math.abs(sample - 128));
|
for (const sample of data) peak = Math.max(peak, Math.abs(sample - 128));
|
||||||
setLevel(Math.min(1, peak / 128));
|
peakSinceEmit = Math.max(peakSinceEmit, peak);
|
||||||
|
if (now - lastEmit >= LEVEL_POLL_MS) {
|
||||||
|
lastEmit = now;
|
||||||
|
setLevel(Math.min(1, peakSinceEmit / 128));
|
||||||
|
peakSinceEmit = 0;
|
||||||
|
}
|
||||||
requestAnimationFrame(tick);
|
requestAnimationFrame(tick);
|
||||||
};
|
};
|
||||||
requestAnimationFrame(tick);
|
requestAnimationFrame(tick);
|
||||||
|
|||||||
Reference in New Issue
Block a user