import React, { useRef, useEffect, useState } from 'react'; /** * WaveformVisualizerProps - Propriétés du composant WaveformVisualizer * * @interface WaveformVisualizerProps */ interface WaveformVisualizerProps { /** * URL de l'audio source (optionnel, pour générer la waveform) */ audioUrl?: string; /** * Données de waveform pré-calculées (valeurs entre 0.0 et 1.0) * Si non fourni, génère des données mock */ waveformData?: number[]; /** * Progression de la lecture (0 à 100) */ progress: number; /** * Fonction appelée lors du clic sur la waveform pour naviguer * * @param {number} percentage - Pourcentage cliqué (0 à 100) */ onSeek: (percentage: number) => void; /** * Hauteur de la waveform en pixels * * @default 64 */ height?: number; /** * Couleur des barres non jouées * * @default '#374054' (kodo-steel) */ color?: string; /** * Couleur des barres jouées * * @default '#00FFF7' (kodo-cyan) */ playedColor?: string; } /** * WaveformVisualizer - Composant de visualisation de waveform audio * * Composant pour afficher une waveform audio interactive avec : * - Visualisation des données audio * - Indicateur de progression * - Navigation par clic * - Support pour données pré-calculées ou génération automatique * * @example * ```tsx * // Waveform avec données pré-calculées * seekTo(percentage)} * /> * ``` * * @example * ```tsx * // Waveform avec génération automatique * * ``` * * @component * @param {WaveformVisualizerProps} props - Propriétés du composant * @returns {JSX.Element} Canvas avec waveform interactive */ export const WaveformVisualizer: React.FC = ({ waveformData, progress, onSeek, height = 64, color = '#374054', // kodo-steel playedColor = '#00FFF7' // kodo-cyan }) => { const canvasRef = useRef(null); const [data, setData] = useState([]); // Initialize or generate mock data if none provided useEffect(() => { if (waveformData && waveformData.length > 0) { setData(waveformData); } else { // Generate mock waveform const mockData = Array.from({ length: 100 }, () => Math.random() * 0.8 + 0.2); setData(mockData); } }, [waveformData]); // Draw loop useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext('2d'); if (!ctx) return; const dpr = window.devicePixelRatio || 1; const width = canvas.offsetWidth; const drawHeight = height; canvas.width = width * dpr; canvas.height = drawHeight * dpr; ctx.scale(dpr, dpr); ctx.clearRect(0, 0, width, drawHeight); const barWidth = width / data.length; const gap = 1; const effectiveBarWidth = Math.max(1, barWidth - gap); data.forEach((val, i) => { const x = i * barWidth; const barHeight = val * drawHeight; const y = (drawHeight - barHeight) / 2; // Determine color based on progress const isPlayed = (i / data.length) * 100 <= progress; ctx.fillStyle = isPlayed ? playedColor : color; // Draw rounded rect equivalent ctx.fillRect(x, y, effectiveBarWidth, barHeight); }); }, [data, progress, height, color, playedColor]); const handleClick = (e: React.MouseEvent) => { const rect = e.currentTarget.getBoundingClientRect(); const x = e.clientX - rect.left; const percentage = (x / rect.width) * 100; onSeek(Math.min(100, Math.max(0, percentage))); }; return ( ); };