Web*_*404 82 javascript video html5 node.js
使用Node.js处理将视频文件流式传输到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>
Run Code Online (Sandbox Code Playgroud)
服务器:
// 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);
Run Code Online (Sandbox Code Playgroud)
但是此方法仅限于大小<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);
});
Run Code Online (Sandbox Code Playgroud)
这使视频流得很好!但视频控制不再有效.
tun*_*ngd 107
的Accept Ranges报头(在该位writeHead()),用于HTML5视频控制工作是必需的.
我认为不是盲目地发送完整文件,而应首先检查Accept RangesREQUEST中的标题,然后读入并发送该位.fs.createReadStream支持start和end选择.
所以我尝试了一个例子,它的确有效.代码不漂亮,但很容易理解.首先,我们处理范围标题以获得开始/结束位置.然后我们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);
Run Code Online (Sandbox Code Playgroud)
dan*_*wig 20
这个问题的公认答案很棒,应该仍然是公认的答案.但是我遇到了代码的问题,其中读取流并不总是被结束/关闭.部分解决方案是autoClose: true与start:start, end:end第二个createReadStreamarg 一起发送.
解决方案的另一部分是限制chunksize响应中发送的最大值.另一个答案设置end如下:
var end = positions[1] ? parseInt(positions[1], 10) : total - 1;
Run Code Online (Sandbox Code Playgroud)
...无论可能有多少字节,它都具有从请求的起始位置到其最后一个字节发送文件的其余部分的效果.但是,客户端浏览器可以选择只读取该流的一部分,如果它还不需要所有字节的话.这将导致流读取被阻止,直到浏览器决定获取更多数据(例如,搜索/擦除等用户操作,或仅通过播放流).
我需要关闭此流,因为我在<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;
}
Run Code Online (Sandbox Code Playgroud)
这具有确保在每个请求之后结束/关闭读取流并且不被浏览器保持活动的效果.
我还写了一个单独的StackOverflow 问题和答案,涵盖了这个问题.
| 归档时间: |
|
| 查看次数: |
107320 次 |
| 最近记录: |