Files
DefVideo/server/src/services/streaming.ts
한성일 82662d417d 초기 커밋: DefVideo 소스 등록
abcVideo 플레이어 소스 (client / server / shared / pythonsource / docs / .claude).
.gitignore 적용으로 node_modules·storage·samplevideo·미디어 등 대용량 일괄 제외.
103 files, ~964K.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 03:20:27 +00:00

79 lines
2.0 KiB
TypeScript

import fs from 'fs';
import { promises as fsp } from 'fs';
import path from 'path';
import { pipeline } from 'stream/promises';
import { Request, Response } from 'express';
import { config } from '../config';
const CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
export function resolveVideoPath(videoId: string): string {
const filePath = path.resolve(config.videosDir, videoId);
// Path traversal guard
if (!filePath.startsWith(path.resolve(config.videosDir))) {
throw new Error('Invalid video path');
}
return filePath;
}
export async function streamVideo(req: Request, res: Response): Promise<void> {
const { videoId } = req.params;
let filePath: string;
try {
filePath = resolveVideoPath(videoId);
} catch {
res.status(400).json({ error: 'Invalid video ID' });
return;
}
let stat: fs.Stats;
try {
stat = await fsp.stat(filePath);
} catch {
res.status(404).json({ error: 'Video not found' });
return;
}
const fileSize = stat.size;
const rangeHeader = req.headers.range;
if (!rangeHeader) {
res.writeHead(200, {
'Content-Type': 'video/mp4',
'Content-Length': fileSize,
'Accept-Ranges': 'bytes',
});
const stream = fs.createReadStream(filePath, { highWaterMark: 1024 * 1024 });
await pipeline(stream, res);
return;
}
const [startStr, endStr] = rangeHeader.replace(/bytes=/, '').split('-');
const start = parseInt(startStr, 10);
const end = Math.min(
endStr ? parseInt(endStr, 10) : start + CHUNK_SIZE - 1,
fileSize - 1
);
if (start > end || start >= fileSize) {
res.writeHead(416, { 'Content-Range': `bytes */${fileSize}` });
res.end();
return;
}
res.writeHead(206, {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': end - start + 1,
'Content-Type': 'video/mp4',
});
const stream = fs.createReadStream(filePath, {
start,
end,
highWaterMark: 1024 * 1024,
});
await pipeline(stream, res);
}