73 lines
2.3 KiB
TypeScript
73 lines
2.3 KiB
TypeScript
import { NextResponse } from 'next/server';
|
|
import { createReadStream } from 'node:fs';
|
|
import { Readable } from 'node:stream';
|
|
import { statVideoFile } from '@/lib/storage';
|
|
|
|
export const runtime = 'nodejs';
|
|
export const dynamic = 'force-dynamic';
|
|
|
|
function parseRange(range: string | null, size: number): { start: number; end: number } | null {
|
|
if (!range) return null;
|
|
const match = range.match(/^bytes=(\d*)-(\d*)$/);
|
|
if (!match) return null;
|
|
|
|
const [, rawStart, rawEnd] = match;
|
|
if (!rawStart && !rawEnd) return null;
|
|
|
|
if (!rawStart) {
|
|
const suffixLength = Number(rawEnd);
|
|
if (!Number.isFinite(suffixLength) || suffixLength <= 0) return null;
|
|
return { start: Math.max(size - suffixLength, 0), end: size - 1 };
|
|
}
|
|
|
|
const start = Number(rawStart);
|
|
const end = rawEnd ? Number(rawEnd) : size - 1;
|
|
if (!Number.isFinite(start) || !Number.isFinite(end) || start < 0 || end < start || start >= size) {
|
|
return null;
|
|
}
|
|
return { start, end: Math.min(end, size - 1) };
|
|
}
|
|
|
|
export async function GET(req: Request, ctx: { params: Promise<{ filename: string }> }) {
|
|
const { filename } = await ctx.params;
|
|
const video = await statVideoFile(filename);
|
|
if (!video) return NextResponse.json({ error: 'video not found' }, { status: 404 });
|
|
|
|
const baseHeaders = {
|
|
'Content-Type': video.type,
|
|
'Cache-Control': 'private, max-age=3600',
|
|
'Accept-Ranges': 'bytes',
|
|
};
|
|
const requestedRange = req.headers.get('range');
|
|
if (requestedRange) {
|
|
const range = parseRange(requestedRange, video.size);
|
|
if (!range) {
|
|
return new Response(null, {
|
|
status: 416,
|
|
headers: {
|
|
...baseHeaders,
|
|
'Content-Range': `bytes */${video.size}`,
|
|
},
|
|
});
|
|
}
|
|
const length = range.end - range.start + 1;
|
|
const stream = Readable.toWeb(createReadStream(video.filePath, { start: range.start, end: range.end }));
|
|
return new Response(stream as ReadableStream, {
|
|
status: 206,
|
|
headers: {
|
|
...baseHeaders,
|
|
'Content-Length': String(length),
|
|
'Content-Range': `bytes ${range.start}-${range.end}/${video.size}`,
|
|
},
|
|
});
|
|
}
|
|
|
|
const stream = Readable.toWeb(createReadStream(video.filePath));
|
|
return new Response(stream as ReadableStream, {
|
|
headers: {
|
|
...baseHeaders,
|
|
'Content-Length': String(video.size),
|
|
},
|
|
});
|
|
}
|