如何动态加载大型视频文件并将其流式传输到不发送范围标头的 IE?



不久前,我在一些用于流式传输视频的教程中找到了这个函数,这样整个文件就不需要加载到 RAM 中即可提供文件(因此您可以提供大型视频文件而不会因超过 Node 的内存上限而崩溃.js - 这不难超过电影长度的视频文件, 增加内存分配只是一个创可贴解决方案)。

var fs = require("fs"), 
http = require("http"), 
url = require("url"), 
path = require("path");
var dirPath = process.cwd();
var videoReqHandler = function(req, res, pathname) {
var file = dirPath + "/client" + pathname;
var range = req.headers.range;
var positions = range.replace(/bytes=/, "").split("-");
var start = parseInt(positions[0], 10);
fs.stat(file, function(err, stats) {
if (err) {
throw err;
} else {
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);
});
}
});
};
module.exports.handle = videoReqHandler;

它在Chrome和FF中运行良好,但是,当您愿意(新名称,相同的可怜功能支持)请求mp4时,它会使服务器崩溃。

var positions = range.replace(/bytes=/, "").split("-");
^
TypeError: Cannot read property 'replace' of undefined

我怀疑这是因为范围标题不是强制性的,并且此函数需要它们。

我的问题是,如何修改此函数以避免在未发送range标头时崩溃?我对标头或视频流了解不多,而且我对以块为单位读取文件的理解非常不确定,所以我真的可以在这个涉及所有三个的功能上使用一些帮助。


我根据评论尝试了什么

到目前为止,基于@AndréDion链接的答案:

想通了。IE11 确实支持伪流式处理,但它需要 "接受范围:字节"标头,然后才会费心请求 范围,因此服务器需要响应该范围,无论是否 它实际上是在发送一个字节范围。我不得不修改我的视频流 模块来做到这一点。

我尝试将处理程序代码包装在:

if (req.headers.range) {
console.log('Video request sent WITH range header!');
// ... handler code ...
} else {
console.log('Video request sent without range header!');
res.writeHead(206, {
"Accept-Ranges": "bytes"
});
res.end();
}

但似乎什么也没发生 - 服务器停止崩溃,并继续在其他浏览器上运行,但 IE 似乎没有加载视频。我期待IE等待该响应,然后发送另一个请求,这次包括范围标头,但这似乎没有发生。

也许在向 IE 发送接受范围标头时,我需要发送与 206 不同的响应代码?我不知道它会是什么,但是当我使用 accept-rages 标头响应时,IE 似乎重复了两次请求(但不包括范围标头,这是我所需要的)。

就目前而言,如果我使用如上所述的条件运行服务器,然后尝试在IE中加载视频一次,然后尝试在Chrome中加载它,我会得到以下日志:

Video request sent without range header!
Video request sent without range header!
Video request sent without range header!
Video request sent WITH range header!

IE发送三个没有范围标头的请求,Chrome当然总是按预期发送范围标头,发送一个请求并成功加载视频。我只是不确定从这里开始。

看起来Internet Explorer期望的不仅仅是Accept-Ranges标头。此 MSDN 论坛帖子指示您还需要使用返回 ETag 的 If-Range 标头进行响应。像这样:

console.log('Video request sent without range header!');
res.writeHead(206, {
"Accept-Ranges": "bytes",
"If-Range": ""<ETag#>""
});

不幸的是,从那以后我无法进行任何测试,所以请告诉我它是怎么回事,当我有机会时,我会进一步调查并编辑我的帖子。


更新:

我进行了进一步的调查,发现 If-Range 是不必要的。事实上,这个请求处理程序对于Internet Explorer来说似乎不够健壮。这是我有效的代码:

