使用Node.js将video文件stream式传输到html5video播放器,以便video控件继续工作?
Tl;博士 – 问题:
使用Node.js处理video文件到html5video播放器的正确方法是什么, 以便video控件继续工作?
我认为这与处理标题的方式有关。 无论如何,这是背景信息。 代码有点冗长,但是,它非常简单。
使用Node将小video文件stream式传输到HTML5video很容易
我学会了如何轻松地将小video文件传输到HTML5video播放器。 通过这种设置,控制器无需任何工作,videostream完美无瑕。 具有示例video的完整工作代码的工作副本位于此处,可在Google文档中下载 。
客户:
<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
stream式传输(任何大小)的video文件
通过利用fs.createReadStream()
,服务器可以读取stream中的文件,而不是一次读入内存。 这听起来像是做事的正确方法,语法非常简单:
服务器片段:
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); });
这streamvideo就好了! 但video控制不再有效。
Accept Ranges
头( writeHead()
的位)是HTML5video控件的工作所必需的。
我认为,而不是盲目地发送完整的文件,你应该首先检查REQUEST中的Accept Ranges
头,然后读入并发送这个位。 fs.createReadStream
支持start
, end
选项。
所以我尝试了一个例子,它的工作原理。 代码并不漂亮,但很容易理解。 首先我们处理范围头部以获得开始/结束位置。 然后我们使用fs.stat
来获取文件的大小,而不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);
这个问题被接受的答案是可怕的,应该保持被接受的答案。 然而,我遇到了一个问题,读取stream不总是被closures。 部分解决scheme是在第二个createReadStream
arg中发送autoClose: true
和start:start, end:end
。
解决scheme的另一部分是限制响应中发送的最大块大小。 另一个答案集是这样end
:
var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
…具有从所请求的开始位置通过其最后一个字节发送文件的其余部分的效果,不pipe可能有多less字节。 然而,客户端浏览器只能读取该stream的一部分,并且如果不需要全部字节的话。 这将导致stream读取被阻塞,直到浏览器决定是时候获取更多的数据为止(例如,像seek / scrub这样的用户操作,或者只是通过播放stream)。
我需要closures此stream,因为我在页面上显示<video>
元素,允许用户删除video文件。 但是,直到客户端(或服务器)closures连接之前,文件才从文件系统中被移除,因为这是stream结束/closures的唯一方法。
我的解决scheme只是设置一个maxChunk
configurationvariables,将其设置为1MB,并且一次只读取一个超过1MB的stream。
// 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; }
这具有确保读取stream在每个请求之后结束/closures并且不被浏览器保持活动的效果。
我也写了一个单独的StackOverflow 问题和涵盖这个问题的答案 。