我怎样才能dynamic加载和stream大的video文件到IE不发送范围头?

前段时间我在一些stream媒体video教程中发现了这个function,这样整个文件就不需要加载到内存中了,这样就可以在没有崩溃的情况下为大video文件提供服务Node.js – 用电影长度的video文件不难超越,增加内存分配只是一个创可贴的解决scheme)。

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中工作正常,但是,如果您愿意,Internet Explorer或Edge(新名称,相同的可怜function支持)请求mp4时,会崩溃服务器。

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

我怀疑这是因为范围标题不是强制性的,而且这个function需要它们 。

我的问题是,如何修改此function,以避免range标题不发送时崩溃? 我对头文件和videostream的了解不多,而且我很了解如何以大块的方式读取文件,所以我真的可以在这个涉及所有三个文件的function上使用一些帮助。


我所试过的基于评论

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

弄清楚了。 IE11确实支持伪stream,但是它希望在“Accept-Ranges:bytes”头之前,它会打扰请求一个范围,所以服务器需要响应,无论它实际上是否发送一个字节范围。 我不得不修改我的vid-streamer模块来做到这一点。

我试着包装处理程序代码:

 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似乎并没有加载video。 我期待IE浏览器等待这个响应,然后发送另一个请求,这次包括一个范围标题,但这似乎并没有发生。

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

现在,如果我运行带有条件的服务器,如上所示,然后尝试在IE中加载video一次,然后尝试在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浏览器当然总是按照预期发送范围标题,发送一个请求并成功加载video。 而我只是不确定该从哪里出发。

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

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

不幸的是,我现在不能做任何testing,所以让我知道它是如何发生的,当我有机会,我会进一步调查和编辑我的post。


更新:

我进行了进一步的调查,发现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允许检测到这种情况,服务器可以发送整个新文件,而不是破坏所有内容,并将不好的请求者与这个新文件的一部分混淆。
  • 此代码不检查以确保请求的范围是以字节为单位。 如果请求指定了任何其他度量单位,则此代码可能会使服务器崩溃。 应该检查。
  • 范围标题允许一次指定多个范围。 请求多个范围时的响应由标题和由可指定的拆分string分隔的请求数据组成。 更多关于MDN的信息 。 这一点是有争议的,你可以说,只要处理生产服务器上的第一部分,只要有更多的东西不会造成任何崩溃,就可以逃脱,但我认为这是规范的一部分。 为了完全符合规范,你需要实现它,我厌倦了运行不符合规范的服务。

最后一个注意事项:这段代码让IE和Edge能够正常工作,但是我注意到,由于某种原因,他们最终请求了两次FULL文件! 请求遵循这样的我的设置:

  1. 部分文件请求的范围字节= 0-,换句话说是完整的文件
  2. 用正常的Range语句请求文件的一小部分
  3. 完整的文件被请求没有范围头

最后的一点是,在我添加完全文件响应的能力之前,当没有Range头IE时,会显示带有“解码错误”的video黑盒。 如果任何人都可以让我知道为什么,那太棒了!

我明白,Viziionary想坚持香草Node.js,但对于谁看到这一点,并不是特别坚持节点,这里有一些替代品,不涉及手动提供静态文件。

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

  2. 如果您在跨越Node之外很好,可以将Node和Nginx配对在一起 。 在这个configuration中,Nginx是一个前端,它为静态文件提供服务,并将Web请求代理到Node服务器。 事实上, Nginx的devise就是这样工作的 。 请注意,您可以代理任何数量的完全不同的服务器和

  3. 你也可以在前端用Apache做这个,我把它留给你看看。 我从来没有做过,所以我所能做的就是引导你走向有用的教程。

我相信还有其他的configuration,但这应该给你一些思考的食物。 对于大多数中小型网站,您很难注意到使用一个系统或其他系统的负面影响。 如果您正在运行大型网站或服务,那么您最好做好功课,因为还有很多其他选项和因素需要考虑:CDN,其他脚本语言,caching,可伸缩性等。