Browse Source

fix: use audio element duration directly for waveform progress rendering

Both GenerateAudioNode and AudioInputNode relied on nodeData.duration
for the waveform progress calculation, but this was often null (never
set by the audio executor). Using audioRef.current.duration directly
avoids the store roundtrip and works as soon as audio metadata loads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
handoff-20260429-1057
shrimbly 5 months ago
parent
commit
1284f85615
  1. 16
      src/components/nodes/AudioInputNode.tsx
  2. 10
      src/components/nodes/GenerateAudioNode.tsx

16
src/components/nodes/AudioInputNode.tsx

@ -52,7 +52,6 @@ export function AudioInputNode({ id, data, selected }: NodeProps<AudioInputNodeT
const handleTimeUpdate = () => { const handleTimeUpdate = () => {
setCurrentTime(audio.currentTime); setCurrentTime(audio.currentTime);
}; };
audio.addEventListener("ended", handleEnded); audio.addEventListener("ended", handleEnded);
audio.addEventListener("timeupdate", handleTimeUpdate); audio.addEventListener("timeupdate", handleTimeUpdate);
@ -146,9 +145,10 @@ export function AudioInputNode({ id, data, selected }: NodeProps<AudioInputNodeT
const height = canvas.height; const height = canvas.height;
if (width === 0 || height === 0) return; if (width === 0 || height === 0) return;
const progress = nodeData.duration && currentTime > 0 ? currentTime / nodeData.duration : undefined; const duration = audioRef.current?.duration;
const progress = duration && isFinite(duration) && currentTime > 0 ? currentTime / duration : undefined;
drawWaveform(ctx, width, height, waveformData.peaks, progress); drawWaveform(ctx, width, height, waveformData.peaks, progress);
}, [isPlaying, currentTime, nodeData.duration, waveformData, drawWaveform]); }, [isPlaying, currentTime, waveformData, drawWaveform]);
// Animation loop for smooth playback position updates // Animation loop for smooth playback position updates
useEffect(() => { useEffect(() => {
@ -256,16 +256,16 @@ export function AudioInputNode({ id, data, selected }: NodeProps<AudioInputNodeT
}, [isPlaying]); }, [isPlaying]);
const handleSeek = useCallback((e: React.MouseEvent<HTMLDivElement>) => { const handleSeek = useCallback((e: React.MouseEvent<HTMLDivElement>) => {
if (!audioRef.current || !nodeData.duration || !waveformContainerRef.current) return; if (!audioRef.current || !audioRef.current.duration || !isFinite(audioRef.current.duration) || !waveformContainerRef.current) return;
const rect = waveformContainerRef.current.getBoundingClientRect(); const rect = waveformContainerRef.current.getBoundingClientRect();
const x = e.clientX - rect.left; const x = e.clientX - rect.left;
const progress = x / rect.width; const progress = x / rect.width;
const newTime = progress * nodeData.duration; const newTime = progress * audioRef.current.duration;
audioRef.current.currentTime = newTime; audioRef.current.currentTime = newTime;
setCurrentTime(newTime); setCurrentTime(newTime);
}, [nodeData.duration]); }, []);
const formatTime = (seconds: number) => { const formatTime = (seconds: number) => {
const mins = Math.floor(seconds / 60); const mins = Math.floor(seconds / 60);
@ -347,10 +347,10 @@ export function AudioInputNode({ id, data, selected }: NodeProps<AudioInputNodeT
{/* Progress bar / scrubber */} {/* Progress bar / scrubber */}
<div className="flex-1 h-1 bg-neutral-700 rounded-full overflow-hidden relative"> <div className="flex-1 h-1 bg-neutral-700 rounded-full overflow-hidden relative">
{nodeData.duration && ( {audioRef.current?.duration && isFinite(audioRef.current.duration) && (
<div <div
className="h-full bg-violet-500 transition-all" className="h-full bg-violet-500 transition-all"
style={{ width: `${(currentTime / nodeData.duration) * 100}%` }} style={{ width: `${(currentTime / audioRef.current.duration) * 100}%` }}
/> />
)} )}
</div> </div>

10
src/components/nodes/GenerateAudioNode.tsx

@ -73,7 +73,6 @@ export function GenerateAudioNode({ id, data, selected }: NodeProps<GenerateAudi
const handleTimeUpdate = () => { const handleTimeUpdate = () => {
setCurrentTime(audio.currentTime); setCurrentTime(audio.currentTime);
}; };
audio.addEventListener("ended", handleEnded); audio.addEventListener("ended", handleEnded);
audio.addEventListener("timeupdate", handleTimeUpdate); audio.addEventListener("timeupdate", handleTimeUpdate);
@ -159,9 +158,10 @@ export function GenerateAudioNode({ id, data, selected }: NodeProps<GenerateAudi
const height = canvas.height; const height = canvas.height;
if (width === 0 || height === 0) return; if (width === 0 || height === 0) return;
const progress = nodeData.duration && currentTime > 0 ? currentTime / nodeData.duration : undefined; const duration = audioRef.current?.duration;
const progress = duration && isFinite(duration) && currentTime > 0 ? currentTime / duration : undefined;
drawWaveform(ctx, width, height, waveformData.peaks, progress); drawWaveform(ctx, width, height, waveformData.peaks, progress);
}, [isPlaying, currentTime, nodeData.duration, waveformData, drawWaveform]); }, [isPlaying, currentTime, waveformData, drawWaveform]);
// Animation loop for smooth playback position updates // Animation loop for smooth playback position updates
useEffect(() => { useEffect(() => {
@ -447,10 +447,10 @@ export function GenerateAudioNode({ id, data, selected }: NodeProps<GenerateAudi
{/* Progress bar */} {/* Progress bar */}
<div className="flex-1 h-1 bg-neutral-700 rounded-full overflow-hidden relative"> <div className="flex-1 h-1 bg-neutral-700 rounded-full overflow-hidden relative">
{nodeData.duration && ( {audioRef.current?.duration && isFinite(audioRef.current.duration) && (
<div <div
className="h-full bg-violet-500 transition-all" className="h-full bg-violet-500 transition-all"
style={{ width: `${(currentTime / nodeData.duration) * 100}%` }} style={{ width: `${(currentTime / audioRef.current.duration) * 100}%` }}
/> />
)} )}
</div> </div>

Loading…
Cancel
Save