var videoReqHandler = function(req, res, pathname) {
var file = path.resolve(__dirname, pathname);
fs.stat(file, function(err, stats) {
if (err) {
if (err.code === 'ENOENT'){
//404 Error if file not found
res.writeHead(404, {
"Accept-Ranges" : "bytes",
"Content-Range" : "bytes " + start + "-" + end + "/" + total,
"Content-Length" : chunksize,
"Content-Type" : "video/mp4"
});
}
res.end(err);
}
var start;
var end;
var chunksize;
var total = stats.size;
var range = req.headers.range;
if (range) {
var positions = range.replace(/bytes=/, "").split("-");
end = positions[1] ? parseInt(positions[1], 10) : total - 1;
start = parseInt(positions[0], 10);
}else{
start = 0;
end = total - 1;
}

if (start > end || start < 0 || end > total - 1){
//error 416 is "Range Not Satisfiable"
res.writeHead(416, {
"Accept-Ranges" : "bytes",
"Content-Range" : "*/" + stats.size,
"Content-Type" : "video/mp4"
});
res.end();
return;
}
if (start == 0 && end == total -1){
res.writeHead(200, {
"Accept-Ranges" : "bytes",
"Content-Range" : "bytes " + start + "-" + end + "/" + total,
"Content-Length" : total,
"Content-Type" : "video/mp4"
});
}else{
res.writeHead(206, {
"Accept-Ranges" : "bytes",
"Content-Range" : "bytes " + start + "-" + end + "/" + total,
"Content-Length" : end - start + 1,
"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);
});
});
};

这将添加一些缺失的错误检查,例如确保请求的范围是有效的范围,如果请求没有范围标头,则返回代码为 200 的整个文件。不过,要使其对生产服务器来说坚如磐石,还需要做几件事:

  • 更多的研究表明,If-Range 标头的目的是处理以下情况:有人请求了文件的一部分,它在服务器上被更改,然后他们请求文件的另一部分。If-Range 允许检测到这种情况,服务器可以发送整个新文件,而不是破坏所有内容并将可怜的请求者与此新文件的一部分混淆。
  • 此代码不会检查以确保请求的范围以字节为单位。如果请求指定任何其他度量单位,则此代码可能会使服务器崩溃。应该检查一下。
  • 范围
  • 标头允许一次指定多个范围。请求多个范围时的响应由标头和请求的数据组成,由可指定的拆分字符串分隔。更多关于 MDN 的信息。这一点是有争议的,你可以说你可以在生产服务器上只处理第一部分,只要拥有更多不会崩溃任何东西,但我认为它是规范的一部分。要完全符合规范,您需要实现它,我厌倦了遇到不太符合规范的服务。

最后一点:这段代码可以让 IE 和 Edge 工作,但我确实注意到,由于某种原因,他们最终两次请求 FULL 文件!在我的设置中,请求是这样的:

请求部分文件时
  1. 为"范围字节=0-",换句话说,完整文件
  2. 使用普通 Range 语句请求文件的一小部分
  3. 请求完整文件而不带范围标头

最后一点,在我添加使用完整文件和 200 状态响应的功能之前,如果没有 Range 标头,IE 将显示带有"解码错误"的视频黑框。如果有人能让我知道为什么,那就太棒了!

我知道 Viziionary 想要坚持使用原版 Node.js,但对于其他看到这一点并且对坚持 Node 不那么特别的人来说,这里有一些不涉及手动提供静态文件的替代方案。

  1. 如果你想坚持使用 Node,但你可以添加一个框架,Express 框架具有提供静态文件和目录的功能。使用 Express 一行或两行代码将允许您提供具有范围请求和 etag 支持的静态文件。这比编写自己的请求处理程序要容易得多。

  2. 如果你可以跳出Node,你可以将Node和Nginx配对在一起。在此配置中,Nginx是一个前端,用于向Node服务器提供静态文件和代理Web请求。事实上,Nginx就是这样设计的。请注意,您可以代理到任意数量的完全不同的服务器,并且

  3. 你也可以在前端使用 Apache 来做到这一点,我把它留给你看看如何。我从来没有做过,所以我所能做的就是引导你学习一个有用的教程。

我确定还有其他配置,但这应该给您一些思考。对于大多数中小型网站,您很难注意到使用一个系统或另一个系统的任何负面影响。如果您正在运行大型网站或服务,那么您最好做好功课,因为还有很多其他选项和因素需要考虑:CDN、其他脚本语言、缓存、可扩展性等。

最新更新