WuYongJie f646b71813
Some checks failed
CI Check / check (push) Has been cancelled
chore: 初始化本地仓库并同步最近改动
2025-11-18 17:50:16 +08:00

112 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextRequest, NextResponse } from 'next/server'
import { prisma } from '@/lib/prisma'
import { readFile, access } from 'fs/promises'
import path from 'path'
// 视频流式传输API(允许未登录用户访问,仅用于视频播放)
export async function GET(req: NextRequest) {
try {
const { searchParams } = new URL(req.url)
const id = searchParams.get('id')
if (!id) {
return NextResponse.json(
{ error: '缺少素材ID' },
{ status: 400 }
)
}
const material = await prisma.material.findUnique({
where: { id },
})
if (!material) {
return NextResponse.json(
{ error: '素材不存在' },
{ status: 404 }
)
}
// 只允许视频类型
if (material.materialType !== '视频') {
return NextResponse.json(
{ error: '此API仅支持视频类型素材' },
{ status: 403 }
)
}
// Read file from local storage
let filePath = material.zipFilePath
if (!path.isAbsolute(filePath)) {
filePath = path.join(process.cwd(), filePath)
}
console.log('Streaming video file:', filePath)
// 检查文件是否存在
try {
await access(filePath)
} catch (error) {
console.error('File not found:', filePath)
return NextResponse.json(
{ error: '文件不存在,可能已被删除' },
{ status: 404 }
)
}
const fileBuffer = await readFile(filePath)
// 根据文件扩展名确定Content-Type
const originalExt = path.extname(material.zipFileName).toLowerCase()
const videoTypes: { [key: string]: string } = {
'.mp4': 'video/mp4',
'.avi': 'video/x-msvideo',
'.mov': 'video/quicktime',
'.wmv': 'video/x-ms-wmv',
'.flv': 'video/x-flv',
'.mkv': 'video/x-matroska',
'.webm': 'video/webm',
}
const contentType = videoTypes[originalExt] || 'video/mp4'
// 支持Range请求(视频流式传输)
const range = req.headers.get('range')
if (range) {
const fileSize = fileBuffer.length
const parts = range.replace(/bytes=/, '').split('-')
const start = parseInt(parts[0], 10)
const end = parts[1] ? parseInt(parts[1], 10) : fileSize - 1
const chunkSize = end - start + 1
const chunk = fileBuffer.slice(start, end + 1)
return new NextResponse(chunk as any, {
status: 206,
headers: {
'Content-Type': contentType,
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunkSize.toString(),
'Cache-Control': 'public, max-age=3600', // 缓存1小时
},
})
}
// 非Range请求,返回完整文件
return new NextResponse(fileBuffer as any, {
headers: {
'Content-Type': contentType,
'Content-Length': fileBuffer.length.toString(),
'Accept-Ranges': 'bytes',
'Cache-Control': 'public, max-age=3600',
},
})
} catch (error) {
console.error('Stream error:', error)
return NextResponse.json(
{ error: '流式传输失败: ' + (error instanceof Error ? error.message : '未知错误') },
{ status: 500 }
)
}
}