74 lines
2.3 KiB
JavaScript
74 lines
2.3 KiB
JavaScript
const fs = require('fs');
|
|
|
|
let route = fs.readFileSync('src/app/api/media/[...path]/route.ts', 'utf8');
|
|
|
|
const rangeLogic = `
|
|
// Get file stats for Content-Length
|
|
const stat = await fs.promises.stat(filePath);
|
|
const fileSize = stat.size;
|
|
|
|
// Handle Range Requests (CRITICAL for video streaming in Safari/Chrome)
|
|
const rangeHeader = request.headers.get('range');
|
|
let start = 0;
|
|
let end = fileSize - 1;
|
|
let isPartial = false;
|
|
|
|
if (rangeHeader) {
|
|
const parts = rangeHeader.replace(/bytes=/, '').split('-');
|
|
const partialStart = parts[0];
|
|
const partialEnd = parts[1];
|
|
|
|
start = parseInt(partialStart, 10);
|
|
end = partialEnd ? parseInt(partialEnd, 10) : fileSize - 1;
|
|
|
|
if (start >= fileSize) {
|
|
return new NextResponse('Requested range not satisfiable', { status: 416 });
|
|
}
|
|
isPartial = true;
|
|
}
|
|
|
|
const contentLength = (end - start) + 1;
|
|
|
|
// Build response headers
|
|
const contentType = getSafeContentType(contentTypeExt);
|
|
const headers = new Headers();
|
|
headers.set('Content-Type', contentType);
|
|
headers.set('Content-Length', contentLength.toString());
|
|
headers.set('Cache-Control', 'public, max-age=3600, stale-while-revalidate=86400');
|
|
headers.set('Accept-Ranges', 'bytes');
|
|
|
|
if (isPartial) {
|
|
headers.set('Content-Range', \`bytes \${start}-\${end}/\${fileSize}\`);
|
|
}
|
|
|
|
// SVG: force download to prevent XSS
|
|
if (contentType === 'image/svg+xml') {
|
|
headers.set('Content-Disposition', \`attachment; filename="\${path.basename(relativePath)}"\`);
|
|
}
|
|
|
|
// Stream the file chunk
|
|
const fileStream = fs.createReadStream(filePath, { start, end });
|
|
const webStream = new ReadableStream({
|
|
start(controller) {
|
|
fileStream.on('data', (chunk) => controller.enqueue(new Uint8Array(chunk)));
|
|
fileStream.on('end', () => controller.close());
|
|
fileStream.on('error', (err) => controller.error(err));
|
|
},
|
|
cancel() {
|
|
fileStream.destroy();
|
|
},
|
|
});
|
|
|
|
return new NextResponse(webStream, {
|
|
status: isPartial ? 206 : 200,
|
|
headers
|
|
});
|
|
`;
|
|
|
|
route = route.replace(
|
|
/\/\/ Get file stats for Content-Length[\s\S]+return new NextResponse\(webStream, \{ headers \}\);/,
|
|
rangeLogic.trim()
|
|
);
|
|
|
|
fs.writeFileSync('src/app/api/media/[...path]/route.ts', route, 'utf8');
|