簡體   English   中英

使用 Node.js 將視頻文件流式傳輸到 html5 視頻播放器,以便視頻控件繼續工作?

[英]Streaming a video file to an html5 video player with Node.js so that the video controls continue to work?

Tl;Dr - 問題:

使用 Node.js 將視頻文件流式傳輸到 html5 視頻播放器以便視頻控件繼續工作的正確方法是什么

認為這與處理標題的方式有關。 無論如何,這是背景信息。 代碼有點長,但是,它非常簡單。

使用 Node 將小視頻文件流式傳輸到 HTML5 視頻很容易

我學會了如何輕松地將小視頻文件流式傳輸到 HTML5 視頻播放器。 使用此設置,控件無需我的任何操作即可工作,並且視頻流完美無缺。 帶有示例視頻的完整代碼的工作副本在這里,可在 Google Docs 上下載

客戶:

<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標頭( writeHead()的位)是 HTML5 視頻控件工作所必需的。

我認為不是盲目地發送完整文件,您應該首先檢查 REQUEST 中的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);

這個問題的公認答案很棒,應該仍然是公認的答案。 但是,我遇到了讀取流並不總是結束/關閉的代碼問題。 部分解決方案是在第二個createReadStream參數中發送autoClose: truestart:start, end:end

該解決方案的另一部分是限制最大chunksize在響應中發送的存在。 另一個答案集是這樣end

var end = positions[1] ? parseInt(positions[1], 10) : total - 1;

...它的作用是從請求的起始位置到最后一個字節發送文件的其余部分,無論可能有多少字節。 然而,客戶端瀏覽器可以選擇只讀取該流的一部分,如果它不需要所有字節,也會這樣做。 這將導致讀取的流被阻塞,直到瀏覽器決定是時候獲取更多數據(例如,諸如搜索/清理之類的用戶操作,或僅通過播放流)。

我需要關閉此流,因為我在允許用戶刪除視頻文件的頁面上顯示<video>元素。 然而,在客戶端(或服務器)關閉連接之前,文件不會從文件系統中刪除,因為這是流結束/關閉的唯一方式。

我的解決方案只是設置一個maxChunk配置變量,將其設置為 1MB,並且永遠不要將一次超過 1MB 的讀取流傳輸到響應。

// same code as accepted answer
var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
var chunksize = (end - start) + 1;

// poor hack to send smaller chunks to the browser
var maxChunk = 1024 * 1024; // 1MB at a time
if (chunksize > maxChunk) {
  end = start + maxChunk - 1;
  chunksize = (end - start) + 1;
}

這具有確保讀取流在每次請求后結束/關閉的效果,並且不會被瀏覽器保持活動狀態。

我也寫了一個單獨的StackOverflow問題答案涉及這個問題。

首先在要發布的目錄中創建app.js文件。

var http = require('http');
var fs = require('fs');
var mime = require('mime');
http.createServer(function(req,res){
    if (req.url != '/app.js') {
    var url = __dirname + req.url;
        fs.stat(url,function(err,stat){
            if (err) {
            res.writeHead(404,{'Content-Type':'text/html'});
            res.end('Your requested URI('+req.url+') wasn\'t found on our server');
            } else {
            var type = mime.getType(url);
            var fileSize = stat.size;
            var range = req.headers.range;
                if (range) {
                    var parts = range.replace(/bytes=/, "").split("-");
                var start = parseInt(parts[0], 10);
                    var end = parts[1] ? parseInt(parts[1], 10) : fileSize-1;
                    var chunksize = (end-start)+1;
                    var file = fs.createReadStream(url, {start, end});
                    var head = {
                'Content-Range': `bytes ${start}-${end}/${fileSize}`,
                'Accept-Ranges': 'bytes',
                'Content-Length': chunksize,
                'Content-Type': type
                }
                    res.writeHead(206, head);
                    file.pipe(res);
                    } else {    
                    var head = {
                'Content-Length': fileSize,
                'Content-Type': type
                    }
                res.writeHead(200, head);
                fs.createReadStream(url).pipe(res);
                    }
            }
        });
    } else {
    res.writeHead(403,{'Content-Type':'text/html'});
    res.end('Sorry, access to that file is Forbidden');
    }
}).listen(8080);

只需運行node app.js ,你的服務器就會在 8080 端口上運行。除了視頻,它還可以流式傳輸各種文件。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM