【问题标题】:Video streaming with HTML 5 via node.js通过 node.js 使用 HTML 5 流式传输视频
【发布时间】:2011-05-20 14:11:54
【问题描述】:

我正在尝试设置一个 Web 服务器,该服务器将支持使用 node.js 将视频流式传输到 HTML5 视频标签。到目前为止,这是我的代码:

var range = request.headers.range;
var total = file.length;

var parts = range.replace(/bytes=/, "").split("-");
var partialstart = parts[0];
var partialend = parts[1];

var start = parseInt(partialstart, 10);
var end = partialend ? parseInt(partialend, 10) : total-1;

var chunksize = (end-start)+1;

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

“request”代表 http 请求,type 是“application/ogg”或“video/ogg”(我都试过了),“file”是从文件系统读取的 .ogv 文件。以下是响应标头:

Content-Range   bytes 0-14270463/14270464
Accept-Ranges   bytes
Content-Length   14270464
Connection     keep-alive
Content-Type     video/ogg

我检查了响应标头,这段代码似乎工作正常,但有几个问题:

  1. 由于在本地网络上,视频加载速度似乎很慢。据我所知,使用 firebug 检查响应,文件似乎以大约 150 kb/秒的速度流入。
  2. 视频根本无法播放。即使我等待整个加载,HTML 5 视频标签也只会显示一个大的“x”而不是 Firefox 中的电影。

有没有人知道我可以做些什么来让视频流通过 node.js 工作?

谢谢!
克里斯

【问题讨论】:

    标签: video html stream node.js


    【解决方案1】:

    我知道这是一个非常老的问题,但正如 Google 似乎喜欢的那样,我认为值得指出的是,我写了一个 Node.js video streaming module(Github,或通过 NPM),希望也值得一看.

    【讨论】:

    • 我喜欢这样的答案!谢谢你。 :)
    • 所以 2 年后回到那个话题...... :) 有没有办法使用该脚本将 udp 接收的实时数据(到浏览器)发送?
    • @randomuser1 不,恐怕不是。直播是脚本不做的more complicated(例如需要对输入进行分段,支持索引文件)。我想支持这一点,但遗憾的是没有时间研究它。对不起。
    • 嘿@meloncholy,实际上我已经完成了所有工作 - 我对输入进行了“分段”,放置了一些索引,然后通过 UDP 发送。我现在可以在 Node.js 中阅读它们并且我看到它们 - 每个段的数量及其内容。但我只在控制台中看到它,我想在用户浏览器的其他站点上合并它 - 我不知道这是否可能
    【解决方案2】:

    在 nodejs 论坛的一些帮助下,我能够让它工作:

    http://groups.google.com/group/nodejs/browse_thread/thread/8339e0dc825c057f/822b2dd48f36e890

    来自 Google 网上论坛主题的亮点:

    众所周知,谷歌浏览器首先发出一个范围为 0-1024 的请求 然后请求范围“1024-”。

    response.end(file.slice(start, chunksize), "binary");

    然后:

    通过设置,我能够让视频在 Firefox 中播放没有问题 将“连接”标头改为“关闭”

    然后:

    您似乎错误地计算了内容长度:

    var chunksize = (end-start)+1;

    如果 start 是 0 并且 end 是 1,在你的情况下 chunksize 是 2,它应该 为 1。

    【讨论】:

    • 我相信块大小是正确的。根据w3.org/Protocols/rfc2616/rfc2616-sec14.htmlThe first-byte-pos value in a byte-range-spec gives the byte-offset of the first byte in a range. The last-byte-pos value gives the byte-offset of the last byte in the range; that is, the byte positions specified are inclusive. Byte offsets start at zero.
    【解决方案3】:

    此解决方案对服务器端视频或音频媒体文件进行异步读取...它在可见的 URL 处启动 nodejs 服务器

    http://localhost:8888/

    它还可以正确处理客户端 HTML5(浏览器/应用程序)向前/向后 UI 小部件滑块移动

    将下面的代码 sn-p 保存为服务器端文件:

    media_server.js
    

    ...使用在服务器端执行它

    node media_server.js
    

    享受

    var http = require('http'),
        fs = require('fs'),
        util = require('util');
    
    var path = "/path/to/local/video/or/audio/file/on/server.mp4";
    
    var port = 8888;
    var host = "localhost";
     
    http.createServer(function (req, res) {
    
      var stat = fs.statSync(path);
      var total = stat.size;
    
      if (req.headers.range) { // meaning client (browser) has moved the forward/back slider
                               // which has sent this request back to this server logic ... cool
        var range = req.headers.range;
        var parts = range.replace(/bytes=/, "").split("-");
        var partialstart = parts[0];
        var partialend = parts[1];
     
        var start = parseInt(partialstart, 10);
        var end = partialend ? parseInt(partialend, 10) : total-1;
        var chunksize = (end-start)+1;
        console.log('RANGE: ' + start + ' - ' + end + ' = ' + chunksize);
     
        var file = fs.createReadStream(path, {start: start, end: end});
        res.writeHead(206, { 'Content-Range': 'bytes ' + start + '-' + end + '/' + total, 'Accept-Ranges': 'bytes', 'Content-Length': chunksize, 'Content-Type': 'video/mp4' });
        file.pipe(res);
    
      } else {
    
        console.log('ALL: ' + total);
        res.writeHead(200, { 'Content-Length': total, 'Content-Type': 'video/mp4' });
        fs.createReadStream(path).pipe(res);
      }
    }).listen(port, host);
    
    console.log("Server running at http://" + host + ":" + port + "/");
    

    【讨论】:

    • 有没有办法不从源文件,而是从 udp 流将视频文件流式传输到浏览器?
    • 快递可以吗?节点菜鸟在这里:)
    【解决方案4】:

    根据 Sam9291 的回答,我使用 createReadStream() 重写了函数并修复了一些问题:

    /**
     * Sends a static file to the HTTP client, supporting partial transfers.
     * 
     * @req HTTP request object
     * @res HTTP response object
     * @fn Path to file that should be sent
     * @contentType MIME type for the response (defaults to HTML)
     */      
    function sendFile(req, res, fn, contentType) {
    
      contentType = contentType || "text/html";
    
      fs.stat(fn, function(err, stats) {
        var headers;
    
        if (err) {
          res.writeHead(404, {"Content-Type":"text/plain"});
          res.end("Could not read file");
          return;
        }
    
        var range = req.headers.range || "";    
        var total = stats.size;
    
        if (range) {
    
          var parts = range.replace(/bytes=/, "").split("-");
          var partialstart = parts[0];
          var partialend = parts[1];
    
          var start = parseInt(partialstart, 10);
          var end = partialend ? parseInt(partialend, 10) : total-1;
    
          var chunksize = (end-start)+1;
    
          headers = { 
            "Content-Range": "bytes " + start + "-" + end + "/" + total, 
            "Accept-Ranges": "bytes", 
            "Content-Length": chunksize, 
            "Content-Type": contentType 
          };
          res.writeHead(206, headers);
    
        } else {
    
          headers = { 
            "Accept-Ranges": "bytes", 
            "Content-Length": stats.size, 
            "Content-Type": contentType 
          };
          res.writeHead(200, headers);
    
        }
    
        var readStream = fs.createReadStream(fn, {start:start, end:end});
        readStream.pipe(res);    
    
      });
    
    }
    

    【讨论】:

      【解决方案5】:

      我在 Node.js 之上使用了 MVC 框架 sails.js,并且我设法通过以下代码使其正常工作:

      /**
       * VideoController
       *
       * @module      :: Controller
       * @description :: Contains logic for handling requests.
       */
      
       var fs = require('fs');
      
      module.exports = {
      
        /* e.g.
        sayHello: function (req, res) {
          res.send('hello world!');
        }
        */
      
        /**
         * /video/stream
         */ 
        stream: function (req,res) {
      
          // This will render the view: 
          // C:\Users\sam\Documents\Dev\Fun\mymoviebank/views/video/stream.ejs
          res.view();
      
        },
      
        play: function (req,res) {
      
          fs.readFile('/Users/sam/Videos/big_buck_bunny.mp4', function (err, data) {
            if (err) throw err;
      
            var range = req.headers.range;
              var total = data.length;
      
              var parts = range.replace(/bytes=/, "").split("-");
              var partialstart = parts[0];
              var partialend = parts[1];
      
              var start = parseInt(partialstart, 10);
              var end = partialend ? parseInt(partialend, 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' });
              res.end(data);
      
          });
      
        }
      
      };
      

      希望对你有帮助

      【讨论】:

      • npm 的 fs.statSync(../file/path/...) 负责处理细节。见gist.github.com/westonplatter/7559003
      • 此解决方案无法扩展 - 它将整个视频文件拉入内存以仅提供一小部分。 fs.createReadStream(theFile, { start : $START, end : #END }) 将让您通过管道将流传输到响应,而无需将整个视频文件加载到内存中(想象一下,如果 1000 个用户同时这样做)。
      【解决方案6】:

      我发现这个解决方案似乎更简单并且(与检查的答案不同)对我有用。 (我尝试在该线程的末尾调整咖啡脚本解决方案,一旦我处理了初始请求(对于“bytes = 0-”)将其炸毁的事实,它就起作用了。

      http://elegantcode.com/2011/04/06/taking-baby-steps-with-node-js-pumping-data-between-streams/

      我的实际实现:

      function stream_response( res, file_path, content_type ){
          var readStream = fs.createReadStream(file_path);
      
          readStream.on('data', function(data) {
              var flushed = res.write(data);
              // Pause the read stream when the write stream gets saturated
              console.log( 'streaming data', file_path );
              if(!flushed){
                  readStream.pause();
              }
          });
      
          res.on('drain', function() {
              // Resume the read stream when the write stream gets hungry 
              readStream.resume();
          });
      
          readStream.on('end', function() {
              res.end();
          });
      
          readStream.on('error', function(err) {
              console.error('Exception', err, 'while streaming', file_path);
              res.end();
          });
      
          res.writeHead(200, {'Content-Type': content_type});
      }
      

      【讨论】:

      • 这确实流媒体很好......但它需要处理 request.headers 以响应客户端小部件请求,例如在源媒体上向前/向后跳过......干得好
      【解决方案7】:

      当使用 express 时,将它放在你的 media_server.js 或 index.js 中,它将在端口 3000 上提供媒体服务

      const express = require('express')
      const fs = require('fs')
      const path = require('path')
      const app = express()
      
      app.use(express.static(path.join(__dirname, 'public')))
      
      app.get('/', function(req, res) {
        res.sendFile(path.join(__dirname + '/index.html'))
      })
      
      app.get('/video', function(req, res) {
        const path = 'assets/sample.mp4'// your video path
        const stat = fs.statSync(path)
        const fileSize = stat.size
        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)
            : fileSize-1
      
          const chunksize = (end-start)+1
          const file = fs.createReadStream(path, {start, end})
          const head = {
            'Content-Range': `bytes ${start}-${end}/${fileSize}`,
            'Accept-Ranges': 'bytes',
            'Content-Length': chunksize,
            'Content-Type': 'video/mp4',
          }
      
          res.writeHead(206, head)
          file.pipe(res)
        } else {
          const head = {
            'Content-Length': fileSize,
            'Content-Type': 'video/mp4',
          }
          res.writeHead(200, head)
          fs.createReadStream(path).pipe(res)
        }
      })
      
      app.listen(3000, function () {
        console.log('Listening on port 3000!')
      })
      

      然后在你的 index.html 中

      <html>
        <head>
          <title>Video stream sample</title>
        </head>
        <body>
          <video id="videoPlayer" controls muted="muted" autoplay> 
            <source src="http://localhost:3000/video" type="video/mp4">
          </video>
        </body>
      </html>
      

      【讨论】:

        【解决方案8】:

        我发现了这个 codesandbox,它似乎真的很有帮助 https://codesandbox.io/s/14n6q1yr33

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-10-28
          • 2019-01-26
          • 1970-01-01
          • 1970-01-01
          • 2017-07-09
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多