SPB Git forge

spb/spb-cloud

Public
10commits 1branches 0releases
3.2 MBsize
maindefault branch
4 h agolast push
JavaScript 56.7% TypeScript 42.6%
5.7 KB · 184 lines tsx
Raw Blame History
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