frontend/src/routes/api/video/[filename].ts
2024-09-19 16:04:35 +02:00

83 lines
2.4 KiB
TypeScript

import { promises as fsPromises } from "fs";
import fs from "fs";
import fetch from "node-fetch";
import type { RequestHandler } from "@sveltejs/kit";
const CHUNK_SIZE = 1 * 1024 * 1024; // 1MB in bytes
const parseRange = (range: string) => {
const [start, end] = range
.replace(/bytes=/, "")
.split("-")
.map(Number);
return { start, end: end || undefined };
};
const createResponse = (
body: ReadableStream | Buffer,
headers: Record<string, string>,
status = 206,
) => new Response(body, { status, headers });
const streamRemoteFile = async (url: string, range: string) => {
const { start, end } = parseRange(range);
const response = await fetch(url, {
headers: { Range: `bytes=${start}-${end || ""}` },
});
return createResponse(response.body, {
"Content-Range": response.headers.get("Content-Range") || "",
"Accept-Ranges": "bytes",
"Content-Length": response.headers.get("Content-Length") || "",
"Content-Type": response.headers.get("Content-Type") || "",
});
};
const streamLocalFile = async (filePath: string, range: string) => {
const fileSize = (await fsPromises.stat(filePath)).size;
const { start, end } = parseRange(range);
const chunkEnd = end || Math.min(start + CHUNK_SIZE - 1, fileSize - 1);
if (start >= fileSize) {
return new Response(
`Requested range not satisfiable\n${start} >= ${fileSize}`,
{
status: 416,
headers: { "Content-Range": `bytes */${fileSize}` },
},
);
}
const stream = fs.createReadStream(filePath, { start, end: chunkEnd });
return createResponse(stream, {
"Content-Range": `bytes ${start}-${chunkEnd}/${fileSize}`,
"Accept-Ranges": "bytes",
"Content-Length": (chunkEnd - start + 1).toString(),
"Content-Type": "video/mp4",
});
};
export const GET: RequestHandler = async ({ params, request }) => {
const { filename } = params;
const range = request.headers.get("range");
if (!range) {
return new Response("Requires Range header", { status: 400 });
}
try {
const videoPath = filename.startsWith("http")
? filename
: `static/${filename}`;
return filename.startsWith("http")
? await streamRemoteFile(videoPath, range)
: await streamLocalFile(videoPath, range);
} catch (error) {
console.error("Error streaming video:", error);
return new Response("File not found or error streaming video", {
status: 404,
});
}
};