We are writing a script that reads a large set of JPG files on our server (infinite, since we have another process that keeps writing JPG files to the same directory) and send them to users' browsers as an MJPEG stream at fixed time interval (variable "frameDelay" in code below). This is similar to what an IP camera would do.
We found out the memory usage of this script keeps going up and always ends up being killed by the system (Ubuntu);
We have inspected this seemingly simple script many many times. Therefore I'm posting the code below. Any comments / suggestions are greatly appreciated!
app.get('/stream', function (req, res) {
res.writeHead(200, {
'Content-Type':'multipart/x-mixed-replace;boundary="' + boundary + '"',
'Transfer-Encoding':'none',
'Connection':'keep-alive',
'Expires':'Fri, 01 Jan 1990 00:00:00 GMT',
'Cache-Control':'no-cache, no-store, max-age=0, must-revalidate',
'Pragma':'no-cache'
});
res.write(CRLF + "--" + boundary + CRLF);
setInterval(function () {
if(fileList.length<=1){
fileList = fs.readdirSync(location).sort();
}else{
var fname = fileList.shift();
if(fs.existsSync(location+fname)){
var data = fs.readFileSync(location+fname);
res.write('Content-Type:image/jpeg' + CRLF + 'Content-Length: ' + data.length + CRLF + CRLF);
res.write(data);
res.write(CRLF + '--' + boundary + CRLF);
fs.unlinkSync(location+fname);
}else{
console.log("File doesn't find")
}
}
console.log("new response:" + fname);
}, frameDelay);
});
app.listen(port);
console.log("Server running at port " + port);
To facilitate the troubleshooting process, below is a stand-alone (no 3rd-party lib) test case.
It has exactly the same memory issue (memory usage keeps going up and finally got killed by the OS).
We believe the problem is in the setInterval () loop - maybe those images didn't get deleted from memory after being sent or something (maybe still stored in variable "res"?).
Any feedback / suggestions are greatly appreciated!
var http = require('http');
var fs = require('fs');
var framedelay = 40;
var port = 3200;
var boundary = 'myboundary';
var CR = '\r';
var LF = '\n';
var CRLF = CR + LF;
function writeHttpHeader(res)
{
res.writeHead(200,
{
'Content-Type': 'multipart/x-mixed-replace;boundary="' + boundary + '"',
'Transfer-Encoding': 'none',
'Connection': 'keep-alive',
'Expires': 'Fri, 01 Jan 1990 00:00:00 GMT',
'Cache-Control': 'no-cache, no-store, max-age=0, must-revalidate',
'Pragma': 'no-cache',
});
res.write(CRLF + '--' + boundary + CRLF);
}
function writeJpegFrame(res, filename)
{
fs.readFile('./videos-8081/frames/' + filename, function(err, data)
{
if (err)
{
console.log(err);
}
else
{
res.write('Content-Type:image/jpeg' + CRLF);
res.write('Content-Length:' + data.length + CRLF + CRLF);
res.write(data);
res.write(CRLF + '--' + boundary + CRLF);
console.log('Sent ' + filename);
}
});
}
http.createServer(function(req, res)
{
writeHttpHeader(res)
fs.readdir('./videos-8081/frames', function(err, files)
{
var i = -1;
var sorted_files = files.sort();
setInterval(function()
{
if (++i >= sorted_files.length)
{
i = 0;
}
writeJpegFrame(res, sorted_files[i]);
}, framedelay);
});
}).listen(port);
console.log('Server running at port ' + port);