Files
videoPlayer/server/middleware/videos.ts
2025-10-02 01:52:03 -06:00

95 lines
2.6 KiB
TypeScript

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<string, string> = {
'.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'
})
}
})