BigDataPolyp/src/routes/api/video-stream/[Index]/+server.ts
2025-10-22 00:05:25 +00:00

95 lines
3.0 KiB
TypeScript

import fs from 'fs';
import path from 'path';
import { error, type RequestHandler } from '@sveltejs/kit';
import pool from '$lib/server/database';
import { lookup as getMimeType } from 'mime-types'; // ✅ ESM 호환 안전한 방식
export const GET: RequestHandler = async ({ params, request }) => {
console.log(`\n--- [video-stream] 요청 수신: ${new Date().toISOString()} ---`);
const Index = params.Index;
console.log(`[1. 파라미터] 요청된 동영상 Index: ${Index}`);
if (!Index || isNaN(Number(Index))) {
throw error(400, '유효한 동영상 Index가 필요합니다.');
}
const client = await pool.connect();
let filePath: string;
try {
console.log('[2. DB] 파일 경로 조회 시작...');
const query = `
SELECT "LocationFile"
FROM "TumerMovie"
WHERE "Index" = $1;
`;
const result = await client.query(query, [Index]);
if (result.rows.length === 0) {
throw error(404, `해당 Index(${Index})의 동영상을 찾을 수 없습니다.`);
}
filePath = result.rows[0].LocationFile;
console.log(`[2. DB] 조회된 파일 경로: ${filePath}`);
} catch (dbError) {
console.error('[DB 오류]', dbError);
throw error(500, '데이터베이스 조회 중 오류 발생');
} finally {
client.release();
}
try {
const stat = fs.statSync(filePath);
const fileSize = stat.size;
const contentType = getMimeType(filePath) || 'application/octet-stream'; // ✅ 안전한 MIME 판별
console.log(`[3. 파일 시스템] 파일 크기: ${fileSize}, Content-Type: ${contentType}`);
const range = request.headers.get('range');
if (!range) {
console.log('[4. Range 없음 → 전체 전송]');
const fullStream = fs.createReadStream(filePath);
return new Response(fullStream as any, {
status: 200,
headers: {
'Content-Type': contentType,
'Content-Length': fileSize.toString(),
'Accept-Ranges': 'bytes'
}
});
}
console.log(`[4. Range 요청 수신]: ${range}`);
const parts = range.replace(/bytes=/, '').split('-');
const start = parseInt(parts[0], 10);
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1;
if (isNaN(start) || isNaN(end) || start >= fileSize || end >= fileSize) {
console.error('[Range 오류] 잘못된 범위 요청:', range);
throw error(416, '잘못된 Range 요청');
}
const chunkSize = end - start + 1;
console.log(`[5. 스트리밍 구간] ${start} - ${end} (${chunkSize} bytes)`);
const fileStream = fs.createReadStream(filePath, { start, end });
return new Response(fileStream as any, {
status: 206,
headers: {
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunkSize.toString(),
'Content-Type': contentType
}
});
} catch (fileError: any) {
if (fileError.code === 'ENOENT') {
console.error('[파일 오류] 파일 없음:', filePath);
throw error(404, '파일을 찾을 수 없습니다.');
}
console.error('[파일 시스템 오류]', fileError);
throw error(500, '파일 스트리밍 중 오류 발생');
}
};