'use client'; import * as React from 'react'; import { cn } from '@/lib/utils/cn'; import { generateMinMaxPeaks } from '@/lib/waveform/peaks'; export interface WaveformProps { audioBuffer: AudioBuffer | null; currentTime: number; duration: number; onSeek?: (time: number) => void; className?: string; height?: number; } export function Waveform({ audioBuffer, currentTime, duration, onSeek, className, height = 128, }: WaveformProps) { const canvasRef = React.useRef(null); const containerRef = React.useRef(null); const [width, setWidth] = React.useState(800); // Handle resize React.useEffect(() => { const handleResize = () => { if (containerRef.current) { setWidth(containerRef.current.clientWidth); } }; handleResize(); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, []); // Draw waveform React.useEffect(() => { const canvas = canvasRef.current; if (!canvas || !audioBuffer) return; const ctx = canvas.getContext('2d'); if (!ctx) return; // Set canvas size const dpr = window.devicePixelRatio || 1; canvas.width = width * dpr; canvas.height = height * dpr; canvas.style.width = `${width}px`; canvas.style.height = `${height}px`; ctx.scale(dpr, dpr); // Clear canvas ctx.fillStyle = getComputedStyle(canvas).getPropertyValue('--color-waveform-bg') || '#f5f5f5'; ctx.fillRect(0, 0, width, height); // Generate peaks const { min, max } = generateMinMaxPeaks(audioBuffer, width, 0); // Draw waveform const middle = height / 2; const scale = height / 2; // Waveform color const waveformColor = getComputedStyle(canvas).getPropertyValue('--color-waveform') || '#3b82f6'; const progressColor = getComputedStyle(canvas).getPropertyValue('--color-waveform-progress') || '#10b981'; // Calculate progress position const progressX = duration > 0 ? (currentTime / duration) * width : 0; // Draw waveform for (let i = 0; i < width; i++) { const minVal = min[i] * scale; const maxVal = max[i] * scale; // Use different color for played portion ctx.fillStyle = i < progressX ? progressColor : waveformColor; ctx.fillRect( i, middle + minVal, 1, Math.max(1, maxVal - minVal) ); } // Draw center line ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(0, middle); ctx.lineTo(width, middle); ctx.stroke(); // Draw playhead if (progressX > 0) { ctx.strokeStyle = '#ef4444'; ctx.lineWidth = 2; ctx.beginPath(); ctx.moveTo(progressX, 0); ctx.lineTo(progressX, height); ctx.stroke(); } }, [audioBuffer, width, height, currentTime, duration]); const handleClick = (e: React.MouseEvent) => { if (!onSeek || !duration) return; const canvas = canvasRef.current; if (!canvas) return; const rect = canvas.getBoundingClientRect(); const x = e.clientX - rect.left; const clickedTime = (x / width) * duration; onSeek(clickedTime); }; return (
{audioBuffer ? ( ) : (

Load an audio file to see waveform

)}
); }