添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Hi! First of all I have to say thanks to this beautiful library that wavesurfer is.. And I would love to sponsor/donate once my project start off..

Currently im trying to do some complex things that i dont know if they are doable or somehow achievable...

For example (On track loaded by user)

Show placeholder initially
Load low-resolution peaks from storage if available
If no stored peaks, generate low-resolution peaks (e.g., 200-400 points)
Save the low-resolution peaks for future use
Optimize for multiple instances on same page

Current Behavior:

The code creates a waveform visualization using WaveSurfer.js
It shows a placeholder sine wave while loading (placeholderPeaks)
It tries to load saved peaks from Supabase (Will migrate this to a json/binary file on s3)
When audio loads, it generates new peaks regardless of saved data
The peaks are always high resolution (8000+ points)
After generated data it changes to the stored data

(And having much problems with the signal.destroy)

Used the version:
"wavesurfer.js": "^7.4.5",

const useWavesurfer = (
  containerRef,
  fileChanged,
  isUploaded,
  trackId,
  isBigger,
  peakDataId,
  onMetadataLoad
) => {
  const [wavesurfer, setWavesurfer] = useState(null);
  const [peaks, setPeaks] = useState(null);
  const wsRef = useRef(null);
  const placeholderPeaks = [
    Array(200)
      .fill(0)
      .map((_, i) => Math.sin((i / 200) * Math.PI)),
  useEffect(() => {
    if (isUploaded && trackId) {
      const fetchPeakData = async () => {
        try {
          const { data, error } = await supabase
            .from("tracks")
            .select("peak_data")
            .eq("id", trackId)
            .single();
          if (error) throw error;
          if (data && data.peak_data) {
            setPeaks(data.peak_data);
        } catch (error) {
          console.error("Error fetching peak data:", error);
      fetchPeakData();
  }, [isUploaded, trackId]);
  useEffect(() => {
    if (!containerRef.current) return;
    if (wsRef.current) {
      wsRef.current.destroy();
      wsRef.current = null;
    const waveColor = isBigger ? "#008000" : isUploaded ? "#bb77ca" : "#808080";
    const progressColor = isBigger
      ? "#59fe4d"
      : isUploaded
      ? "#c575db"
      : "#808080";
    const ws = WaveSurfer.create({
      container: containerRef.current,
      waveColor: waveColor,
      progressColor: progressColor,
      audioRate: 1,
      cursorColor: "#59fe4d",
      cursorWidth: isBigger ? 3 : 1,
      barWidth: isBigger ? 3 : 1,
      barGap: isBigger ? 3 : 1,
      barRadius: isBigger ? 1 : 1,
      barAlign: isBigger ? "bottom" : "",
      barHeight: isBigger ? 1 : 1,
      height: isBigger ? 54 : 24,
      fillParent: true,
      dragToSeek: true,
      interact: true,
      hideScrollbar: true,
      responsive: true,
      normalize: false,
      peaks: peaks || placeholderPeaks,
      backend: "MediaElement",
      fetchParams: {
        headers: {
          Referer: window.location.origin,
      duration: 2,
    wsRef.current = ws;
    const loadAudio = async () => {
      try {
        await ws.load(url);
        // Get metadata after successful load
        const duration = ws.getDuration();
        const audioBuffer = ws.getDecodedData();
        const metadata = {
          duration: duration,
          // sampleRate: audioBuffer?.sampleRate || 0,
          channels: audioBuffer?.numberOfChannels || 0,
          // // Calculate bitrate using file size and duration
          // bitrate: duration ? Math.round((file?.size * 8) / duration) : 0,
          // format: file?.type
        onMetadataLoad?.(metadata);
        if (!isUploaded && !peaks) {
          const realPeaks = ws.exportPeaks({ precision: 10 });
          useTrackStore.getState().setTrackPeaks(trackId, realPeaks);
      } catch (error) {
        console.error("Error loading audio:", error);
    loadAudio();
    return () => {
      if (wsRef.current) {
        wsRef.current.pause();
        wsRef.current.destroy();
        wsRef.current = null;
  }, [url, isUploaded, trackId, isBigger, peaks]);
  return wavesurfer;
const WaveForm = ({
  fileChanged,
  isUploaded,
  trackId,
  isBigger = false,
  peakDataId,
  onMetadataLoad
}) => {
  const containerRef = useRef(null);
  const wavesurfer = useWavesurfer(
    containerRef,
    fileChanged,
    isUploaded,
    trackId,
    isBigger,
    peakDataId,
    (metadata) => {
      const fullMetadata = {
        ...metadata,
      console.log("Full metadata:", fullMetadata);
      onMetadataLoad?.(fullMetadata);
  const {
    currentWaveSurfer,
    currentTrackId,
    setCurrentWaveSurfer,
    setCurrentTrackId,
    stopCurrentTrack,
  } = usePlaybackStore();
  const isPlaying = currentTrackId === trackId;
  useEffect(() => {
    if (wavesurfer) {
      wavesurfer.on("finish", () => {
        stopCurrentTrack();
      // Event listener for seeking to the clicked position and auto-playing
      const handleClick = (e) => {
        if (currentWaveSurfer && currentWaveSurfer !== wavesurfer) {
          currentWaveSurfer.pause();
        const rect = containerRef.current.getBoundingClientRect();
        const x = e.clientX - rect.left;
        const width = rect.width;
        const progress = x / width;
        wavesurfer.seekTo(progress);
        wavesurfer.play(); // Auto-play on seek
        setCurrentWaveSurfer(wavesurfer); // Set current WaveSurfer
        setCurrentTrackId(trackId); // Set current track ID
      containerRef.current.addEventListener("click", handleClick);
      return () => {
        if (wavesurfer) {
          wavesurfer.un("finish");
        if (containerRef.current) {
          containerRef.current.removeEventListener("click", handleClick);
    wavesurfer,
    stopCurrentTrack,
    currentWaveSurfer,
    setCurrentWaveSurfer,
    setCurrentTrackId,
    trackId,
  const handlePlayPause = () => {
    if (wavesurfer) {
      if (currentWaveSurfer && currentWaveSurfer !== wavesurfer) {
        currentWaveSurfer.pause();
      if (isPlaying) {
        wavesurfer.pause();
        stopCurrentTrack();
      } else {
        wavesurfer.play();
        setCurrentWaveSurfer(wavesurfer);
        setCurrentTrackId(trackId);
  return (
      <div className="controls" onClick={handlePlayPause}>
        {isPlaying ? (
          isBigger ? (
            <Button className="h-14 w-14 border-2" variant="outline">
              <PauseIcon
                className="stroke-2 text-zinc-100/80 hover:text-zinc-100 transition-colors duration-200"
                size={36}
            </Button>
          ) : (
            <PauseIcon size={20} />
        ) : isBigger ? (
          <Button className="h-14 w-14 border-2" variant="outline">
            <PlayIcon
              className="stroke-2 text-zinc-100/80 hover:text-zinc-100 transition-colors duration-200"
              size={36}
          </Button>
        ) : (
          <PlayIcon size={20} />
      <div ref={containerRef} className="h-full w-full" />
      {isBigger && (
        <div className="flex gap-x-2">
          <Button className="h-12 w-12" variant="outline">
            <CheckIcon width={16} height={16} />
          </Button>
          <Button className="h-12 w-12" variant="outline">
            <XIcon width={16} height={16} />
          </Button>

Any pointing on the right direction would be greatly appreciated..

To get low-res peaks, you're already using the right method (exportPeaks), just pass { maxLength: 400, precision: 1000, channels: 1 } or similar to limit how many peaks it returns.

You can also use the useWavesurfer hook from wavesurfer-react for convenience. Just make sure to memoize all the props you pass to it to avoid re-renderings. In your current code, the metadata callback isn't memoized, for example.

To get low-res peaks, you're already using the right method (exportPeaks), just pass { maxLength: 400, precision: 1000, channels: 1 } or similar to limit how many peaks it returns.

You can also use the useWavesurfer hook from wavesurfer-react for convenience. Just make sure to memoize all the props you pass to it to avoid re-renderings. In your current code, the metadata callback isn't memoized, for example.