A while back I found this function in some tutorial for streaming video so that the whole file doesn't need to be loaded into RAM for the file to be served (so you can serve big video files without crashing due to exceeding the memory cap of Node.js - which isn't hard to exceed with a movie-length video file, and increasing memory allocation is just a band-aid solution).
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;
It works fine in Chrome and FF, however, when Internet Explorer, or Edge, if you will, (new name, same pathetic feature support) requests an mp4, it crashes the server.
var positions = range.replace(/bytes=/, "").split("-");
^
TypeError: Cannot read property 'replace' of undefined
I suspect that's because of the fact that range headers aren't mandatory, and this function requires them.
My question is, how can I modify this function to avoid crashing when the range
header isn't sent? I don't know much about headers or video streaming, and I'm pretty iffy on my understanding of reading files in chunks, so I could really use some help on this function which involves all three.
What I've Tried Based on Comments
So far, based on the answer @AndréDion linked:
Figured it out. IE11 does support pseudo streaming but it wants the "Accept-Ranges: bytes" header before it will bother requesting a range, so the server needs to respond with that regardless of whether it is actually sending a byte range. I had to modify my vid-streamer module to do that.
I tried wrapping the handler code in:
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();
}
but nothing seems to happen - the server stops crashing, and continues to work on other browsers, but IE doesn't seem to be loading the video. I was expecting for IE to wait for that response then send another request, this time including a range header, but that doesnt appear to be happening.
Maybe I need to send a different response code than 206 when sending IE the accept-ranges header? I have no idea what it would be, but when I respond with the accept-rages header, IE seems to repeat the request twice (but not with the range header included, which is what I need).
As it stands now, if I run the server with the conditional as shown above, then try to load the video once in IE, then try to load it in Chrome, I get these logs:
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 sends three requests without range headers, Chrome of course always sends the range header as expected, sending one request and successfully loading the video. And I'm just not sure where to go from here.