You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node.js HTTP服务器加载缓慢求助:video.js视频流场景

解决Node.js视频流服务器加载缓慢的问题

让我帮你排查一下这个视频流服务器加载慢的问题,先揪出代码里的几个关键坑,再给你对应的修复方案:

1. 语法错误直接导致服务器异常

你在设置响应头的时候犯了个低级错误:

'Content-Type' = mime.getType(filename),

这里应该用冒号:而非等号=,而且结尾的逗号是中文的,这会直接让服务器处理请求时抛出语法错误,请求直接卡住,这绝对是加载慢的元凶之一!

2. HTML页面Gzip处理的Content-Length错误

你给HTML页面设置的Content-Length是原始未压缩页面的字节长度,但实际返回的是gzip压缩后的内容,这会让浏览器接收数据时出现校验异常,导致解析延迟甚至失败。正确的做法是要么去掉手动设置的Content-Length让Node.js自动处理,要么用压缩后的数据长度:

zlib.gzip(page, (err, data) => {
  if (err) {
    console.error(err); // 这里log.e是未定义的,换成console.error
    throw err;
  }
  headers['Content-Length'] = data.length; // 改用压缩后的内容长度
  res.writeHead(200, headers);
  res.end(data);
});

3. 视频文件MIME类型不匹配

你的视频是.mkv格式,但<source>里写的type="video/webm",这完全不对!MKV的正确MIME类型是video/x-matroska,浏览器识别不了正确类型,就无法高效处理流式加载,自然会卡顿。赶紧修改HTML里的source标签:

<source src="video.mkv" type="video/x-matroska"/>

另外更推荐把视频转成MP4(H.264编码)格式,它的浏览器兼容性更好,流式播放支持也更成熟。

4. 异步文件检查与同步stat的冲突

你用了异步的fs.exists,但后面立刻用同步的fs.statSync,如果文件在这两个操作之间被删除或修改,会直接抛出错误导致服务器崩溃。应该用异步的fs.stat来替代,既避免阻塞,又能统一处理文件存在性和状态:

fs.stat(filename, (err, stat) => {
  if (err) {
    if (err.code === 'ENOENT') {
      res.writeHead(404);
      return res.end();
    }
    res.writeHead(500);
    return res.end();
  }
  // 后续处理逻辑全部放在这里
});

5. 缺少缓存头优化

静态资源(JS、CSS、视频)没有设置缓存策略,浏览器每次请求都要重新下载,大大增加加载时间。给静态资源加上缓存头:

// 针对静态资源设置1天的缓存
if (/\.(js|css|ico|mkv|vtt)$/.test(filename.toLowerCase())) {
  headers['Cache-Control'] = 'public, max-age=86400';
}

6. 错误处理不完善

代码里的log.e(err)是未定义的,换成console.error(err);另外流的错误处理要确保响应能正确结束,避免请求挂起:

stream.on('error', err => {
  console.error(err);
  if (!res.headersSent) {
    res.writeHead(500);
  }
  res.end();
});

修正后的完整代码

const http = require('http');
const fs = require('fs');
const url = require('url');
const path = require('path');
const zlib = require('zlib');
const mime = require('mime');

http.createServer((req, res) => {
  let uri = url.parse(req.url).pathname;

  // 根路径返回首页
  if (uri === '/') {
    let page = `<!DOCTYPE html>
<html lang="en">
<head>
<title>Video</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1.0, minimum-scale=1.0">
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" type="text/css" href="assets/css/video-js.min.css">
<script src="assets/js/video.min.js"></script>
</head>
<body>
<div id="video_area">
<video id="video" class="video-js vjs-big-play-centered">
<source src="video.mkv" type="video/x-matroska"/>
<track kind="subtitles" lang="en" src="en.vtt" default/>
</video>
</div>
<script>
window.player = videojs('video', {
controls: true,
autoplay: false,
preload: 'metadata', // 改成metadata比auto更适合慢加载场景,仅加载视频元数据
fluid: true,
playbackRates: [0.5, 1, 1.5, 2]
});
</script>
</body>
</html>`;

    let headers = {
      'Content-Encoding': 'gzip',
      'Content-Type': 'text/html',
      'Vary': 'Accept-Encoding'
    };

    zlib.gzip(page, (err, data) => {
      if (err) {
        console.error(err);
        res.writeHead(500);
        return res.end();
      }
      headers['Content-Length'] = data.length;
      res.writeHead(200, headers);
      res.end(data);
    });
  } else {
    let filename = path.join("./", uri);
    // 用异步stat替代exists+statSync,避免阻塞和异常
    fs.stat(filename, (err, stat) => {
      if (err) {
        if (err.code === 'ENOENT') {
          res.writeHead(404);
          return res.end();
        }
        console.error(err);
        res.writeHead(500);
        return res.end();
      }

      let headers = {
        'Accept-Ranges': 'bytes',
        'Content-Type': mime.getType(filename), // 修复语法错误
        'Vary': 'Accept-Encoding'
      };

      // 添加静态资源缓存头
      if (/\.(js|css|ico|mkv|vtt)$/.test(filename.toLowerCase())) {
        headers['Cache-Control'] = 'public, max-age=86400';
      }

      // 处理断点续传
      let range = req.headers.range;
      let stream;
      if (range) {
        let parts = range.replace(/bytes=/, '').split('-');
        let start = parseInt(parts[0]);
        let end = parts[1] ? parseInt(parts[1]) : stat.size - 1;
        // 修正Range的合法性,避免超出文件范围
        start = Math.max(0, Math.min(start, stat.size - 1));
        end = Math.max(start, Math.min(end, stat.size - 1));
        let chunk_size = end - start + 1;
        stream = fs.createReadStream(filename, {start, end});
        headers['Content-Length'] = chunk_size;
        headers['Content-Range'] = `bytes ${start}-${end}/${stat.size}`;
        res.writeHead(206, headers);
      } else {
        headers['Content-Length'] = stat.size;
        stream = fs.createReadStream(filename);
        res.writeHead(200, headers);
      }

      stream.on('error', err => {
        console.error(err);
        if (!res.headersSent) {
          res.writeHead(500);
        }
        res.end();
      });

      // JS/CSS文件启用gzip压缩
      if (/\.(js|css)$/.test(filename.toLowerCase())) {
        let gzip = zlib.createGzip();
        gzip.on('error', err => {
          console.error(err);
          if (!res.headersSent) {
            res.writeHead(500);
          }
          res.end();
        });
        headers['Content-Encoding'] = 'gzip';
        // 压缩后删除Content-Length,让Node.js自动处理
        delete headers['Content-Length'];
        res.writeHead(range ? 206 : 200, headers);
        stream.pipe(gzip).pipe(res);
      } else {
        stream.pipe(res);
      }
    });
  }
}).listen(80);

额外优化建议

  • 推荐用express框架配合compression、serve-static中间件搭建服务器,这些成熟库已经处理了缓存、压缩、错误处理等细节,比手写原生HTTP服务器更稳定高效。
  • 如果视频文件很大,建议完善断点续传的合法性校验,避免无效请求占用资源。
  • 优先把视频转成H.264编码的MP4格式,它的流式播放支持更好,浏览器兼容性更广,能有效提升加载速度。

内容的提问来源于stack exchange,提问作者askar882

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 07:56:38