diff --git a/components/WaveformEditor.tsx b/components/WaveformEditor.tsx index 62662fe..2a9b9e2 100644 --- a/components/WaveformEditor.tsx +++ b/components/WaveformEditor.tsx @@ -12,6 +12,7 @@ interface WaveformEditorProps { export default function WaveformEditor({ audioUrl, startTime, duration, unlockSteps, onStartTimeChange }: WaveformEditorProps) { const canvasRef = useRef(null); + const timelineRef = useRef(null); const [audioBuffer, setAudioBuffer] = useState(null); const [audioDuration, setAudioDuration] = useState(0); const [isPlaying, setIsPlaying] = useState(false); @@ -58,6 +59,80 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt }; }, [audioUrl]); + // Draw timeline + useEffect(() => { + if (!audioDuration || !timelineRef.current) return; + + const timeline = timelineRef.current; + const ctx = timeline.getContext('2d'); + if (!ctx) return; + + const width = timeline.width; + const height = timeline.height; + + // Calculate visible range based on zoom and offset (same as waveform) + const visibleDuration = audioDuration / zoom; + const visibleStart = Math.max(0, Math.min(viewOffset, audioDuration - visibleDuration)); + const visibleEnd = Math.min(audioDuration, visibleStart + visibleDuration); + + // Clear timeline + ctx.fillStyle = '#ffffff'; + ctx.fillRect(0, 0, width, height); + + // Draw border + ctx.strokeStyle = '#e5e7eb'; + ctx.lineWidth = 1; + ctx.strokeRect(0, 0, width, height); + + // Calculate appropriate time interval based on visible duration + let timeInterval = 1; // Start with 1 second + if (visibleDuration > 60) timeInterval = 10; + else if (visibleDuration > 30) timeInterval = 5; + else if (visibleDuration > 10) timeInterval = 2; + else if (visibleDuration > 5) timeInterval = 1; + else if (visibleDuration > 1) timeInterval = 0.5; + else timeInterval = 0.1; + + // Draw time markers + ctx.strokeStyle = '#9ca3af'; + ctx.lineWidth = 1; + ctx.fillStyle = '#374151'; + ctx.font = '10px sans-serif'; + ctx.textAlign = 'center'; + ctx.textBaseline = 'top'; + + const startTimeMarker = Math.floor(visibleStart / timeInterval) * timeInterval; + for (let time = startTimeMarker; time <= visibleEnd; time += timeInterval) { + const timePx = ((time - visibleStart) / visibleDuration) * width; + + if (timePx >= 0 && timePx <= width) { + // Draw tick mark + ctx.beginPath(); + ctx.moveTo(timePx, 0); + ctx.lineTo(timePx, height); + ctx.stroke(); + + // Draw time label + const timeLabel = time.toFixed(timeInterval < 1 ? 1 : 0); + ctx.fillText(`${timeLabel}s`, timePx, 2); + } + } + + // Draw current playback position if playing + if (playbackPosition !== null) { + const playbackPx = ((playbackPosition - visibleStart) / visibleDuration) * width; + if (playbackPx >= 0 && playbackPx <= width) { + ctx.strokeStyle = '#10b981'; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(playbackPx, 0); + ctx.lineTo(playbackPx, height); + ctx.stroke(); + } + } + + }, [audioDuration, zoom, viewOffset, playbackPosition]); + useEffect(() => { if (!audioBuffer || !canvasRef.current) return; @@ -491,21 +566,38 @@ export default function WaveformEditor({ audioUrl, startTime, duration, unlockSt )} - +
+ + +
{/* Playback Controls */}