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...
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.