Tl;Dr -问题:
使用Node.js 将视频文件流式传输到html5视频播放器以使视频控件继续工作的正确方法是什么?
我认为这与处理头部的方式有关。不管怎样,这是背景信息。代码有点长,但是,它很简单。
使用Node将小视频文件流式传输到HTML5视频非常容易
I learned how to stream small video files to an HTML5 video player very easily.使用此设置,控件可以在我没有任何工作的情况下工作,并且视频流完美无瑕。带有示例视频的完整工作代码的工作副本是here, for download on 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);
但此方法仅限于小于1 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);
});
这样就可以很好地播放视频了!但视频控件不再起作用。
发布于 2014-07-27 09:29:30
Accept Ranges
标头(writeHead()
中的位)是HTML5视频控件工作所必需的。
我认为,与其盲目地发送整个文件,不如先检查请求中的Accept Ranges
头,然后读入并仅发送该位。fs.createReadStream
支持start
和end
选项。
所以我试了一个例子,它起作用了。代码并不美观,但很容易理解。首先,我们处理range标头以获得开始/结束位置。然后,我们使用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);
发布于 2016-08-20 23:59:00
The accepted answer to this question很棒,应该仍然是公认的答案。然而,我在代码中遇到了一个问题,即读取流并不总是结束/关闭。部分解决方案是在第二个createReadStream
参数中将autoClose: true
与start:start, end:end
一起发送。
解决方案的另一部分是限制在响应中发送的最大chunksize
。另一个答案是这样设置end
的:
var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
...which的作用是从所请求的起始位置到其最后一个字节发送文件的其余部分,而不管它有多少字节。但是,客户端浏览器可以选择只读取该流的一部分,如果它还不需要所有字节,就会读取。这将导致流读取被阻止,直到浏览器决定是时候获取更多数据(例如,用户操作,如查找/擦除,或只是通过播放流)。
我需要关闭这个流,因为我在允许用户删除视频文件的页面上显示了<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;
}
这样做的效果是确保在每次请求之后结束/关闭读取流,而不是由浏览器保持活动。
发布于 2020-10-30 11:21:53
首先,在您要发布的目录中创建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端口上运行。除了视频,它还可以流式传输所有类型的文件。
https://stackoverflow.com/questions/24976123
复制相似问题