Files
ai-toy-patent-workflow/src/app/api/video-file/[filename]/route.ts
2026-05-21 21:56:20 +08:00

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),
},
});
}