summaryrefslogtreecommitdiff
path: root/apps/web/src/components/editor/audio-waveform.tsx
blob: cae2359b5dcd904d478e384d1bcb083314e08cdb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import React, { useEffect, useRef, useState } from "react";
import WaveSurfer from "wavesurfer.js";

interface AudioWaveformProps {
	audioUrl: string;
	height?: number;
	className?: string;
}

const AudioWaveform: React.FC<AudioWaveformProps> = ({
	audioUrl,
	height = 32,
	className = "",
}) => {
	const waveformRef = useRef<HTMLDivElement>(null);
	const wavesurfer = useRef<WaveSurfer | null>(null);
	const [isLoading, setIsLoading] = useState(true);
	const [error, setError] = useState(false);

	useEffect(() => {
		let mounted = true;

		const initWaveSurfer = async () => {
			if (!waveformRef.current || !audioUrl) return;

			try {
				// Clean up any existing instance
				if (wavesurfer.current) {
					try {
						wavesurfer.current.destroy();
					} catch (e) {
						// Silently ignore destroy errors
					}
					wavesurfer.current = null;
				}

				wavesurfer.current = WaveSurfer.create({
					container: waveformRef.current,
					waveColor: "rgba(255, 255, 255, 0.6)",
					progressColor: "rgba(255, 255, 255, 0.9)",
					cursorColor: "transparent",
					barWidth: 2,
					barGap: 1,
					height: height,
					normalize: true,
					interact: false,
				});

				// Event listeners
				wavesurfer.current.on("ready", () => {
					if (mounted) {
						setIsLoading(false);
						setError(false);
					}
				});

				wavesurfer.current.on("error", (err) => {
					console.error("WaveSurfer error:", err);
					if (mounted) {
						setError(true);
						setIsLoading(false);
					}
				});

				await wavesurfer.current.load(audioUrl);
			} catch (err) {
				console.error("Failed to initialize WaveSurfer:", err);
				if (mounted) {
					setError(true);
					setIsLoading(false);
				}
			}
		};

		initWaveSurfer();

		return () => {
			mounted = false;
			if (wavesurfer.current) {
				try {
					wavesurfer.current.destroy();
				} catch (e) {
					// Silently ignore destroy errors
				}
				wavesurfer.current = null;
			}
		};
	}, [audioUrl, height]);

	if (error) {
		return (
			<div
				className={`flex items-center justify-center ${className}`}
				style={{ height }}
			>
				<span className="text-xs text-foreground/60">Audio unavailable</span>
			</div>
		);
	}

	return (
		<div className={`relative ${className}`}>
			{isLoading && (
				<div className="absolute inset-0 flex items-center justify-center">
					<span className="text-xs text-foreground/60">Loading...</span>
				</div>
			)}
			<div
				ref={waveformRef}
				className={`w-full transition-opacity duration-200 ${isLoading ? "opacity-0" : "opacity-100"}`}
				style={{ height }}
			/>
		</div>
	);
};

export default AudioWaveform;