101 lines
2.7 KiB
TypeScript
101 lines
2.7 KiB
TypeScript
'use client'
|
|
|
|
import { useState } from "react";
|
|
import { AudioLines, Loader2, RefreshCw } from "lucide-react";
|
|
import { Button } from "~/components/ui/button";
|
|
import { trpc } from "~/app/_trpc/Client";
|
|
import { useUploadThing } from "~/lib/uploadthing";
|
|
import { transcodeToAac } from "~/lib/ffmpeg/transcode";
|
|
import { toast } from "sonner";
|
|
import type { RouterOutputs } from "~/server/routers/_app";
|
|
import type { IterableElement } from "type-fest";
|
|
|
|
export default function ConvertToStreamButton(props: {
|
|
track: IterableElement<RouterOutputs['music']['list']>;
|
|
}) {
|
|
const { track } = props;
|
|
const utils = trpc.useUtils();
|
|
const [busy, setBusy] = useState(false);
|
|
const [stage, setStage] = useState("");
|
|
const [progress, setProgress] = useState(0);
|
|
|
|
const setStream = trpc.music.setStream.useMutation({
|
|
onSuccess: () => utils.music.list.invalidate(),
|
|
});
|
|
|
|
const { startUpload } = useUploadThing("musicUploader");
|
|
|
|
async function handleConvert() {
|
|
setBusy(true);
|
|
setProgress(0);
|
|
let currentStage = "Loading ffmpeg";
|
|
const goto = (s: string) => {
|
|
currentStage = s;
|
|
setStage(s);
|
|
};
|
|
try {
|
|
goto("Transcoding");
|
|
const file = await transcodeToAac({
|
|
sourceUrl: track.fileUrl,
|
|
outputName: `${track.title || "track"}.m4a`,
|
|
onProgress: setProgress,
|
|
});
|
|
|
|
goto("Uploading");
|
|
const uploaded = await startUpload([file]);
|
|
const res = uploaded?.[0];
|
|
if (!res) throw new Error("Upload returned no file");
|
|
|
|
goto("Saving");
|
|
await setStream.mutateAsync({
|
|
id: track.id,
|
|
streamUrl: res.serverData.fileUrl,
|
|
streamKey: res.serverData.fileKey,
|
|
streamName: res.serverData.fileName,
|
|
});
|
|
toast("Streaming version saved");
|
|
} catch (e) {
|
|
console.error("[ConvertToStream] failed during", currentStage, e);
|
|
const detail =
|
|
e instanceof Error
|
|
? e.message
|
|
: typeof e === "string"
|
|
? e
|
|
: (() => {
|
|
try {
|
|
return JSON.stringify(e);
|
|
} catch {
|
|
return String(e);
|
|
}
|
|
})();
|
|
toast(`Conversion failed (${currentStage}): ${detail || "see console for details"}`);
|
|
} finally {
|
|
setBusy(false);
|
|
setStage("");
|
|
setProgress(0);
|
|
}
|
|
}
|
|
|
|
return (
|
|
<div className="flex items-center gap-2">
|
|
<Button type="button" variant="outline" size="sm" disabled={busy} onClick={handleConvert}>
|
|
{busy ? (
|
|
<Loader2 className="animate-spin" />
|
|
) : track.streamUrl ? (
|
|
<RefreshCw />
|
|
) : (
|
|
<AudioLines />
|
|
)}
|
|
{busy
|
|
? `${stage}${stage === "Transcoding" && progress ? ` ${Math.round(progress * 100)}%` : "…"}`
|
|
: track.streamUrl
|
|
? "Re-generate stream"
|
|
: "Generate stream (AAC)"}
|
|
</Button>
|
|
{track.streamUrl && !busy && (
|
|
<span className="text-xs text-muted-foreground">Streaming version ready</span>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|