83 lines
2.4 KiB
TypeScript
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,
|
|
});
|
|
}
|
|
};
|