'use client'; import { useEffect, useRef, useState } from 'react'; import { alpha, useThemeColor } from '@djangocfg/ui-core/styles/palette'; import { useAnalyser } from '../../hooks/useAnalyser'; import { useElementWidth } from '../../hooks/useResizeObserver'; import { usePlayerAudio, usePlayerControls, usePlayerLevels } from '../../context/selectors'; import { attachHover, attachSeek } from './waveformInteraction'; import { paintLive } from './waveformRenderer'; type Props = { height: number; barWidth: number; barGap: number; seekStartsPlayback?: boolean; }; export function LiveWaveform({ height, barWidth, barGap, seekStartsPlayback }: Props) { const audio = usePlayerAudio(); const controls = usePlayerControls(); const store = usePlayerLevels(); const [container, setContainer] = useState(null); const canvasRef = useRef(null); const fgHex = useThemeColor('primary'); const mutedHex = useThemeColor('muted-foreground'); const colorRef = useRef(fgHex); colorRef.current = fgHex; useElementWidth(container); useAnalyser(audio, store, true); // rAF paint loop reads from the imperative store. Zero React renders. useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; let raf = 0; const tick = () => { if (typeof document !== 'undefined' && document.hidden) { raf = requestAnimationFrame(tick); return; } paintLive(canvas, store.getCurrent(), { color: colorRef.current, barWidth, barGap, minBarHeight: 1, }); raf = requestAnimationFrame(tick); }; raf = requestAnimationFrame(tick); return () => cancelAnimationFrame(raf); }, [store, barWidth, barGap]); useEffect(() => { if (!container) return; const detachSeek = attachSeek(container, audio, { startsPlayback: seekStartsPlayback, onPlayRequest: () => void controls.play(), }); const detachHover = attachHover(container, audio); return () => { detachSeek(); detachHover(); }; }, [audio, container, controls, seekStartsPlayback]); return (