如何使用Node.js将视频文件流式传输到html5视频播放器,以便视频控件继续工作?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (3)
  • 关注 (0)
  • 查看 (2019)

使用Node.js处理视频文件到html5视频播放器的正确方法是什么,以便视频控件继续工作?

认为它必须处理标题的处理方式。

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

通过这种设置,控件无需任何工作

客户:

<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>

服务器:

// 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);

但是这种方法仅限于<1GB大小的文件。

使用流式传输(任何大小)视频文件 fs.createReadStream

通过利用fs.createReadStream(),服务器可以读取流中的文件,而不是立即将其全部读入存储器中。

服务器片段:

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);
});

这流式传输视频就好了!但视频控制不再有效。

提问于
用户回答回答于

Accept Ranges报头,用于HTML5视频控制工作是必需的。

我认为,不要盲目地发送完整的文件,你应该首先检查Accept RangesREQUEST中的头部,然后读入并发送该位。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);
用户回答回答于

我遇到了一个问题,其中读取流并不总是被关闭。部分解决方案是在第二个参数中autoClose: true一起发送。start:start, end:endcreateReadStream

解决方案的另一部分是限制chunksize响应中发送的最大值。

用户回答回答于

我用eggjs这样推流,但是根本不能快进,一快进视频就停止了。只能重新播放。

   
 //type "video/mp4"
    video(filePath, type) {
        const { ctx, app } = this;
        if (!(/video/.test(type))) {
            return;
        }
        let fileSize = fs.statSync(filePath).size;
        let step=parseInt(fileSize/100);
            let start = ctx.get('range').substr(ctx.get('range').indexOf('=') + 1, ctx.get('range').indexOf('-') - 1),
                end = ctx.get('range').substr(ctx.get('range').indexOf('-') + 1);
            start = parseInt(start);
            end = parseInt(end);

            if (isNaN(start)) {
                start=0;
            }
            if (isNaN(end)) {
                end = fileSize - 1;
                // end=start+step>=fileSize?fileSize-1:start+step;
            }
            console.log('**************************')
            console.log('start-end', start,'-',end)
            console.log('**************************')
            ctx.status = 206;
            let header = {
                'Accept-Ranges': 'bytes',
                'Content-Type': type,
                'Content-Length': end - start + 1,
                'Content-Range': `bytes ${start}-${end}/${fileSize}`,
                "cache-control":'public,max-age=31536000'
            };
            ctx.set(header);

            ctx.body = fs.createReadStream(filePath, {
                satrt: start,
                end: end,
                autoClose:true
            });
    }

扫码关注云+社区

领取腾讯云代金券