Aurora SpotiRip desktop and web app
- Spotify playlist, library, album and artist browsing with in-app playback - Song, playlist and album downloads via spotdl with YouTube and Bandcamp fallbacks - Electron desktop app (castLabs Widevine) with a permanent sidebar layout - Linux and Windows packaging scripts
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { toast } from "sonner";
|
||||
import { postSpotifyDownload, type InputType } from "../endpoints/spotify-download_POST.schema";
|
||||
import {
|
||||
getSpotifyDownloadStatus,
|
||||
spotifyDownloadFileUrl,
|
||||
} from "../endpoints/spotify-download-status_GET.schema";
|
||||
import { getSpotifySession } from "../endpoints/spotify-session_GET.schema";
|
||||
import { getDesktopBridge } from "./desktopBridge";
|
||||
|
||||
const POLL_INTERVAL_MS = 1500;
|
||||
|
||||
let canDownloadPromise: Promise<boolean> | undefined;
|
||||
function loadCanDownload() {
|
||||
canDownloadPromise ??= getSpotifySession()
|
||||
.then((session) => {
|
||||
const allowed = Boolean(session.connected && session.canDownload);
|
||||
if (!allowed) canDownloadPromise = undefined;
|
||||
return allowed;
|
||||
})
|
||||
.catch(() => {
|
||||
canDownloadPromise = undefined;
|
||||
return false;
|
||||
});
|
||||
return canDownloadPromise;
|
||||
}
|
||||
|
||||
function saveFromServer(jobId: string) {
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = spotifyDownloadFileUrl(jobId);
|
||||
anchor.rel = "noopener";
|
||||
document.body.appendChild(anchor);
|
||||
anchor.click();
|
||||
anchor.remove();
|
||||
}
|
||||
|
||||
export function useSpotifyDownload() {
|
||||
const [busyKeys, setBusyKeys] = useState<Set<string>>(new Set());
|
||||
const [canDownload, setCanDownload] = useState(false);
|
||||
const mounted = useRef(true);
|
||||
|
||||
useEffect(() => {
|
||||
mounted.current = true;
|
||||
loadCanDownload().then((allowed) => {
|
||||
if (mounted.current) setCanDownload(allowed);
|
||||
});
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
function setBusy(key: string, busy: boolean) {
|
||||
setBusyKeys((current) => {
|
||||
const next = new Set(current);
|
||||
if (busy) next.add(key);
|
||||
else next.delete(key);
|
||||
return next;
|
||||
});
|
||||
}
|
||||
|
||||
async function startDownload(key: string, label: string, request: InputType) {
|
||||
if (busyKeys.has(key)) return;
|
||||
setBusy(key, true);
|
||||
const toastId = toast.loading(`Preparing ${label}…`);
|
||||
try {
|
||||
const { jobId } = await postSpotifyDownload(request);
|
||||
while (mounted.current) {
|
||||
await new Promise((resolve) => window.setTimeout(resolve, POLL_INTERVAL_MS));
|
||||
const status = await getSpotifyDownloadStatus(jobId);
|
||||
if (status.status === "failed") throw new Error(status.error || "Download failed.");
|
||||
if (status.status === "ready") {
|
||||
const skipped = status.failed ? ` (${status.failed} track${status.failed === 1 ? "" : "s"} could not be found)` : "";
|
||||
const desktop = getDesktopBridge();
|
||||
if (desktop && status.savedPath) {
|
||||
const savedPath = status.savedPath;
|
||||
toast.success(`Saved ${label}${skipped}`, {
|
||||
id: toastId,
|
||||
description: savedPath,
|
||||
action: { label: "Show in folder", onClick: () => desktop.showInFolder(savedPath) },
|
||||
});
|
||||
} else {
|
||||
saveFromServer(jobId);
|
||||
toast.success(`Downloading ${status.fileName}${skipped}`, { id: toastId, description: undefined });
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (status.total > 1) {
|
||||
toast.loading(`Downloading ${label}: ${status.done} of ${status.total}`, {
|
||||
id: toastId,
|
||||
description: status.currentTitle,
|
||||
});
|
||||
}
|
||||
}
|
||||
toast.dismiss(toastId);
|
||||
} catch (reason) {
|
||||
toast.error(reason instanceof Error ? reason.message : "Download failed.", { id: toastId });
|
||||
} finally {
|
||||
if (mounted.current) setBusy(key, false);
|
||||
}
|
||||
}
|
||||
|
||||
return { busyKeys, canDownload, startDownload };
|
||||
}
|
||||
Reference in New Issue
Block a user