JavaScript 56.7%
TypeScript 42.6%
1"use client";23import { useEffect, useRef, useState, useCallback } from "react";4import { Play, Pause, Volume2, VolumeX, Gauge } from "lucide-react";5import type { PreviewPluginProps } from "./types";67export default function AudioPreview({ file }: PreviewPluginProps) {8 const containerRef = useRef<HTMLDivElement>(null);9 // eslint-disable-next-line @typescript-eslint/no-explicit-any10 const wavesurferRef = useRef<any>(null);11 const [isPlaying, setIsPlaying] = useState(false);12 const [currentTime, setCurrentTime] = useState(0);13 const [duration, setDuration] = useState(0);14 const [volume, setVolume] = useState(0.8);15 const [isMuted, setIsMuted] = useState(false);16 const [playbackRate, setPlaybackRate] = useState(1);17 const [loading, setLoading] = useState(true);18 const [error, setError] = useState(false);1920 useEffect(() => {21 // eslint-disable-next-line @typescript-eslint/no-explicit-any22 let ws: any = null;2324 const initWaveSurfer = async () => {25 try {26 const WaveSurfer = (await import("wavesurfer.js")).default;2728 if (!containerRef.current) return;2930 ws = WaveSurfer.create({31 container: containerRef.current,32 waveColor: "#4b5563",33 progressColor: "#9ca3af",34 cursorColor: "#ffffff",35 barWidth: 2,36 barGap: 1,37 barRadius: 2,38 height: 120,39 normalize: true,40 url: `/api/files/${file.id}/stream`,41 });4243 ws.on("ready", () => {44 setDuration(ws.getDuration());45 setLoading(false);46 ws.setVolume(volume);47 });4849 ws.on("audioprocess", () => {50 setCurrentTime(ws.getCurrentTime());51 });5253 ws.on("seeking", () => {54 setCurrentTime(ws.getCurrentTime());55 });5657 ws.on("finish", () => {58 setIsPlaying(false);59 });6061 ws.on("error", () => {62 setError(true);63 setLoading(false);64 });6566 wavesurferRef.current = ws;67 } catch {68 setError(true);69 setLoading(false);70 }71 };7273 initWaveSurfer();7475 return () => {76 if (ws) ws.destroy();77 };78 }, [file.id]);7980 const togglePlay = useCallback(() => {81 const ws = wavesurferRef.current;82 if (!ws) return;83 ws.playPause();84 setIsPlaying(!isPlaying);85 }, [isPlaying]);8687 const toggleMute = useCallback(() => {88 const ws = wavesurferRef.current;89 if (!ws) return;90 const newMuted = !isMuted;91 ws.setVolume(newMuted ? 0 : volume);92 setIsMuted(newMuted);93 }, [isMuted, volume]);9495 const changeVolume = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {96 const ws = wavesurferRef.current;97 if (!ws) return;98 const vol = parseFloat(e.target.value);99 ws.setVolume(vol);100 setVolume(vol);101 setIsMuted(vol === 0);102 }, []);103104 const cycleSpeed = useCallback(() => {105 const ws = wavesurferRef.current;106 if (!ws) return;107 const speeds = [0.5, 1, 1.25, 1.5, 2];108 const idx = speeds.indexOf(playbackRate);109 const next = speeds[(idx + 1) % speeds.length];110 setPlaybackRate(next);111 ws.setPlaybackRate(next);112 }, [playbackRate]);113114 const formatTime = (t: number) => {115 const m = Math.floor(t / 60);116 const s = Math.floor(t % 60);117 return `${m}:${s.toString().padStart(2, "0")}`;118 };119120 if (error) {121 return (122 <div className="flex flex-col items-center justify-center h-full p-8">123 <p className="text-gray-400 mb-4">Lecteur audio</p>124 <audio src={`/api/files/${file.id}/stream`} controls className="w-full max-w-md" />125 </div>126 );127 }128129 return (130 <div className="flex flex-col items-center justify-center h-full p-8 gap-6">131 {/* File info */}132 <div className="text-center">133 <div className="w-20 h-20 mx-auto mb-4 rounded-full bg-white/5 flex items-center justify-center">134 <Volume2 className="w-10 h-10 text-gray-400" />135 </div>136 <p className="text-sm text-gray-400">{file.name}</p>137 </div>138139 {/* Waveform */}140 <div className="w-full max-w-lg">141 {loading && (142 <div className="flex items-center justify-center h-[120px]">143 <div className="w-6 h-6 border-2 border-white/20 border-t-white rounded-full animate-spin" />144 </div>145 )}146 <div ref={containerRef} className={loading ? "hidden" : ""} />147 </div>148149 {/* Controls */}150 <div className="flex items-center gap-4">151 <button onClick={togglePlay} className="p-3 rounded-full bg-white/10 hover:bg-white/20 text-white transition-colors">152 {isPlaying ? <Pause className="w-5 h-5" /> : <Play className="w-5 h-5 ml-0.5" />}153 </button>154155 <span className="text-xs text-gray-500 min-w-[5rem] text-center">156 {formatTime(currentTime)} / {formatTime(duration)}157 </span>158159 <button onClick={cycleSpeed} className="text-xs text-gray-400 hover:text-white flex items-center gap-1">160 <Gauge className="w-3 h-3" />161 {playbackRate}x162 </button>163164 <div className="flex items-center gap-1">165 <button onClick={toggleMute} className="text-gray-400 hover:text-white">166 {isMuted || volume === 0 ? <VolumeX className="w-4 h-4" /> : <Volume2 className="w-4 h-4" />}167 </button>168 <input169 type="range"170 min={0}171 max={1}172 step={0.05}173 value={isMuted ? 0 : volume}174 onChange={changeVolume}175 className="w-20 h-1 appearance-none bg-white/20 rounded-full cursor-pointer176 [&::-webkit-slider-thumb]:appearance-none [&::-webkit-slider-thumb]:w-2.5 [&::-webkit-slider-thumb]:h-2.5177 [&::-webkit-slider-thumb]:rounded-full [&::-webkit-slider-thumb]:bg-white"178 />179 </div>180 </div>181 </div>182 );183}184