audio-player-with-waveform

PreviousNext

An advanced audio player with waveform visualization powered by WaveSurfer.js. Features customizable waveform styling, playback speed control, and interactive seeking.

Docs
austin-uiblock

Preview

Loading preview…
registry/hooks/use-audio-waveform.ts
// ./registry/hooks/use-audio-waveform.ts

import { useEffect, useRef, useState } from "react";
import type WaveSurfer from "wavesurfer.js";

export interface UseAudioWaveformOptions {
  waveColor?: string;
  progressColor?: string;
  height?: number;
  barWidth?: number;
  barGap?: number;
  barRadius?: number;
  normalize?: boolean;
  onPlay?: () => void;
  onPause?: () => void;
  onEnd?: () => void;
  onError?: (error: Error) => void;
  onReady?: () => void;
}

export interface UseAudioWaveformReturn {
  waveformRef: React.RefObject<HTMLDivElement | null>;
  isPlaying: boolean;
  isLoading: boolean;
  duration: number;
  currentTime: number;
  playPause: () => void;
  stop: () => void;
  seek: (time: number) => void;
  setPlaybackRate: (rate: number) => void;
}

export const useAudioWaveform = (
  audioUrl: string | null,
  options: UseAudioWaveformOptions = {}
): UseAudioWaveformReturn => {
  const {
    waveColor = "#e5e7eb",
    progressColor = "#3b82f6",
    height = 64,
    barWidth = 4,
    barGap = 8,
    barRadius = 30,
    normalize = true,
    onPlay,
    onPause,
    onEnd,
    onError,
    onReady,
  } = options;

  const waveformRef = useRef<HTMLDivElement>(null);
  const wavesurferRef = useRef<WaveSurfer | null>(null);
  const [isPlaying, setIsPlaying] = useState(false);
  const [isLoading, setIsLoading] = useState(true);
  const [duration, setDuration] = useState(0);
  const [currentTime, setCurrentTime] = useState(0);

  useEffect(() => {
    if (!waveformRef.current || !audioUrl) return;

    let wavesurfer: WaveSurfer | null = null;

    // Dynamic import of WaveSurfer
    import("wavesurfer.js").then((WaveSurferModule) => {
      const WaveSurfer = WaveSurferModule.default;

      if (!waveformRef.current) return;

      wavesurfer = WaveSurfer.create({
        container: waveformRef.current,
        waveColor,
        progressColor,
        height,
        barWidth,
        barGap,
        barRadius,
        cursorWidth: 2,
        cursorColor: progressColor,
        normalize,
        backend: "WebAudio",
        mediaControls: false,
      });

      wavesurferRef.current = wavesurfer;

      // Load audio
      wavesurfer.load(audioUrl);

      // Event listeners
      wavesurfer.on("ready", () => {
        setIsLoading(false);
        setDuration(wavesurfer!.getDuration());
        onReady?.();
      });

      wavesurfer.on("play", () => {
        setIsPlaying(true);
        onPlay?.();
      });

      wavesurfer.on("pause", () => {
        setIsPlaying(false);
        onPause?.();
      });

      wavesurfer.on("finish", () => {
        setIsPlaying(false);
        setCurrentTime(0);
        onEnd?.();
      });

      wavesurfer.on("audioprocess", () => {
        setCurrentTime(wavesurfer!.getCurrentTime());
      });

      wavesurfer.on("seeking", () => {
        setCurrentTime(wavesurfer!.getCurrentTime());
      });

      wavesurfer.on("error", (error) => {
        console.error("WaveSurfer error:", error);
        setIsLoading(false);
        onError?.(new Error(String(error)));
      });
    });

    // Cleanup
    return () => {
      if (wavesurfer) {
        wavesurfer.destroy();
      }
      wavesurferRef.current = null;
    };
  }, [
    audioUrl,
    waveColor,
    progressColor,
    height,
    barWidth,
    barGap,
    barRadius,
    normalize,
  ]);

  const playPause = () => {
    if (wavesurferRef.current) {
      wavesurferRef.current.playPause();
    }
  };

  const stop = () => {
    if (wavesurferRef.current) {
      wavesurferRef.current.stop();
      setCurrentTime(0);
    }
  };

  const seek = (time: number) => {
    if (wavesurferRef.current && duration > 0) {
      const seekPosition = Math.max(0, Math.min(time / duration, 1));
      wavesurferRef.current.seekTo(seekPosition);
    }
  };

  const setPlaybackRate = (rate: number) => {
    if (wavesurferRef.current) {
      wavesurferRef.current.setPlaybackRate(rate);
    }
  };

  return {
    waveformRef,
    isPlaying,
    isLoading,
    duration,
    currentTime,
    playPause,
    stop,
    seek,
    setPlaybackRate,
  };
};

Installation

npx shadcn@latest add @austin-ui/audio-player-with-waveform

Usage

import { AudioPlayerWithWaveform } from "@/components/audio-player-with-waveform"
<AudioPlayerWithWaveform />