import React, { useState, useRef, useMemo, useCallback, useEffect } from 'react' // All 24 key+mode combinations const ALL_KEYS_WITH_MODES = [ 'C major', 'C minor', 'C# major', 'C# minor', 'D major', 'D minor', 'D# major', 'D# minor', 'E major', 'E minor', 'F major', 'F minor', 'F# major', 'F# minor', 'G major', 'G minor', 'G# major', 'G# minor', 'A major', 'A minor', 'A# major', 'A# minor', 'B major', 'B minor' ] const KEY_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] function formatTime(seconds) { if (!seconds || !isFinite(seconds)) return '0:00' const mins = Math.floor(seconds / 60) const secs = Math.floor(seconds % 60) return `${mins}:${secs.toString().padStart(2, '0')}` } function ContinuationPlayer({ sessionId }) { const audioRef = useRef(null) const rafRef = useRef(null) const [playing, setPlaying] = useState(false) const [cTime, setCTime] = useState(0) const [dur, setDur] = useState(0) // Stable URL — only changes when sessionId changes, not on every render const src = useMemo( () => `/api/stem/${sessionId}/_continuation?processed=false&t=${Date.now()}`, [sessionId] ) // Try to read duration from the audio element (WAV streams may delay reporting it) const tryReadDuration = useCallback(() => { const audio = audioRef.current if (!audio) return const d = audio.duration if (d && isFinite(d) && d > 0) { setDur(d) } }, []) // Smooth animation loop — reads currentTime every frame while playing useEffect(() => { if (!playing) { if (rafRef.current) cancelAnimationFrame(rafRef.current) return } const tick = () => { if (audioRef.current) { setCTime(audioRef.current.currentTime) tryReadDuration() } rafRef.current = requestAnimationFrame(tick) } rafRef.current = requestAnimationFrame(tick) return () => { if (rafRef.current) cancelAnimationFrame(rafRef.current) } }, [playing, tryReadDuration]) const toggle = () => { if (!audioRef.current) return if (playing) { audioRef.current.pause() } else { audioRef.current.play() } } const handleSeek = (e) => { const d = dur || audioRef.current?.duration if (!audioRef.current || !d || !isFinite(d)) return const rect = e.currentTarget.getBoundingClientRect() const pct = Math.max(0, Math.min(1, (e.clientX - rect.left) / rect.width)) audioRef.current.currentTime = pct * d setCTime(audioRef.current.currentTime) } return (