Initial commit
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
export async function GET(
|
||||
req: NextRequest,
|
||||
{ params }: { params: Promise<{ key: string[] }> }
|
||||
) {
|
||||
const { key } = await params;
|
||||
// key is a catch-all segment array, e.g. ["videos", "uuid-filename.mp4"]
|
||||
const relativePath = key.join("/");
|
||||
|
||||
// Sanitize: prevent path traversal
|
||||
const uploadDir = path.resolve(process.env.LOCAL_UPLOAD_DIR ?? "./uploads");
|
||||
const filePath = path.resolve(path.join(uploadDir, relativePath));
|
||||
|
||||
if (!filePath.startsWith(uploadDir)) {
|
||||
return new NextResponse("Forbidden", { status: 403 });
|
||||
}
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
return new NextResponse("Not found", { status: 404 });
|
||||
}
|
||||
|
||||
const stat = fs.statSync(filePath);
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
const mimeMap: Record<string, string> = {
|
||||
".mp4": "video/mp4",
|
||||
".mov": "video/quicktime",
|
||||
".avi": "video/x-msvideo",
|
||||
".mxf": "application/mxf",
|
||||
".webm": "video/webm",
|
||||
};
|
||||
const contentType = mimeMap[ext] ?? "application/octet-stream";
|
||||
|
||||
// Support range requests so the HTML5 video player can seek
|
||||
const rangeHeader = req.headers.get("range");
|
||||
|
||||
if (rangeHeader) {
|
||||
const [startStr, endStr] = rangeHeader.replace("bytes=", "").split("-");
|
||||
const start = parseInt(startStr, 10);
|
||||
const end = endStr ? parseInt(endStr, 10) : stat.size - 1;
|
||||
const chunkSize = end - start + 1;
|
||||
|
||||
const stream = fs.createReadStream(filePath, { start, end });
|
||||
const nodeStream = stream as unknown as ReadableStream;
|
||||
|
||||
return new NextResponse(nodeStream, {
|
||||
status: 206,
|
||||
headers: {
|
||||
"Content-Range": `bytes ${start}-${end}/${stat.size}`,
|
||||
"Accept-Ranges": "bytes",
|
||||
"Content-Length": String(chunkSize),
|
||||
"Content-Type": contentType,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const stream = fs.createReadStream(filePath) as unknown as ReadableStream;
|
||||
return new NextResponse(stream, {
|
||||
headers: {
|
||||
"Content-Length": String(stat.size),
|
||||
"Content-Type": contentType,
|
||||
"Accept-Ranges": "bytes",
|
||||
},
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user