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 = { ".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", }, }); }