Next.js应用MongoDB GridFSBucket MP4视频流无法播放问题排查
Next.js中GridFS视频流无法播放的问题排查与修复
问题描述
在Next.js应用中尝试通过getServerSideProps()实现MongoDB GridFSBucket中MP4视频的流播放,Chrome浏览器中视频播放器正常显示,但视频无法播放(时长显示为0:00)。日志出现警告warn - You should not access 'res' after getServerSideProps resolves.,无其他错误信息。
原代码
export const getServerSideProps: GetServerSideProps = async ({ res, query: { hash } }) => { const database = await mongodb() const Videos = database.collection('videos') const { fileId } = await Videos.findOne({ uid: hash }) const Files = new GridFSBucket(database) const id = new ObjectId(fileId) const file: GridFSFile = await new Promise((resolve, reject) => { Files.find({ _id: id }).toArray((err, files) => { if (err) reject(err) resolve(files[0]) }) }) const { contentType } = file || {} res.writeHead('Content-Type', contentType) // contentType is "video/mp4" Files.openDownloadStream(id) .on('data', (chunk) => { res.write(chunk) }) .on('end', () => { res.end() }) .on('error', (err) => { throw err }) return { props: {} } }
根本性错误分析
getServerSideProps使用场景错误:该函数设计目的是为页面获取渲染所需Props,而非直接处理响应流。你在函数内直接操作res写入视频数据,同时返回props,会导致Next.js继续执行页面渲染流程,与流操作冲突,既触发警告,又导致响应内容混乱,播放器无法解析。res.writeHead参数错误:res.writeHead第一个参数必须是HTTP状态码(如200),第二个参数才是响应头对象。原代码将'Content-Type'作为第一个参数,导致响应头设置完全错误,浏览器无法识别视频类型。未支持范围请求(Range):视频播放器需要通过Range请求实现分段加载、解析时长,原代码未处理该请求,也未设置
Accept-Ranges、Content-Length等必需响应头,这是时长显示0:00且无法播放的核心原因。错误处理不规范:流的
error事件中直接抛出异常,会导致未捕获错误,无法正确返回错误响应。
修复方案:改用API路由处理视频流
Next.js推荐使用API路由处理这类响应流场景,以下是修复后的代码示例:
import type { NextApiRequest, NextApiResponse } from 'next' import { mongodb } from '../../lib/mongodb' // 替换为你的MongoDB连接路径 import { GridFSBucket, GridFSFile } from 'mongodb' import { ObjectId } from 'mongodb' export default async function handler(req: NextApiRequest, res: NextApiResponse) { const { hash } = req.query if (!hash || typeof hash !== 'string') { res.status(400).json({ error: '无效的哈希值' }) return } try { const database = await mongodb() const Videos = database.collection('videos') const videoDoc = await Videos.findOne({ uid: hash }) if (!videoDoc?.fileId) { res.status(404).json({ error: '视频不存在' }) return } const Files = new GridFSBucket(database) const id = new ObjectId(videoDoc.fileId) const file: GridFSFile = await new Promise((resolve, reject) => { Files.find({ _id: id }).toArray((err, files) => { if (err) reject(err) resolve(files[0]) }) }) if (!file) { res.status(404).json({ error: '文件不存在' }) return } const { contentType, length } = file const range = req.headers.range if (range) { // 处理范围请求,支持视频分段加载 const parts = range.replace(/bytes=/, '').split('-') const start = parseInt(parts[0], 10) const end = parts[1] ? parseInt(parts[1], 10) : length - 1 const chunksize = end - start + 1 res.writeHead(206, { 'Content-Range': `bytes ${start}-${end}/${length}`, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': contentType || 'video/mp4', }) const stream = Files.openDownloadStream(id, { start, end }) stream.pipe(res) } else { // 处理完整视频请求 res.writeHead(200, { 'Content-Length': length, 'Content-Type': contentType || 'video/mp4', 'Accept-Ranges': 'bytes', }) const stream = Files.openDownloadStream(id) stream.pipe(res) } } catch (err) { console.error('视频流错误:', err) res.status(500).json({ error: '服务器内部错误' }) } } // 禁用Next.js默认的响应大小限制,支持大视频流 export const config = { api: { responseLimit: false, }, }
修复说明
- 改用API路由专门处理视频流,符合Next.js设计规范,避免与页面Props获取逻辑冲突。
- 正确处理Range请求,实现视频分段加载,解决时长显示异常问题。
- 修复
res.writeHead参数错误,正确设置响应头。 - 增加完整的错误处理,返回对应HTTP状态码。
- 设置
responseLimit: false,避免Next.js限制大视频的响应大小。
内容的提问来源于stack exchange,提问作者user3142695
相关产品推荐
相关产品推荐

