fix: add AbortController to fetch call in audio download

Abort in-flight download when the component unmounts or a new download
starts, preventing wasted bandwidth on navigation.

Closes #919
This commit is contained in:
likiosliu 2026-03-24 19:53:14 +08:00
parent c41e79860f
commit a3b3852452

View file

@ -27,6 +27,7 @@ function formatTime(seconds: number): string {
export function Audio({ id, src, title, description, artwork, durationMs, className }: AudioProps) {
const audioRef = useRef<HTMLAudioElement>(null);
const downloadControllerRef = useRef<AbortController | null>(null);
const [isPlaying, setIsPlaying] = useState(false);
const [currentTime, setCurrentTime] = useState(0);
const [duration, setDuration] = useState(durationMs ? durationMs / 1000 : 0);
@ -84,8 +85,12 @@ export function Audio({ id, src, title, description, artwork, durationMs, classN
// Handle download
const handleDownload = useCallback(async () => {
downloadControllerRef.current?.abort();
const controller = new AbortController();
downloadControllerRef.current = controller;
try {
const response = await fetch(src);
const response = await fetch(src, { signal: controller.signal });
const blob = await response.blob();
const url = window.URL.createObjectURL(blob);
const a = document.createElement("a");
@ -96,10 +101,16 @@ export function Audio({ id, src, title, description, artwork, durationMs, classN
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
} catch (err) {
if (err instanceof DOMException && err.name === "AbortError") return;
console.error("Error downloading audio:", err);
}
}, [src, title]);
// Abort in-flight download on unmount
useEffect(() => {
return () => downloadControllerRef.current?.abort();
}, []);
// Set up audio event listeners
useEffect(() => {
const audio = audioRef.current;