TL;DR-问题:

What is the right way to handle streaming a video file to an html5 video player with Node.js so that the video controls continue to work?

I think这与处理报头的方式有关.不管怎样,这是背景资料.代码很长,但是非常简单.

使用Node将小视频文件流式传输到HTML5视频很容易

I learned how to stream small video files to an HTML5 video player very easily.在这种设置下,控件可以在我不做任何工作的情况下工作,视频流也可以完美无瑕.带有示例视频的完整工作代码的工作副本是here, for download on Google Docs.

Client:

<html>
  <title>Welcome</title>
    <body>
      <video controls>
        <source src="movie.mp4" type="video/mp4"/>
        <source src="movie.webm" type="video/webm"/>
        <source src="movie.ogg" type="video/ogg"/>
        <!-- fallback -->
        Your browser does not support the <code>video</code> element.
    </video>
  </body>
</html>

Server:

// Declare Vars & Read Files

var fs = require('fs'),
    http = require('http'),
    url = require('url'),
    path = require('path');
var movie_webm, movie_mp4, movie_ogg;
// ... [snip] ... (Read index page)
fs.readFile(path.resolve(__dirname,"movie.mp4"), function (err, data) {
    if (err) {
        throw err;
    }
    movie_mp4 = data;
});
// ... [snip] ... (Read two other formats for the video)

// Serve & Stream Video

http.createServer(function (req, res) {
    // ... [snip] ... (Serve client files)
    var total;
    if (reqResource == "/movie.mp4") {
        total = movie_mp4.length;
    }
    // ... [snip] ... handle two other formats for the video
    var range = req.headers.range;
    var positions = range.replace(/bytes=/, "").split("-");
    var start = parseInt(positions[0], 10);
    var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
    var chunksize = (end - start) + 1;
    if (reqResource == "/movie.mp4") {
        res.writeHead(206, {
            "Content-Range": "bytes " + start + "-" + end + "/" + total,
                "Accept-Ranges": "bytes",
                "Content-Length": chunksize,
                "Content-Type": "video/mp4"
        });
        res.end(movie_mp4.slice(start, end + 1), "binary");
    }
    // ... [snip] ... handle two other formats for the video
}).listen(8888);

但此方法仅限于大小为&lt;1 GB的文件.

Streaming (any size) video files with fs.createReadStream

通过使用fs.createReadStream(),服务器可以读取流中的文件,而不是一次将其全部读入内存.这听起来是正确的做法,语法非常简单:

Server Snippet:

movieStream = fs.createReadStream(pathToFile);
movieStream.on('open', function () {
    res.writeHead(206, {
        "Content-Range": "bytes " + start + "-" + end + "/" + total,
            "Accept-Ranges": "bytes",
            "Content-Length": chunksize,
            "Content-Type": "video/mp4"
    });
    // This just pipes the read stream to the response object (which goes 
    //to the client)
    movieStream.pipe(res);
});

movieStream.on('error', function (err) {
    res.end(err);
});

这个视频流很好!但视频控制不再起作用.

推荐答案

HTML5视频控件需要Accept Ranges标头(writeHead()中的位)才能工作.

我认为,与其盲目地发送完整的文件,不如先判断请求中的Accept Ranges头,然后读入并仅发送该位.fs.createReadStream支持startend选项.

所以我试了一个例子,它起作用了.代码并不美观,但很容易理解.首先,我们处理范围标头以获得开始/结束位置.然后,我们使用fs.stat来获取文件的大小,而无需将整个文件读取到内存中.最后,使用fs.createReadStream将请求的部件发送给客户端.

var fs = require("fs"),
    http = require("http"),
    url = require("url"),
    path = require("path");

http.createServer(function (req, res) {
  if (req.url != "/movie.mp4") {
    res.writeHead(200, { "Content-Type": "text/html" });
    res.end('<video src="http://localhost:8888/movie.mp4" controls></video>');
  } else {
    var file = path.resolve(__dirname,"movie.mp4");
    fs.stat(file, function(err, stats) {
      if (err) {
        if (err.code === 'ENOENT') {
          // 404 Error if file not found
          return res.sendStatus(404);
        }
      res.end(err);
      }
      var range = req.headers.range;
      if (!range) {
       // 416 Wrong range
       return res.sendStatus(416);
      }
      var positions = range.replace(/bytes=/, "").split("-");
      var start = parseInt(positions[0], 10);
      var total = stats.size;
      var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
      var chunksize = (end - start) + 1;

      res.writeHead(206, {
        "Content-Range": "bytes " + start + "-" + end + "/" + total,
        "Accept-Ranges": "bytes",
        "Content-Length": chunksize,
        "Content-Type": "video/mp4"
      });

      var stream = fs.createReadStream(file, { start: start, end: end })
        .on("open", function() {
          stream.pipe(res);
        }).on("error", function(err) {
          res.end(err);
        });
    });
  }
}).listen(8888);

Html相关问答推荐

Angular 17 -如何使用@if @else向 *ngIf的同一个ng-模板指示其他两个

如何删除html原生对话框的宽度

当表头包含特殊字符时,R Quarto发布的HTML表中不必要的大列宽

carousel 的垂直滚动按钮居中

从html文件中提取元素的Python BeautifulSoup

如何用背景色填充边框半径创建的间隙

为什么字体大小而不是 colored颜色 属性适用于元素?

为什么一个 CSS 网格框比其他网格框低?

带有图像的虚线边框

为什么我不能覆盖 div 的样式?

用由文本制成的边框包围内容

按钮位于主 div 的底部而不是内部 Div

在Firefox中使用keySplines时,SVG: <animateMotion>不起作用

CSS "overflow"不能显示整个单词

调整屏幕大小时标题在图像下方重叠 - HTML

调整窗口大小时文本重叠导航栏

pull-right 不适用于 bootstrap alert 内的按钮

无法使用 HTML 中的 Bootstrap 自上而下居中

如何使列表的第一个元素比 css 中的其他元素大?

AWS SES:如何正确使用 CSS