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

时间:2017-05-25 16:11:23

标签: javascript node.js http header

前段时间我在一些流媒体视频教程中发现了这个功能,因此不需要将整个文件加载到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中运行良好,但是,如果您使用Internet Explorer或Edge(新名称,相同的可怜功能支持)请求mp4,它会崩溃服务器。

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

我怀疑是因为the fact that range headers aren't mandatory, and this function requires them

我的问题是,如何修改此功能以避免在range标题未发送时崩溃?我对头文件或视频流不太了解,而且我对理解以块状态读取文件的方式非常不错,所以我真的可以在这个函数上使用一些帮助来解决这三个问题。

我根据评论尝试的内容

到目前为止,基于the answer @AndréDion linked

  

想出来。 IE11确实支持伪流,但它想要   " Accept-Ranges:bytes"标题之前它会打扰请求a   范围,所以服务器需要响应,无论是否   它实际上是发送一个字节范围。我不得不修改我的视频流   这样做的模块。

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

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当然总是按预期发送范围标头,发送一个请求并成功加载视频。我只是不确定从哪里开始。

2 个答案:

答案 0 :(得分:2)

看起来Internet Explorer不仅仅需要Accept-Ranges标头。 This MSDN forum post表示您还需要使用返回If-Range headerETag进行回复。像这样:

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

不幸的是,我现在无法进行任何测试,所以让我知道它是怎么回事,当我有机会时我会进一步调查并编辑我的帖子。

UPDATE:

我接受了进一步的调查,发现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 header的意图是处理某人请求文件的一部分,在服务器上更改,然后他们请求文件的另一部分的情况。 If-Range允许检测这种情况,服务器可以发送整个新文件,而不是破坏所有内容,并将可怜的请求者与此新文件的一部分混淆。
  • 此代码不会检查以确保请求的范围是以字节为单位。如果请求指定任何其他度量单位,则此代码可能会使服务器崩溃。应该检查。
  • 范围标题允许一次指定多个范围。请求多个范围时的响应由标题组成,请求的数据由可指定的拆分字符串分隔。 More info on MDN。这一点是有争议的,你可以说你只能处理生产服务器上的第一部分,只要有更多不会崩溃,但我认为它是规范的一部分。为了完全符合规范,你需要实现它,我厌倦了运行不太符合规范的服务。

最后一点说明:此代码可以在IE和Edge中运行,但我确实注意到由于某种原因,他们最终请求两次FULL文件!这些请求遵循我的设置:

  1. 使用`Range bytes = 0-'请求部分文件,换句话说是完整文件
  2. 使用正常的Range语句
  3. 请求文件的一小部分
  4. 请求没有Range标头的完整文件
  5. 最后一点说明,在我添加了对完整文件进行响应的能力之前,当没有Range标题时,IE会显示200状态IE将显示带有“Decode Error”的视频的黑盒子。如果有人能告诉我原因,那就太棒了!

答案 1 :(得分:0)

据我所知,Viziionary希望坚持使用vanilla Node.js,但是对于其他任何看过这个并且不太关注Node的人来说,这里有一些不涉及手动服务的选择静态文件。

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

  2. 如果你在节点之外走路,你可以pair Node and Nginx together。在此配置中,Nginx是一个前端,为您的节点服务器提供静态文件和代理Web请求。事实上,Nginx was designed to work this way。请注意,您可以代理任意数量的完全不同的服务器和

  3. 你也可以在前端用Apache做这件事,我留给你查询如何。我从来没有这样做过,所以我所能做的就是指导你找一份有用的教程。

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