Skip to content

Commit 057aff5

Browse files
committed
feat: add error handling fallback for audio visualization
1 parent a549122 commit 057aff5

File tree

1 file changed

+47
-19
lines changed

1 file changed

+47
-19
lines changed

src/browser/components/ChatInput/RecordingOverlay.tsx

Lines changed: 47 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -102,11 +102,13 @@ interface SlidingWaveformProps {
102102
/**
103103
* Renders a sliding window of audio amplitude over time.
104104
* New samples appear on the right and scroll left as time passes.
105+
* Falls back to a simple pulsing indicator if Web Audio API fails.
105106
*/
106107
const SlidingWaveform: React.FC<SlidingWaveformProps> = (props) => {
107108
const canvasRef = useRef<HTMLCanvasElement>(null);
108109
const containerRef = useRef<HTMLDivElement>(null);
109110
const [containerWidth, setContainerWidth] = useState(600);
111+
const [audioError, setAudioError] = useState(false);
110112

111113
// Audio analysis state (refs to avoid re-renders)
112114
const audioContextRef = useRef<AudioContext | null>(null);
@@ -136,24 +138,29 @@ const SlidingWaveform: React.FC<SlidingWaveformProps> = (props) => {
136138
const stream = props.mediaRecorder.stream;
137139
if (!stream) return;
138140

139-
const audioContext = new AudioContext();
140-
const analyser = audioContext.createAnalyser();
141-
analyser.fftSize = 256;
142-
analyser.smoothingTimeConstant = 0.3;
143-
144-
const source = audioContext.createMediaStreamSource(stream);
145-
source.connect(analyser);
146-
147-
audioContextRef.current = audioContext;
148-
analyserRef.current = analyser;
149-
samplesRef.current = new Array<number>(NUM_SAMPLES).fill(0);
150-
lastSampleTimeRef.current = performance.now();
151-
152-
return () => {
153-
void audioContext.close();
154-
audioContextRef.current = null;
155-
analyserRef.current = null;
156-
};
141+
try {
142+
const audioContext = new AudioContext();
143+
const analyser = audioContext.createAnalyser();
144+
analyser.fftSize = 256;
145+
analyser.smoothingTimeConstant = 0.3;
146+
147+
const source = audioContext.createMediaStreamSource(stream);
148+
source.connect(analyser);
149+
150+
audioContextRef.current = audioContext;
151+
analyserRef.current = analyser;
152+
samplesRef.current = new Array<number>(NUM_SAMPLES).fill(0);
153+
lastSampleTimeRef.current = performance.now();
154+
155+
return () => {
156+
void audioContext.close();
157+
audioContextRef.current = null;
158+
analyserRef.current = null;
159+
};
160+
} catch (err) {
161+
console.error("Failed to initialize audio visualization:", err);
162+
setAudioError(true);
163+
}
157164
}, [props.mediaRecorder]);
158165

159166
// Animation loop: sample audio amplitude and render bars
@@ -217,9 +224,30 @@ const SlidingWaveform: React.FC<SlidingWaveformProps> = (props) => {
217224

218225
// Run animation loop
219226
useEffect(() => {
227+
if (audioError) return;
220228
animationFrameRef.current = requestAnimationFrame(draw);
221229
return () => cancelAnimationFrame(animationFrameRef.current);
222-
}, [draw]);
230+
}, [draw, audioError]);
231+
232+
// Fallback: simple pulsing indicator if Web Audio API unavailable
233+
if (audioError) {
234+
return (
235+
<div className="flex h-full w-full items-center justify-center gap-1">
236+
{[...Array(5)].map((_, i) => (
237+
<div
238+
key={i}
239+
className="animate-pulse rounded-full"
240+
style={{
241+
width: 4,
242+
height: 12 + (i % 3) * 4,
243+
backgroundColor: props.color,
244+
animationDelay: `${i * 100}ms`,
245+
}}
246+
/>
247+
))}
248+
</div>
249+
);
250+
}
223251

224252
return (
225253
<div ref={containerRef} className="flex h-full w-full items-center justify-center">

0 commit comments

Comments
 (0)