import { createReadStream, statSync } from 'fs' import { join } from 'path' export default defineEventHandler(async (event) => { const url = getRequestURL(event) // Solo manejar rutas que empiecen con /videos/ if (!url.pathname.startsWith('/videos/')) { return } // Extraer el path del archivo (puede incluir subcarpetas para HLS) const file = url.pathname.substring('/videos/'.length) if (!file) { return } // Validar que el path solo contenga caracteres seguros (permite subcarpetas con /) if (!/^[a-zA-Z0-9_\-\.\/]+$/.test(file)) { throw createError({ statusCode: 400, statusMessage: 'Invalid file path' }) } const filePath = join(process.cwd(), 'videos', file) try { // Verificar que el archivo existe const stats = statSync(filePath) if (!stats.isFile()) { throw createError({ statusCode: 404, statusMessage: 'File not found' }) } // Determinar el tipo MIME basado en la extensión const ext = file.toLowerCase().substring(file.lastIndexOf('.')) const mimeTypes: Record = { '.mp4': 'video/mp4', '.webm': 'video/webm', '.ogg': 'video/ogg', '.mov': 'video/quicktime', '.avi': 'video/x-msvideo', '.mkv': 'video/x-matroska', '.m3u8': 'application/vnd.apple.mpegurl', '.ts': 'video/mp2t' } const mimeType = mimeTypes[ext] || 'application/octet-stream' // Configurar headers para streaming de video setResponseHeaders(event, { 'Content-Type': mimeType, 'Content-Length': String(stats.size), 'Accept-Ranges': 'bytes', 'Cache-Control': 'public, max-age=604800' // 7 días }) // Manejar range requests para seeking en el video const range = getRequestHeader(event, 'range') if (range) { const parts = range.replace(/bytes=/, '').split('-') const start = parseInt(parts[0], 10) const end = parts[1] ? parseInt(parts[1], 10) : stats.size - 1 const chunkSize = (end - start) + 1 setResponseStatus(event, 206) // Partial Content setResponseHeaders(event, { 'Content-Range': `bytes ${start}-${end}/${stats.size}`, 'Content-Length': String(chunkSize) }) return createReadStream(filePath, { start, end }) } // Retornar el archivo completo si no hay range request return createReadStream(filePath) } catch (error: any) { if (error.code === 'ENOENT') { throw createError({ statusCode: 404, statusMessage: 'File not found' }) } throw createError({ statusCode: 500, statusMessage: 'Error reading file' }) } })