mirror of
https://github.com/vale981/doccam-pi
synced 2025-03-05 09:21:40 -05:00
should_use_redux!
This commit is contained in:
parent
e57624454e
commit
73bbc1db92
18 changed files with 2040 additions and 55 deletions
494
#main.js#
Executable file
494
#main.js#
Executable file
|
@ -0,0 +1,494 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Main Module - Communicates with the Server and Orchestrates the other Moules. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
const sock = require('socket.io-client');
|
||||||
|
const ffmpeg = require('fluent-ffmpeg');
|
||||||
|
const http = require('http');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const WMStrm = require(__dirname + '/lib/memWrite.js');
|
||||||
|
const exec = require('child_process').exec;
|
||||||
|
const execSync = require('child_process').execSync;
|
||||||
|
const spawnP = require('child_process').spawn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Modules
|
||||||
|
* Yet to be initialized.
|
||||||
|
*/
|
||||||
|
const logger = require('src/logger.js');
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Declarations //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Force a Stop.
|
||||||
|
let mustBe = false;
|
||||||
|
|
||||||
|
// Restart the stream after it stopped.
|
||||||
|
let restart = false;
|
||||||
|
|
||||||
|
// Central State Variable
|
||||||
|
// NOTE: Could be done with redux!
|
||||||
|
let status = {
|
||||||
|
status: 0,
|
||||||
|
error: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minor declarations.
|
||||||
|
let config, source, snapSource, stopTimeout;
|
||||||
|
|
||||||
|
let spawn = function() {
|
||||||
|
source = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.camProfile;
|
||||||
|
ffmpeg.setFfmpegPath(config.ffmpegPath);
|
||||||
|
delete cmd;
|
||||||
|
cmd = ffmpeg({
|
||||||
|
source: source,
|
||||||
|
stdoutLines: 20
|
||||||
|
});
|
||||||
|
if (config.customOutputOptions !== "")
|
||||||
|
cmd.outputOptions(config.customOutputOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(','));
|
||||||
|
if (config.customAudioOptions !== "")
|
||||||
|
cmd.outputOptions(config.customAudioOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(','));
|
||||||
|
else
|
||||||
|
cmd.AudioCodec('copy');
|
||||||
|
if (config.customVideoOptions !== "")
|
||||||
|
cmd.outputOptions(config.customVideoOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(','));
|
||||||
|
else
|
||||||
|
cmd.videoCodec('copy');
|
||||||
|
|
||||||
|
cmd.on('start', function(commandLine) {
|
||||||
|
status.running = 0;
|
||||||
|
logger.log(logger.importance[4], 'Spawned Ffmpeg with command: ' + commandLine);
|
||||||
|
})
|
||||||
|
.on('end', function(o, e) {
|
||||||
|
imDead('Normal Stop.', e);
|
||||||
|
})
|
||||||
|
.on('error', function(err, o, e) {
|
||||||
|
if (err.message.indexOf(source) > -1)
|
||||||
|
criticalProblem(0, e, handleDisc, config.camIP, config.camPort);
|
||||||
|
else if (err.message.indexOf(source + 'Input/output error') > -1 || err.message.indexOf('rtmp://a.rtmp.youtube.com/live2/' + config.key) > -1)
|
||||||
|
criticalProblem(1, e, handleDisc, 'a.rtmp.youtube.com/live2/', 1935);
|
||||||
|
else if (err.message.indexOf('spawn') > -1 || err.message.indexOf('niceness') > -1)
|
||||||
|
criticalProblem(2, e, function() {});
|
||||||
|
else if (err.message.indexOf('SIGINT') > -1 || err.message.indexOf('SIGKILL') > -1)
|
||||||
|
imDead('Normal Stop.', e);
|
||||||
|
else
|
||||||
|
imDead(err.message, e);
|
||||||
|
})
|
||||||
|
.outputFormat('flv')
|
||||||
|
.outputOptions(['-bufsize 50000k', '-tune film'])
|
||||||
|
.output('rtmp://a.rtmp.youtube.com/live2/' + config.key);
|
||||||
|
|
||||||
|
status.error = -1;
|
||||||
|
socket.emit('change', {
|
||||||
|
type: 'startStop',
|
||||||
|
change: {
|
||||||
|
running: 0,
|
||||||
|
error: -1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cmd.run();
|
||||||
|
};
|
||||||
|
|
||||||
|
let getSnap = function(cb) {
|
||||||
|
snapSource = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.snapProfile;
|
||||||
|
let picBuff = new WMStrm();
|
||||||
|
recCmd = ffmpeg(snapSource)
|
||||||
|
.on('start', function(commandLine) {
|
||||||
|
logger.log(logger.importance[4], 'Snapshot ' + commandLine);
|
||||||
|
})
|
||||||
|
.on('error', function(err, o, e) {})
|
||||||
|
.outputFormat('mjpeg')
|
||||||
|
.frames(1)
|
||||||
|
.stream(picBuff, {
|
||||||
|
end: true
|
||||||
|
});
|
||||||
|
picBuff.on('finish', function() {
|
||||||
|
try {
|
||||||
|
cb(picBuff.memStore.toString('base64'));
|
||||||
|
delete pickBuff;
|
||||||
|
} catch (e) {
|
||||||
|
cb(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function imDead(why, e = '') {
|
||||||
|
if(stopTimeout) {
|
||||||
|
clearTimeout(stopTimeout);
|
||||||
|
stopTimeout = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
status.running = 1;
|
||||||
|
socket.emit('change', {
|
||||||
|
type: 'startStop',
|
||||||
|
change: {
|
||||||
|
running: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (restart) {
|
||||||
|
spawn();
|
||||||
|
restart = false;
|
||||||
|
}
|
||||||
|
if (!mustBe) {
|
||||||
|
logger.log(logger.importance[2], 'Crash! ' + e);
|
||||||
|
setTimeout(function() {
|
||||||
|
spawn();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
mustBe = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function criticalProblem(err, e, handler, ...args) {
|
||||||
|
if (!mustBe) {
|
||||||
|
setTimeout(function() {
|
||||||
|
status.running = 2;
|
||||||
|
status.error = err;
|
||||||
|
logger.log(logger.importance[3], 'Critical Problem: ' + errors[err] + '\n' + e);
|
||||||
|
socket.emit('change', {
|
||||||
|
type: 'error',
|
||||||
|
change: {
|
||||||
|
running: 2,
|
||||||
|
error: err
|
||||||
|
}
|
||||||
|
});
|
||||||
|
handler(args)
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
mustBe = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDisc(info) {
|
||||||
|
let [host, port] = info;
|
||||||
|
isReachable(host, port, is => {
|
||||||
|
if (is) {
|
||||||
|
spawn();
|
||||||
|
} else {
|
||||||
|
setTimeout(function() {
|
||||||
|
handleDisc(info);
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isReachable(host, port, callback) {
|
||||||
|
http.get({
|
||||||
|
host: host.split('/')[0],
|
||||||
|
port: port
|
||||||
|
}, function(res) {
|
||||||
|
callback(true);
|
||||||
|
}).on("error", function(e) {
|
||||||
|
if (e.message == "socket hang up") {
|
||||||
|
setTimeout(function() {
|
||||||
|
callback(true);
|
||||||
|
}, 1000);
|
||||||
|
} else
|
||||||
|
callback(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopFFMPEG() {
|
||||||
|
cmd.kill('SIGINT');
|
||||||
|
stopTimeout = setTimeout(() => {
|
||||||
|
logger.log(logger.importance[3], "Force Stop!");
|
||||||
|
cmd.kill();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandHandlers = function commandHandlers(command, cb) {
|
||||||
|
var handlers = {
|
||||||
|
startStop: function() {
|
||||||
|
if(restart)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (status.running !== 2)
|
||||||
|
if (status.running === 0) {
|
||||||
|
logger.log(logger.importance[1], "Stop Command!");
|
||||||
|
mustBe = true;
|
||||||
|
stopFFMPEG();
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'message',
|
||||||
|
data: {
|
||||||
|
title: 'Success',
|
||||||
|
type: 'success',
|
||||||
|
text: 'Stopped!'
|
||||||
|
}
|
||||||
|
}, command.sender);
|
||||||
|
} else {
|
||||||
|
logger.log(logger.importance[1], "Start Command!");
|
||||||
|
spawn();
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'message',
|
||||||
|
data: {
|
||||||
|
title: 'Success',
|
||||||
|
type: 'success',
|
||||||
|
text: 'Started!'
|
||||||
|
}
|
||||||
|
}, command.sender);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
snap: function() {
|
||||||
|
getSnap(snap => {
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'snap',
|
||||||
|
data: snap,
|
||||||
|
}, command.sender);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
config: function() {
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'config',
|
||||||
|
data: config,
|
||||||
|
}, command.sender);
|
||||||
|
},
|
||||||
|
changeSettings: function() {
|
||||||
|
for (let set in command.data) {
|
||||||
|
if (typeof config[set] !== 'undefined')
|
||||||
|
config[set] = command.data[set];
|
||||||
|
}
|
||||||
|
let oldConfigured;
|
||||||
|
if (config.configured === true)
|
||||||
|
oldConfigured = true;
|
||||||
|
config.configured = true;
|
||||||
|
fs.writeFile(__dirname + '/config.js',
|
||||||
|
JSON.stringify(config,
|
||||||
|
undefined, 2),
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'message',
|
||||||
|
data: {
|
||||||
|
title: 'Error',
|
||||||
|
type: 'error',
|
||||||
|
text: 'Can\'t save the Settings!\n' + err.message
|
||||||
|
}
|
||||||
|
}, command.sender);
|
||||||
|
} else {
|
||||||
|
restartSSH(() => {
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'message',
|
||||||
|
data: {
|
||||||
|
title: 'Success',
|
||||||
|
type: 'success',
|
||||||
|
text: 'Settings Saved!'
|
||||||
|
}
|
||||||
|
}, command.sender);
|
||||||
|
if (oldConfigured) {
|
||||||
|
socket.emit('change', {
|
||||||
|
type: 'settings',
|
||||||
|
change: {
|
||||||
|
config: command.data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stopFFMPEG();
|
||||||
|
spawn();
|
||||||
|
} else {
|
||||||
|
socket.disconnect();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
restart: function() {
|
||||||
|
if (status.running === 0) {
|
||||||
|
logger.log(logger.importance[1], "Restart Command!");
|
||||||
|
mustBe = true;
|
||||||
|
restart = true;
|
||||||
|
stopFFMPEG();
|
||||||
|
} else {
|
||||||
|
logger.log(logger.importance[1], "Start Command!");
|
||||||
|
spawn();
|
||||||
|
}
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'message',
|
||||||
|
data: {
|
||||||
|
title: 'Success',
|
||||||
|
type: 'success',
|
||||||
|
text: 'Restarted!'
|
||||||
|
}
|
||||||
|
}, command.sender);
|
||||||
|
},
|
||||||
|
restartSSH: function() {
|
||||||
|
restartSSH(() => {
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'message',
|
||||||
|
data: {
|
||||||
|
title: 'Success',
|
||||||
|
type: 'success',
|
||||||
|
text: 'Restarted SSH Tunnels!'
|
||||||
|
}
|
||||||
|
}, command.sender);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getLogs: function() {
|
||||||
|
logger.query({limit: 100}, function (err, data) {
|
||||||
|
data = data.file;
|
||||||
|
if (err) {
|
||||||
|
data = [];
|
||||||
|
} else
|
||||||
|
if (data.length === 0)
|
||||||
|
data = [];
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'logs',
|
||||||
|
data: data
|
||||||
|
}, command.sender);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//call the handler
|
||||||
|
var call = handlers[command.command];
|
||||||
|
if (call)
|
||||||
|
call();
|
||||||
|
}
|
||||||
|
|
||||||
|
function restartSSH(cb) {
|
||||||
|
exec('forever stop SSH-Serv', () => {
|
||||||
|
connectSSH(() => {
|
||||||
|
socket.emit('change', {
|
||||||
|
change: {
|
||||||
|
ssh: {
|
||||||
|
port: status.ssh.port,
|
||||||
|
camForwardPort: status.ssh.camForwardPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKill() {
|
||||||
|
process.stdin.resume();
|
||||||
|
logger.log(logger.importance[0], "Received Shutdown Command");
|
||||||
|
mustBe = true;
|
||||||
|
cmd.kill();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('SIGTERM', function() {
|
||||||
|
handleKill();
|
||||||
|
});
|
||||||
|
|
||||||
|
//let's go
|
||||||
|
function init() {
|
||||||
|
config = readConfig();
|
||||||
|
|
||||||
|
if (config.configured) {
|
||||||
|
socket = sock(config.master + '/pi');
|
||||||
|
initSocket();
|
||||||
|
status.name = config.name;
|
||||||
|
if (!cmd)
|
||||||
|
spawn();
|
||||||
|
} else {
|
||||||
|
socket = sock(config.master + '/pi', {
|
||||||
|
query: "unconfigured=true"
|
||||||
|
});
|
||||||
|
status.running = 2;
|
||||||
|
initSocket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function initSSH(cb) {
|
||||||
|
status.ssh = {
|
||||||
|
// that Could come from the Master server!
|
||||||
|
user: config['ssh-user'],
|
||||||
|
localUser: config['ssh-local-user'],
|
||||||
|
masterPort: config['ssh-port']
|
||||||
|
};
|
||||||
|
|
||||||
|
checkSSH((alive) => {
|
||||||
|
if (alive)
|
||||||
|
cb();
|
||||||
|
else
|
||||||
|
connectSSH(cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectSSH(cb = function() {
|
||||||
|
socket.emit('change', {
|
||||||
|
change: {
|
||||||
|
ssh: {
|
||||||
|
port: status.ssh.port,
|
||||||
|
camForwardPort: status.ssh.camForwardPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}) {
|
||||||
|
socket.emit('getSSHPort', ports => {
|
||||||
|
[status.ssh.port, status.ssh.camForwardPort] = ports;
|
||||||
|
let ssh = exec(`forever start -a --killSignal=SIGINT --uid SSH-Serv sshManager.js ${status.ssh.port} ${status.ssh.camForwardPort} ${config.camPanelPort}`, {
|
||||||
|
detached: true,
|
||||||
|
shell: true,
|
||||||
|
cwd: __dirname
|
||||||
|
});
|
||||||
|
ssh.on('error', (err) => {
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'message',
|
||||||
|
data: {
|
||||||
|
title: 'Error',
|
||||||
|
type: 'error',
|
||||||
|
text: 'Could not start SSH tunnels!'
|
||||||
|
}
|
||||||
|
}, command.sender);
|
||||||
|
});
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkSSH(cb) {
|
||||||
|
let m, pid, alive;
|
||||||
|
let re = /SSH-Serv.*?sshManager\.js\s([0-9]+)\s([0-9]+).*log.*[^STOPPED]+/g;
|
||||||
|
exec('forever list', (error, stdout, stderr) => {
|
||||||
|
if (error)
|
||||||
|
throw error;
|
||||||
|
let alive = false;
|
||||||
|
while ((m = re.exec(stdout)) !== null) {
|
||||||
|
if (m.index === re.lastIndex) {
|
||||||
|
re.lastIndex++;
|
||||||
|
}
|
||||||
|
if (alive) {
|
||||||
|
exec('forever stop SSH-Serv');
|
||||||
|
cb(false)
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
[, status.ssh.port, status.ssh.camForwardPort] = m;
|
||||||
|
alive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cb(alive);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initSocket() {
|
||||||
|
socket.on('connect', function() {
|
||||||
|
logger.log(logger.importance[0], 'Connected to Master: ' + config.master + '.');
|
||||||
|
if (config['ssh-user'])
|
||||||
|
initSSH(err => {
|
||||||
|
if (err)
|
||||||
|
throw err;
|
||||||
|
socket.emit('meta', status);
|
||||||
|
});
|
||||||
|
else {
|
||||||
|
socket.emit('meta', status);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', function() {
|
||||||
|
socket.disconnect();
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('command', (command, cb) => {
|
||||||
|
commandHandlers(command, cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function readConfig() {
|
||||||
|
return JSON.parse(fs.readFileSync(__dirname + '/config.js'));
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
1
.#main.js
Symbolic link
1
.#main.js
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
hiro@ArLeenUX.4902:1492242088
|
9
.idea/codeStyleSettings.xml
generated
Normal file
9
.idea/codeStyleSettings.xml
generated
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectCodeStyleSettingsManager">
|
||||||
|
<option name="PER_PROJECT_SETTINGS">
|
||||||
|
<value />
|
||||||
|
</option>
|
||||||
|
<option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" />
|
||||||
|
</component>
|
||||||
|
</project>
|
12
.idea/doccam-pi.iml
generated
Normal file
12
.idea/doccam-pi.iml
generated
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/doccam-pi.iml" filepath="$PROJECT_DIR$/.idea/doccam-pi.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
295
.idea/workspace.xml
generated
Normal file
295
.idea/workspace.xml
generated
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="BookmarkManager">
|
||||||
|
<bookmark url="file://$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="ChangeListManager">
|
||||||
|
<list default="true" id="969ea136-ca9e-4b96-a835-7f68ccc264a6" name="Default" comment="">
|
||||||
|
<change type="MODIFICATION" beforePath="$PROJECT_DIR$/main.js" afterPath="$PROJECT_DIR$/main.js" />
|
||||||
|
</list>
|
||||||
|
<ignored path="$PROJECT_DIR$/.tmp/" />
|
||||||
|
<ignored path="$PROJECT_DIR$/temp/" />
|
||||||
|
<ignored path="$PROJECT_DIR$/tmp/" />
|
||||||
|
<option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
|
||||||
|
<option name="TRACKING_ENABLED" value="true" />
|
||||||
|
<option name="SHOW_DIALOG" value="false" />
|
||||||
|
<option name="HIGHLIGHT_CONFLICTS" value="true" />
|
||||||
|
<option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
|
||||||
|
<option name="LAST_RESOLUTION" value="IGNORE" />
|
||||||
|
</component>
|
||||||
|
<component name="ExecutionTargetManager" SELECTED_TARGET="default_target" />
|
||||||
|
<component name="FavoritesManager">
|
||||||
|
<favorites_list name="doccam-pi" />
|
||||||
|
</component>
|
||||||
|
<component name="FileEditorManager">
|
||||||
|
<leaf SIDE_TABS_SIZE_LIMIT_KEY="300">
|
||||||
|
<file leaf-file-name="main.js" pinned="false" current-in-tab="true">
|
||||||
|
<entry file="file://$PROJECT_DIR$/main.js">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="36">
|
||||||
|
<caret line="5" column="21" lean-forward="true" selection-start-line="5" selection-start-column="21" selection-end-line="5" selection-end-column="21" />
|
||||||
|
<folding />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</file>
|
||||||
|
</leaf>
|
||||||
|
</component>
|
||||||
|
<component name="Git.Settings">
|
||||||
|
<option name="RECENT_GIT_ROOT_PATH" value="$PROJECT_DIR$" />
|
||||||
|
</component>
|
||||||
|
<component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
|
||||||
|
<component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER">
|
||||||
|
<package-json value="$PROJECT_DIR$/package.json" />
|
||||||
|
</component>
|
||||||
|
<component name="JsGulpfileManager">
|
||||||
|
<detection-done>true</detection-done>
|
||||||
|
<sorting>DEFINITION_ORDER</sorting>
|
||||||
|
</component>
|
||||||
|
<component name="NodeModulesDirectoryManager">
|
||||||
|
<handled-path value="$PROJECT_DIR$/node_modules" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectFrameBounds">
|
||||||
|
<option name="width" value="1920" />
|
||||||
|
<option name="height" value="1044" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectView">
|
||||||
|
<navigator currentView="ProjectPane" proportions="" version="1">
|
||||||
|
<flattenPackages />
|
||||||
|
<showMembers />
|
||||||
|
<showModules />
|
||||||
|
<showLibraryContents />
|
||||||
|
<hideEmptyPackages />
|
||||||
|
<abbreviatePackageNames />
|
||||||
|
<autoscrollToSource />
|
||||||
|
<autoscrollFromSource />
|
||||||
|
<sortByType />
|
||||||
|
<manualOrder />
|
||||||
|
<foldersAlwaysOnTop value="true" />
|
||||||
|
</navigator>
|
||||||
|
<panes>
|
||||||
|
<pane id="ProjectPane">
|
||||||
|
<subPane>
|
||||||
|
<PATH>
|
||||||
|
<PATH_ELEMENT>
|
||||||
|
<option name="myItemId" value="doccam-pi" />
|
||||||
|
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
|
||||||
|
</PATH_ELEMENT>
|
||||||
|
<PATH_ELEMENT>
|
||||||
|
<option name="myItemId" value="doccam-pi" />
|
||||||
|
<option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode" />
|
||||||
|
</PATH_ELEMENT>
|
||||||
|
</PATH>
|
||||||
|
</subPane>
|
||||||
|
</pane>
|
||||||
|
<pane id="Scope" />
|
||||||
|
<pane id="Scratches" />
|
||||||
|
</panes>
|
||||||
|
</component>
|
||||||
|
<component name="PropertiesComponent">
|
||||||
|
<property name="WebServerToolWindowFactoryState" value="false" />
|
||||||
|
<property name="last_opened_file_path" value="$PROJECT_DIR$" />
|
||||||
|
<property name="HbShouldOpenHtmlAsHb" value="" />
|
||||||
|
<property name="nodejs_interpreter_path" value="/usr/bin/node" />
|
||||||
|
<property name="settings.editor.selected.configurable" value="preferences.pluginManager" />
|
||||||
|
</component>
|
||||||
|
<component name="RunDashboard">
|
||||||
|
<option name="ruleStates">
|
||||||
|
<list>
|
||||||
|
<RuleState>
|
||||||
|
<option name="name" value="ConfigurationTypeDashboardGroupingRule" />
|
||||||
|
</RuleState>
|
||||||
|
<RuleState>
|
||||||
|
<option name="name" value="StatusDashboardGroupingRule" />
|
||||||
|
</RuleState>
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
<component name="RunManager" selected="Node.js.main.js">
|
||||||
|
<configuration default="true" type="DartCommandLineRunConfigurationType" factoryName="Dart Command Line Application">
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="DartTestRunConfigurationType" factoryName="Dart Test">
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="JavaScriptTestRunnerJest" factoryName="Jest">
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<working-dir value="" />
|
||||||
|
<envs />
|
||||||
|
<scope-kind value="ALL" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="JavaScriptTestRunnerKarma" factoryName="Karma">
|
||||||
|
<config-file value="" />
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<envs />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="JavaScriptTestRunnerProtractor" factoryName="Protractor">
|
||||||
|
<config-file value="" />
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<envs />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="JavascriptDebugType" factoryName="JavaScript Debug">
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="NodeJSConfigurationType" factoryName="Node.js" path-to-node="project" working-dir="">
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="cucumber.js" factoryName="Cucumber.js">
|
||||||
|
<option name="cucumberJsArguments" value="" />
|
||||||
|
<option name="executablePath" />
|
||||||
|
<option name="filePath" />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="js.build_tools.gulp" factoryName="Gulp.js">
|
||||||
|
<node-interpreter>project</node-interpreter>
|
||||||
|
<node-options />
|
||||||
|
<gulpfile />
|
||||||
|
<tasks />
|
||||||
|
<arguments />
|
||||||
|
<envs />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="js.build_tools.npm" factoryName="npm">
|
||||||
|
<command value="run" />
|
||||||
|
<scripts />
|
||||||
|
<node-interpreter value="project" />
|
||||||
|
<envs />
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="true" type="mocha-javascript-test-runner" factoryName="Mocha">
|
||||||
|
<node-interpreter>project</node-interpreter>
|
||||||
|
<node-options />
|
||||||
|
<working-directory />
|
||||||
|
<pass-parent-env>true</pass-parent-env>
|
||||||
|
<envs />
|
||||||
|
<ui />
|
||||||
|
<extra-mocha-options />
|
||||||
|
<test-kind>DIRECTORY</test-kind>
|
||||||
|
<test-directory />
|
||||||
|
<recursive>false</recursive>
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<configuration default="false" name="main.js" type="NodeJSConfigurationType" factoryName="Node.js" path-to-node="project" path-to-js-file="main.js" working-dir="$PROJECT_DIR$">
|
||||||
|
<EXTENSION ID="com.jetbrains.nodejs.remote.docker.NodeJSDockerRunConfigurationExtension">
|
||||||
|
<option name="envVars">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="extraHosts">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="links">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="networkDisabled" value="false" />
|
||||||
|
<option name="networkMode" value="bridge" />
|
||||||
|
<option name="portBindings">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
<option name="publishAllPorts" value="false" />
|
||||||
|
<option name="version" value="1" />
|
||||||
|
<option name="volumeBindings">
|
||||||
|
<list />
|
||||||
|
</option>
|
||||||
|
</EXTENSION>
|
||||||
|
<method />
|
||||||
|
</configuration>
|
||||||
|
<list size="1">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="Node.js.main.js" />
|
||||||
|
</list>
|
||||||
|
</component>
|
||||||
|
<component name="ShelveChangesManager" show_recycled="false">
|
||||||
|
<option name="remove_strategy" value="false" />
|
||||||
|
</component>
|
||||||
|
<component name="TaskManager">
|
||||||
|
<task active="true" id="Default" summary="Default task">
|
||||||
|
<changelist id="969ea136-ca9e-4b96-a835-7f68ccc264a6" name="Default" comment="" />
|
||||||
|
<created>1492326299503</created>
|
||||||
|
<option name="number" value="Default" />
|
||||||
|
<option name="presentableId" value="Default" />
|
||||||
|
<updated>1492326299503</updated>
|
||||||
|
<workItem from="1492326301278" duration="521000" />
|
||||||
|
<workItem from="1492326839089" duration="529000" />
|
||||||
|
</task>
|
||||||
|
<servers />
|
||||||
|
</component>
|
||||||
|
<component name="TimeTrackingManager">
|
||||||
|
<option name="totallyTimeSpent" value="1050000" />
|
||||||
|
</component>
|
||||||
|
<component name="TodoView">
|
||||||
|
<todo-panel id="selected-file">
|
||||||
|
<is-autoscroll-to-source value="true" />
|
||||||
|
</todo-panel>
|
||||||
|
<todo-panel id="all">
|
||||||
|
<are-packages-shown value="true" />
|
||||||
|
<is-autoscroll-to-source value="true" />
|
||||||
|
</todo-panel>
|
||||||
|
</component>
|
||||||
|
<component name="ToolWindowManager">
|
||||||
|
<frame x="0" y="0" width="1920" height="1044" extended-state="6" />
|
||||||
|
<layout>
|
||||||
|
<window_info id="Project" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.18125" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
|
||||||
|
<window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" />
|
||||||
|
<window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="npm" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
|
||||||
|
<window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
|
||||||
|
<window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
|
||||||
|
<window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
|
||||||
|
<window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
|
||||||
|
</layout>
|
||||||
|
</component>
|
||||||
|
<component name="TypeScriptGeneratedFilesManager">
|
||||||
|
<option name="processedProjectFiles" value="true" />
|
||||||
|
</component>
|
||||||
|
<component name="VcsContentAnnotationSettings">
|
||||||
|
<option name="myLimit" value="2678400000" />
|
||||||
|
</component>
|
||||||
|
<component name="XDebuggerManager">
|
||||||
|
<breakpoint-manager />
|
||||||
|
<watches-manager />
|
||||||
|
</component>
|
||||||
|
<component name="editorHistoryManager">
|
||||||
|
<entry file="file://$PROJECT_DIR$/main.js">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="0">
|
||||||
|
<caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
|
||||||
|
<folding />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
<entry file="file://$PROJECT_DIR$/main.js">
|
||||||
|
<provider selected="true" editor-type-id="text-editor">
|
||||||
|
<state relative-caret-position="36">
|
||||||
|
<caret line="5" column="21" lean-forward="true" selection-start-line="5" selection-start-column="21" selection-end-line="5" selection-end-column="21" />
|
||||||
|
<folding />
|
||||||
|
</state>
|
||||||
|
</provider>
|
||||||
|
</entry>
|
||||||
|
</component>
|
||||||
|
<component name="masterDetails">
|
||||||
|
<states>
|
||||||
|
<state key="ScopeChooserConfigurable.UI">
|
||||||
|
<settings>
|
||||||
|
<splitter-proportions>
|
||||||
|
<option name="proportions">
|
||||||
|
<list>
|
||||||
|
<option value="0.2" />
|
||||||
|
</list>
|
||||||
|
</option>
|
||||||
|
</splitter-proportions>
|
||||||
|
</settings>
|
||||||
|
</state>
|
||||||
|
</states>
|
||||||
|
</component>
|
||||||
|
</project>
|
1
.tern-port
Normal file
1
.tern-port
Normal file
|
@ -0,0 +1 @@
|
||||||
|
36411
|
7
.tern-project
Normal file
7
.tern-project
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"plugins": {
|
||||||
|
"node": {},
|
||||||
|
"lint": {},
|
||||||
|
"node-extension": {}
|
||||||
|
}
|
||||||
|
}
|
89
main.js
89
main.js
|
@ -1,3 +1,7 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Main Module - Communicates with the Server and Orchestrates the other Moules. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
const sock = require('socket.io-client');
|
const sock = require('socket.io-client');
|
||||||
const ffmpeg = require('fluent-ffmpeg');
|
const ffmpeg = require('fluent-ffmpeg');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
|
@ -8,56 +12,31 @@ const exec = require('child_process').exec;
|
||||||
const execSync = require('child_process').execSync;
|
const execSync = require('child_process').execSync;
|
||||||
const spawnP = require('child_process').spawn;
|
const spawnP = require('child_process').spawn;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom Modules
|
||||||
|
* Yet to be initialized.
|
||||||
|
*/
|
||||||
|
const logger = require('src/logger.js');
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Declarations //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Force a Stop.
|
||||||
let mustBe = false;
|
let mustBe = false;
|
||||||
|
|
||||||
|
// Restart the stream after it stopped.
|
||||||
let restart = false;
|
let restart = false;
|
||||||
let config, source, snapSource, stopTimeout;
|
|
||||||
|
|
||||||
const importance = ['normal', 'info', 'warning', 'danger', 'success'];
|
// Central State Variable
|
||||||
var customLevels = {
|
// NOTE: Could be done with redux!
|
||||||
levels: {
|
|
||||||
normal: 0,
|
|
||||||
info: 1,
|
|
||||||
warning: 2,
|
|
||||||
danger: 3,
|
|
||||||
success: 4
|
|
||||||
},
|
|
||||||
colors: {
|
|
||||||
normal: 'white',
|
|
||||||
info: 'blue',
|
|
||||||
warning: 'orange',
|
|
||||||
danger: 'red',
|
|
||||||
success: 'green'
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let winston = require('winston');
|
|
||||||
let logger = new(winston.Logger)({
|
|
||||||
levels: customLevels.levels,
|
|
||||||
transports: [
|
|
||||||
new(winston.transports.Console)({
|
|
||||||
level: 'success'
|
|
||||||
}),
|
|
||||||
new(winston.transports.File)({
|
|
||||||
filename: __dirname + '/process.log',
|
|
||||||
colorize: true,
|
|
||||||
timestamp: true,
|
|
||||||
level: 'success',
|
|
||||||
json: true,
|
|
||||||
maxsize: 500000,
|
|
||||||
maxFiles: 10
|
|
||||||
})
|
|
||||||
]
|
|
||||||
});
|
|
||||||
winston.addColors(customLevels.colors);
|
|
||||||
|
|
||||||
let dir = '/home';
|
|
||||||
let status = {
|
let status = {
|
||||||
status: 0,
|
status: 0,
|
||||||
error: -1
|
error: -1
|
||||||
}
|
}
|
||||||
|
|
||||||
let errors = ['Camera Disconnected', 'YoutTube Disconnected', 'Wrong ffmpeg executable.'];
|
// Minor declarations.
|
||||||
let cmd;
|
let config, source, snapSource, stopTimeout;
|
||||||
|
|
||||||
let spawn = function() {
|
let spawn = function() {
|
||||||
source = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.camProfile;
|
source = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.camProfile;
|
||||||
|
@ -80,7 +59,7 @@ let spawn = function() {
|
||||||
|
|
||||||
cmd.on('start', function(commandLine) {
|
cmd.on('start', function(commandLine) {
|
||||||
status.running = 0;
|
status.running = 0;
|
||||||
logger.log(importance[4], 'Spawned Ffmpeg with command: ' + commandLine);
|
logger.log(logger.importance[4], 'Spawned Ffmpeg with command: ' + commandLine);
|
||||||
})
|
})
|
||||||
.on('end', function(o, e) {
|
.on('end', function(o, e) {
|
||||||
imDead('Normal Stop.', e);
|
imDead('Normal Stop.', e);
|
||||||
|
@ -110,14 +89,14 @@ let spawn = function() {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
cmd.run();
|
cmd.run();
|
||||||
}
|
};
|
||||||
|
|
||||||
let getSnap = function(cb) {
|
let getSnap = function(cb) {
|
||||||
snapSource = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.snapProfile;
|
snapSource = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.snapProfile;
|
||||||
let picBuff = new WMStrm();
|
let picBuff = new WMStrm();
|
||||||
recCmd = ffmpeg(snapSource)
|
recCmd = ffmpeg(snapSource)
|
||||||
.on('start', function(commandLine) {
|
.on('start', function(commandLine) {
|
||||||
logger.log(importance[4], 'Snapshot ' + commandLine);
|
logger.log(logger.importance[4], 'Snapshot ' + commandLine);
|
||||||
})
|
})
|
||||||
.on('error', function(err, o, e) {})
|
.on('error', function(err, o, e) {})
|
||||||
.outputFormat('mjpeg')
|
.outputFormat('mjpeg')
|
||||||
|
@ -153,7 +132,7 @@ function imDead(why, e = '') {
|
||||||
restart = false;
|
restart = false;
|
||||||
}
|
}
|
||||||
if (!mustBe) {
|
if (!mustBe) {
|
||||||
logger.log(importance[2], 'Crash! ' + e);
|
logger.log(logger.importance[2], 'Crash! ' + e);
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
spawn();
|
spawn();
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
@ -166,7 +145,7 @@ function criticalProblem(err, e, handler, ...args) {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
status.running = 2;
|
status.running = 2;
|
||||||
status.error = err;
|
status.error = err;
|
||||||
logger.log(importance[3], 'Critical Problem: ' + errors[err] + '\n' + e);
|
logger.log(logger.importance[3], 'Critical Problem: ' + errors[err] + '\n' + e);
|
||||||
socket.emit('change', {
|
socket.emit('change', {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
change: {
|
change: {
|
||||||
|
@ -187,7 +166,7 @@ function handleDisc(info) {
|
||||||
spawn();
|
spawn();
|
||||||
} else {
|
} else {
|
||||||
setTimeout(function() {
|
setTimeout(function() {
|
||||||
handleDisc(info)
|
handleDisc(info);
|
||||||
}, 10000);
|
}, 10000);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -212,7 +191,7 @@ function isReachable(host, port, callback) {
|
||||||
function stopFFMPEG() {
|
function stopFFMPEG() {
|
||||||
cmd.kill('SIGINT');
|
cmd.kill('SIGINT');
|
||||||
stopTimeout = setTimeout(() => {
|
stopTimeout = setTimeout(() => {
|
||||||
logger.log(importance[3], "Force Stop!");
|
logger.log(logger.importance[3], "Force Stop!");
|
||||||
cmd.kill();
|
cmd.kill();
|
||||||
}, 3000);
|
}, 3000);
|
||||||
}
|
}
|
||||||
|
@ -225,7 +204,7 @@ var commandHandlers = function commandHandlers(command, cb) {
|
||||||
|
|
||||||
if (status.running !== 2)
|
if (status.running !== 2)
|
||||||
if (status.running === 0) {
|
if (status.running === 0) {
|
||||||
logger.log(importance[1], "Stop Command!");
|
logger.log(logger.importance[1], "Stop Command!");
|
||||||
mustBe = true;
|
mustBe = true;
|
||||||
stopFFMPEG();
|
stopFFMPEG();
|
||||||
socket.emit('data', {
|
socket.emit('data', {
|
||||||
|
@ -237,7 +216,7 @@ var commandHandlers = function commandHandlers(command, cb) {
|
||||||
}
|
}
|
||||||
}, command.sender);
|
}, command.sender);
|
||||||
} else {
|
} else {
|
||||||
logger.log(importance[1], "Start Command!");
|
logger.log(logger.importance[1], "Start Command!");
|
||||||
spawn();
|
spawn();
|
||||||
socket.emit('data', {
|
socket.emit('data', {
|
||||||
type: 'message',
|
type: 'message',
|
||||||
|
@ -314,12 +293,12 @@ var commandHandlers = function commandHandlers(command, cb) {
|
||||||
},
|
},
|
||||||
restart: function() {
|
restart: function() {
|
||||||
if (status.running === 0) {
|
if (status.running === 0) {
|
||||||
logger.log(importance[1], "Restart Command!");
|
logger.log(logger.importance[1], "Restart Command!");
|
||||||
mustBe = true;
|
mustBe = true;
|
||||||
restart = true;
|
restart = true;
|
||||||
stopFFMPEG();
|
stopFFMPEG();
|
||||||
} else {
|
} else {
|
||||||
logger.log(importance[1], "Start Command!");
|
logger.log(logger.importance[1], "Start Command!");
|
||||||
spawn();
|
spawn();
|
||||||
}
|
}
|
||||||
socket.emit('data', {
|
socket.emit('data', {
|
||||||
|
@ -383,7 +362,7 @@ function restartSSH(cb) {
|
||||||
|
|
||||||
function handleKill() {
|
function handleKill() {
|
||||||
process.stdin.resume();
|
process.stdin.resume();
|
||||||
logger.log(importance[0], "Received Shutdown Command");
|
logger.log(logger.importance[0], "Received Shutdown Command");
|
||||||
mustBe = true;
|
mustBe = true;
|
||||||
cmd.kill();
|
cmd.kill();
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
|
@ -486,7 +465,7 @@ function checkSSH(cb) {
|
||||||
|
|
||||||
function initSocket() {
|
function initSocket() {
|
||||||
socket.on('connect', function() {
|
socket.on('connect', function() {
|
||||||
logger.log(importance[0], 'Connected to Master: ' + config.master + '.');
|
logger.log(logger.importance[0], 'Connected to Master: ' + config.master + '.');
|
||||||
if (config['ssh-user'])
|
if (config['ssh-user'])
|
||||||
initSSH(err => {
|
initSSH(err => {
|
||||||
if (err)
|
if (err)
|
||||||
|
|
488
main.js.orig
Executable file
488
main.js.orig
Executable file
|
@ -0,0 +1,488 @@
|
||||||
|
const sock = require('socket.io-client');
|
||||||
|
const ffmpeg = require('fluent-ffmpeg');
|
||||||
|
const http = require('http');
|
||||||
|
const path = require('path');
|
||||||
|
const fs = require('fs');
|
||||||
|
const WMStrm = require(__dirname + '/lib/memWrite.js');
|
||||||
|
const exec = require('child_process').exec;
|
||||||
|
const execSync = require('child_process').execSync;
|
||||||
|
const spawnP = require('child_process').spawn;
|
||||||
|
|
||||||
|
|
||||||
|
const logger = require('src/logger.js');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global Variables
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Force a Stop.
|
||||||
|
let mustBe = false;
|
||||||
|
|
||||||
|
// Restart the stream after it stopped.
|
||||||
|
let restart = false;
|
||||||
|
|
||||||
|
// Central State Variable
|
||||||
|
// NOTE: Could be done with redux!
|
||||||
|
let status = {
|
||||||
|
status: 0,
|
||||||
|
error: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minor declarations.
|
||||||
|
let config, source, snapSource, stopTimeout;
|
||||||
|
|
||||||
|
|
||||||
|
let spawn = function() {
|
||||||
|
source = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.camProfile;
|
||||||
|
ffmpeg.setFfmpegPath(config.ffmpegPath);
|
||||||
|
delete cmd;
|
||||||
|
cmd = ffmpeg({
|
||||||
|
source: source,
|
||||||
|
stdoutLines: 20
|
||||||
|
});
|
||||||
|
if (config.customOutputOptions !== "")
|
||||||
|
cmd.outputOptions(config.customOutputOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(','));
|
||||||
|
if (config.customAudioOptions !== "")
|
||||||
|
cmd.outputOptions(config.customAudioOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(','));
|
||||||
|
else
|
||||||
|
cmd.AudioCodec('copy');
|
||||||
|
if (config.customVideoOptions !== "")
|
||||||
|
cmd.outputOptions(config.customVideoOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(','));
|
||||||
|
else
|
||||||
|
cmd.videoCodec('copy');
|
||||||
|
|
||||||
|
cmd.on('start', function(commandLine) {
|
||||||
|
status.running = 0;
|
||||||
|
logger.log(logger.importance[4], 'Spawned Ffmpeg with command: ' + commandLine);
|
||||||
|
})
|
||||||
|
.on('end', function(o, e) {
|
||||||
|
imDead('Normal Stop.', e);
|
||||||
|
})
|
||||||
|
.on('error', function(err, o, e) {
|
||||||
|
if (err.message.indexOf(source) > -1)
|
||||||
|
criticalProblem(0, e, handleDisc, config.camIP, config.camPort);
|
||||||
|
else if (err.message.indexOf(source + 'Input/output error') > -1 || err.message.indexOf('rtmp://a.rtmp.youtube.com/live2/' + config.key) > -1)
|
||||||
|
criticalProblem(1, e, handleDisc, 'a.rtmp.youtube.com/live2/', 1935);
|
||||||
|
else if (err.message.indexOf('spawn') > -1 || err.message.indexOf('niceness') > -1)
|
||||||
|
criticalProblem(2, e, function() {});
|
||||||
|
else if (err.message.indexOf('SIGINT') > -1 || err.message.indexOf('SIGKILL') > -1)
|
||||||
|
imDead('Normal Stop.', e);
|
||||||
|
else
|
||||||
|
imDead(err.message, e);
|
||||||
|
})
|
||||||
|
.outputFormat('flv')
|
||||||
|
.outputOptions(['-bufsize 50000k', '-tune film'])
|
||||||
|
.output('rtmp://a.rtmp.youtube.com/live2/' + config.key);
|
||||||
|
|
||||||
|
status.error = -1;
|
||||||
|
socket.emit('change', {
|
||||||
|
type: 'startStop',
|
||||||
|
change: {
|
||||||
|
running: 0,
|
||||||
|
error: -1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cmd.run();
|
||||||
|
};
|
||||||
|
|
||||||
|
let getSnap = function(cb) {
|
||||||
|
snapSource = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.snapProfile;
|
||||||
|
let picBuff = new WMStrm();
|
||||||
|
recCmd = ffmpeg(snapSource)
|
||||||
|
.on('start', function(commandLine) {
|
||||||
|
logger.log(logger.importance[4], 'Snapshot ' + commandLine);
|
||||||
|
})
|
||||||
|
.on('error', function(err, o, e) {})
|
||||||
|
.outputFormat('mjpeg')
|
||||||
|
.frames(1)
|
||||||
|
.stream(picBuff, {
|
||||||
|
end: true
|
||||||
|
});
|
||||||
|
picBuff.on('finish', function() {
|
||||||
|
try {
|
||||||
|
cb(picBuff.memStore.toString('base64'));
|
||||||
|
delete pickBuff;
|
||||||
|
} catch (e) {
|
||||||
|
cb(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function imDead(why, e = '') {
|
||||||
|
if(stopTimeout) {
|
||||||
|
clearTimeout(stopTimeout);
|
||||||
|
stopTimeout = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
status.running = 1;
|
||||||
|
socket.emit('change', {
|
||||||
|
type: 'startStop',
|
||||||
|
change: {
|
||||||
|
running: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (restart) {
|
||||||
|
spawn();
|
||||||
|
restart = false;
|
||||||
|
}
|
||||||
|
if (!mustBe) {
|
||||||
|
logger.log(logger.importance[2], 'Crash! ' + e);
|
||||||
|
setTimeout(function() {
|
||||||
|
spawn();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
mustBe = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function criticalProblem(err, e, handler, ...args) {
|
||||||
|
if (!mustBe) {
|
||||||
|
setTimeout(function() {
|
||||||
|
status.running = 2;
|
||||||
|
status.error = err;
|
||||||
|
logger.log(logger.importance[3], 'Critical Problem: ' + errors[err] + '\n' + e);
|
||||||
|
socket.emit('change', {
|
||||||
|
type: 'error',
|
||||||
|
change: {
|
||||||
|
running: 2,
|
||||||
|
error: err
|
||||||
|
}
|
||||||
|
});
|
||||||
|
handler(args)
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
mustBe = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDisc(info) {
|
||||||
|
let [host, port] = info;
|
||||||
|
isReachable(host, port, is => {
|
||||||
|
if (is) {
|
||||||
|
spawn();
|
||||||
|
} else {
|
||||||
|
setTimeout(function() {
|
||||||
|
handleDisc(info);
|
||||||
|
}, 10000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function isReachable(host, port, callback) {
|
||||||
|
http.get({
|
||||||
|
host: host.split('/')[0],
|
||||||
|
port: port
|
||||||
|
}, function(res) {
|
||||||
|
callback(true);
|
||||||
|
}).on("error", function(e) {
|
||||||
|
if (e.message == "socket hang up") {
|
||||||
|
setTimeout(function() {
|
||||||
|
callback(true);
|
||||||
|
}, 1000);
|
||||||
|
} else
|
||||||
|
callback(false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopFFMPEG() {
|
||||||
|
cmd.kill('SIGINT');
|
||||||
|
stopTimeout = setTimeout(() => {
|
||||||
|
logger.log(logger.importance[3], "Force Stop!");
|
||||||
|
cmd.kill();
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
|
var commandHandlers = function commandHandlers(command, cb) {
|
||||||
|
var handlers = {
|
||||||
|
startStop: function() {
|
||||||
|
if(restart)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (status.running !== 2)
|
||||||
|
if (status.running === 0) {
|
||||||
|
logger.log(logger.importance[1], "Stop Command!");
|
||||||
|
mustBe = true;
|
||||||
|
stopFFMPEG();
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'message',
|
||||||
|
data: {
|
||||||
|
title: 'Success',
|
||||||
|
type: 'success',
|
||||||
|
text: 'Stopped!'
|
||||||
|
}
|
||||||
|
}, command.sender);
|
||||||
|
} else {
|
||||||
|
logger.log(logger.importance[1], "Start Command!");
|
||||||
|
spawn();
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'message',
|
||||||
|
data: {
|
||||||
|
title: 'Success',
|
||||||
|
type: 'success',
|
||||||
|
text: 'Started!'
|
||||||
|
}
|
||||||
|
}, command.sender);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
snap: function() {
|
||||||
|
getSnap(snap => {
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'snap',
|
||||||
|
data: snap,
|
||||||
|
}, command.sender);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
config: function() {
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'config',
|
||||||
|
data: config,
|
||||||
|
}, command.sender);
|
||||||
|
},
|
||||||
|
changeSettings: function() {
|
||||||
|
for (let set in command.data) {
|
||||||
|
if (typeof config[set] !== 'undefined')
|
||||||
|
config[set] = command.data[set];
|
||||||
|
}
|
||||||
|
let oldConfigured;
|
||||||
|
if (config.configured === true)
|
||||||
|
oldConfigured = true;
|
||||||
|
config.configured = true;
|
||||||
|
fs.writeFile(__dirname + '/config.js',
|
||||||
|
JSON.stringify(config,
|
||||||
|
undefined, 2),
|
||||||
|
function(err) {
|
||||||
|
if (err) {
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'message',
|
||||||
|
data: {
|
||||||
|
title: 'Error',
|
||||||
|
type: 'error',
|
||||||
|
text: 'Can\'t save the Settings!\n' + err.message
|
||||||
|
}
|
||||||
|
}, command.sender);
|
||||||
|
} else {
|
||||||
|
restartSSH(() => {
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'message',
|
||||||
|
data: {
|
||||||
|
title: 'Success',
|
||||||
|
type: 'success',
|
||||||
|
text: 'Settings Saved!'
|
||||||
|
}
|
||||||
|
}, command.sender);
|
||||||
|
if (oldConfigured) {
|
||||||
|
socket.emit('change', {
|
||||||
|
type: 'settings',
|
||||||
|
change: {
|
||||||
|
config: command.data
|
||||||
|
}
|
||||||
|
});
|
||||||
|
stopFFMPEG();
|
||||||
|
spawn();
|
||||||
|
} else {
|
||||||
|
socket.disconnect();
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
restart: function() {
|
||||||
|
if (status.running === 0) {
|
||||||
|
logger.log(logger.importance[1], "Restart Command!");
|
||||||
|
mustBe = true;
|
||||||
|
restart = true;
|
||||||
|
stopFFMPEG();
|
||||||
|
} else {
|
||||||
|
logger.log(logger.importance[1], "Start Command!");
|
||||||
|
spawn();
|
||||||
|
}
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'message',
|
||||||
|
data: {
|
||||||
|
title: 'Success',
|
||||||
|
type: 'success',
|
||||||
|
text: 'Restarted!'
|
||||||
|
}
|
||||||
|
}, command.sender);
|
||||||
|
},
|
||||||
|
restartSSH: function() {
|
||||||
|
restartSSH(() => {
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'message',
|
||||||
|
data: {
|
||||||
|
title: 'Success',
|
||||||
|
type: 'success',
|
||||||
|
text: 'Restarted SSH Tunnels!'
|
||||||
|
}
|
||||||
|
}, command.sender);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
getLogs: function() {
|
||||||
|
logger.query({limit: 100}, function (err, data) {
|
||||||
|
data = data.file;
|
||||||
|
if (err) {
|
||||||
|
data = [];
|
||||||
|
} else
|
||||||
|
if (data.length === 0)
|
||||||
|
data = [];
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'logs',
|
||||||
|
data: data
|
||||||
|
}, command.sender);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
//call the handler
|
||||||
|
var call = handlers[command.command];
|
||||||
|
if (call)
|
||||||
|
call();
|
||||||
|
}
|
||||||
|
|
||||||
|
function restartSSH(cb) {
|
||||||
|
exec('forever stop SSH-Serv', () => {
|
||||||
|
connectSSH(() => {
|
||||||
|
socket.emit('change', {
|
||||||
|
change: {
|
||||||
|
ssh: {
|
||||||
|
port: status.ssh.port,
|
||||||
|
camForwardPort: status.ssh.camForwardPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleKill() {
|
||||||
|
process.stdin.resume();
|
||||||
|
logger.log(logger.importance[0], "Received Shutdown Command");
|
||||||
|
mustBe = true;
|
||||||
|
cmd.kill();
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
process.on('SIGTERM', function() {
|
||||||
|
handleKill();
|
||||||
|
});
|
||||||
|
|
||||||
|
//let's go
|
||||||
|
function init() {
|
||||||
|
config = readConfig();
|
||||||
|
|
||||||
|
if (config.configured) {
|
||||||
|
socket = sock(config.master + '/pi');
|
||||||
|
initSocket();
|
||||||
|
status.name = config.name;
|
||||||
|
if (!cmd)
|
||||||
|
spawn();
|
||||||
|
} else {
|
||||||
|
socket = sock(config.master + '/pi', {
|
||||||
|
query: "unconfigured=true"
|
||||||
|
});
|
||||||
|
status.running = 2;
|
||||||
|
initSocket();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function initSSH(cb) {
|
||||||
|
status.ssh = {
|
||||||
|
// that Could come from the Master server!
|
||||||
|
user: config['ssh-user'],
|
||||||
|
localUser: config['ssh-local-user'],
|
||||||
|
masterPort: config['ssh-port']
|
||||||
|
};
|
||||||
|
|
||||||
|
checkSSH((alive) => {
|
||||||
|
if (alive)
|
||||||
|
cb();
|
||||||
|
else
|
||||||
|
connectSSH(cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function connectSSH(cb = function() {
|
||||||
|
socket.emit('change', {
|
||||||
|
change: {
|
||||||
|
ssh: {
|
||||||
|
port: status.ssh.port,
|
||||||
|
camForwardPort: status.ssh.camForwardPort
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}) {
|
||||||
|
socket.emit('getSSHPort', ports => {
|
||||||
|
[status.ssh.port, status.ssh.camForwardPort] = ports;
|
||||||
|
let ssh = exec(`forever start -a --killSignal=SIGINT --uid SSH-Serv sshManager.js ${status.ssh.port} ${status.ssh.camForwardPort} ${config.camPanelPort}`, {
|
||||||
|
detached: true,
|
||||||
|
shell: true,
|
||||||
|
cwd: __dirname
|
||||||
|
});
|
||||||
|
ssh.on('error', (err) => {
|
||||||
|
socket.emit('data', {
|
||||||
|
type: 'message',
|
||||||
|
data: {
|
||||||
|
title: 'Error',
|
||||||
|
type: 'error',
|
||||||
|
text: 'Could not start SSH tunnels!'
|
||||||
|
}
|
||||||
|
}, command.sender);
|
||||||
|
});
|
||||||
|
cb();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkSSH(cb) {
|
||||||
|
let m, pid, alive;
|
||||||
|
let re = /SSH-Serv.*?sshManager\.js\s([0-9]+)\s([0-9]+).*log.*[^STOPPED]+/g;
|
||||||
|
exec('forever list', (error, stdout, stderr) => {
|
||||||
|
if (error)
|
||||||
|
throw error;
|
||||||
|
let alive = false;
|
||||||
|
while ((m = re.exec(stdout)) !== null) {
|
||||||
|
if (m.index === re.lastIndex) {
|
||||||
|
re.lastIndex++;
|
||||||
|
}
|
||||||
|
if (alive) {
|
||||||
|
exec('forever stop SSH-Serv');
|
||||||
|
cb(false)
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
[, status.ssh.port, status.ssh.camForwardPort] = m;
|
||||||
|
alive = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cb(alive);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function initSocket() {
|
||||||
|
socket.on('connect', function() {
|
||||||
|
logger.log(logger.importance[0], 'Connected to Master: ' + config.master + '.');
|
||||||
|
if (config['ssh-user'])
|
||||||
|
initSSH(err => {
|
||||||
|
if (err)
|
||||||
|
throw err;
|
||||||
|
socket.emit('meta', status);
|
||||||
|
});
|
||||||
|
else {
|
||||||
|
socket.emit('meta', status);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('disconnect', function() {
|
||||||
|
socket.disconnect();
|
||||||
|
init();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('command', (command, cb) => {
|
||||||
|
commandHandlers(command, cb);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function readConfig() {
|
||||||
|
return JSON.parse(fs.readFileSync(__dirname + '/config.js'));
|
||||||
|
}
|
||||||
|
|
||||||
|
init();
|
1
src/.tern-port
Normal file
1
src/.tern-port
Normal file
|
@ -0,0 +1 @@
|
||||||
|
44205
|
251
src/ffmpegCommand.js
Normal file
251
src/ffmpegCommand.js
Normal file
|
@ -0,0 +1,251 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// A Wrapper for Fluent-FFMPEG with a custom command. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
const ffmpeg = require('fluent-ffmpeg');
|
||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Declarations //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Reference to itself. (Object oriented this. Only used to call public methods.)
|
||||||
|
let self = false;
|
||||||
|
|
||||||
|
// The Variable for the FFMpeg command.
|
||||||
|
let cmd = ffmpeg({
|
||||||
|
stdoutLines: 20
|
||||||
|
});
|
||||||
|
|
||||||
|
// The Config, Logger and a handle for the kill timeout.
|
||||||
|
let config, logger, stopHandle = false, connectHandle = false;
|
||||||
|
|
||||||
|
// True if stream should be restarted.
|
||||||
|
let restart = false;
|
||||||
|
|
||||||
|
// Error Texts
|
||||||
|
let errorDescriptions = ['Camera Disconnected',
|
||||||
|
'YoutTube Disconnected',
|
||||||
|
'Wrong ffmpeg executable.',
|
||||||
|
'Unknown Error - Restarting'];
|
||||||
|
|
||||||
|
// Internal Status
|
||||||
|
let status = {
|
||||||
|
streaming: false,
|
||||||
|
error: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// The stream source url. Yet to be set.
|
||||||
|
let source = "";
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Code //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to the ffmpeg process. Uses fluent-ffmpeg.
|
||||||
|
* @param {Object} _config Configuration for the stream. @see config.js.example
|
||||||
|
* @param {Object} _logger Logger Instance from main module.
|
||||||
|
*/
|
||||||
|
let command = function(_config, _logger){
|
||||||
|
// singleton
|
||||||
|
if(self)
|
||||||
|
return self;
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Better Error Checking
|
||||||
|
if(!_config)
|
||||||
|
throw new Error("Invalid Config");
|
||||||
|
|
||||||
|
if(!_logger)
|
||||||
|
throw new Error("Invalid Logger");
|
||||||
|
|
||||||
|
config = _config;
|
||||||
|
|
||||||
|
self = this;
|
||||||
|
|
||||||
|
// (Re)Create the ffmpeg command and configure it.
|
||||||
|
let createCommand = function() {
|
||||||
|
// Clear inputs
|
||||||
|
cmd._inputs = [];
|
||||||
|
|
||||||
|
// TODO: Multi Protocol
|
||||||
|
source = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.camProfile;
|
||||||
|
cmd.input(source);
|
||||||
|
|
||||||
|
// Custom config if any.
|
||||||
|
if (config.customOutputOptions !== "")
|
||||||
|
cmd.outputOptions(config.customOutputOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(','));
|
||||||
|
|
||||||
|
if (config.customAudioOptions !== "")
|
||||||
|
cmd.outputOptions(config.customAudioOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(','));
|
||||||
|
else
|
||||||
|
cmd.AudioCodec('copy');
|
||||||
|
|
||||||
|
if (config.customVideoOptions !== "")
|
||||||
|
cmd.outputOptions(config.customVideoOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(','));
|
||||||
|
else
|
||||||
|
cmd.videoCodec('copy');
|
||||||
|
|
||||||
|
// Output Options.
|
||||||
|
cmd.outputFormat('flv')
|
||||||
|
.outputOptions(['-bufsize 50000k', '-tune film'])
|
||||||
|
.output('rtmp://a.rtmp.youtube.com/live2/' + config.key);
|
||||||
|
|
||||||
|
// Register events.
|
||||||
|
cmd.on('start', started);
|
||||||
|
|
||||||
|
cmd.on('end', stopped);
|
||||||
|
|
||||||
|
cmd.on('error', crashed);
|
||||||
|
};
|
||||||
|
|
||||||
|
let ffmpegCommand = function() {
|
||||||
|
return cmd;
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: Maybe better error resolving strategy.
|
||||||
|
// Start streaming.
|
||||||
|
let start = function() {
|
||||||
|
// Ignore if we try to reconnect.
|
||||||
|
if(connectHandle)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cmd.run();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Restart the stream;
|
||||||
|
let restart = function() {
|
||||||
|
if(status.streaming) {
|
||||||
|
restart = true; // NOTE: not very nice
|
||||||
|
this.stop();
|
||||||
|
} else
|
||||||
|
this.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stop streaming.
|
||||||
|
let stop = function() {
|
||||||
|
cmd.kill('SIGINT');
|
||||||
|
|
||||||
|
stopHandle = setTimeout(() => {
|
||||||
|
logger.log(logger.importance[3], "Force Stop!");
|
||||||
|
cmd.kill();
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
let setConfig = function(_conf) {
|
||||||
|
config = _conf;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Handle Stop and Restart
|
||||||
|
function stopped() {
|
||||||
|
status.streaming = false;
|
||||||
|
|
||||||
|
// Clear force kill Timeout
|
||||||
|
if(stopTimeout) {
|
||||||
|
clearTimeout(stopTimeout);
|
||||||
|
stopTimeout = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart the stream;
|
||||||
|
if (restart) {
|
||||||
|
self.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.emit('stopped');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Restart = false in stopped?
|
||||||
|
// Hande Stat
|
||||||
|
function started() {
|
||||||
|
cmd.emit(restart ? 'restarted' : 'started');
|
||||||
|
restart = false;
|
||||||
|
status.error = false;
|
||||||
|
status.streaming = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error Handling
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log and handle crashes. Notify the main module.
|
||||||
|
* @param { Object } error - Error object from the crash.
|
||||||
|
* @param { String } stdout
|
||||||
|
* @param { String } stderr
|
||||||
|
*/
|
||||||
|
function crashed(error, stdout, stderr){
|
||||||
|
// Can't connect to the
|
||||||
|
if (err.message.indexOf(source) > -1)
|
||||||
|
status.error = 0;
|
||||||
|
|
||||||
|
// Can't connect to the Internet / YouTube
|
||||||
|
else if (err.message.indexOf(source + 'Input/output error') > -1 || err.message.indexOf('rtmp://a.rtmp.youtube.com/live2/' + config.key) > -1)
|
||||||
|
status.error = 1;
|
||||||
|
|
||||||
|
// Wrong FFMPEG Executable
|
||||||
|
else if (err.message.indexOf('spawn') > -1 || err.message.indexOf('niceness') > -1)
|
||||||
|
status.error = 2;
|
||||||
|
|
||||||
|
// Stopped by us - SIGINT Shouldn't lead to a crash.
|
||||||
|
else if (err.message.indexOf('SIGINT') > -1 || err.message.indexOf('SIGKILL') > -1){
|
||||||
|
stopped();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some unknown Problem, just try to restart.
|
||||||
|
else {
|
||||||
|
status.error = 3;
|
||||||
|
|
||||||
|
// Just restart in a Second
|
||||||
|
setTimeout(function(){
|
||||||
|
self.start();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log(logger.importance[2], `Crashed: ${erro}\nSTDERR: ${stderr}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Probe the connection to the host on the port and restart the stream afterwards.
|
||||||
|
* @param { string } host
|
||||||
|
* @param { number } port
|
||||||
|
*/
|
||||||
|
function tryReconnect( host, port ){
|
||||||
|
if (!host || !port)
|
||||||
|
return;
|
||||||
|
|
||||||
|
http.get({
|
||||||
|
host: host.split('/')[0],
|
||||||
|
port: port
|
||||||
|
}, function(res) {
|
||||||
|
// We have a response! Connection works.
|
||||||
|
setTimeout(function() {
|
||||||
|
// NOTE: Ugly!
|
||||||
|
|
||||||
|
// We have a response! Connection works.
|
||||||
|
connectHandle = false;
|
||||||
|
self.start();
|
||||||
|
}, 1000);
|
||||||
|
}).on("error", function(e) {
|
||||||
|
if (e.message == "socket hang up") {
|
||||||
|
setTimeout(function() {
|
||||||
|
// NOTE: Ugly!
|
||||||
|
|
||||||
|
// We have a response! Connection works.
|
||||||
|
connectHandle = false;
|
||||||
|
self.start();
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
// try again
|
||||||
|
connectHandle = setTimeout(function(){
|
||||||
|
tryReconnect(host, port);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
246
src/ffmpegCommand.js~
Normal file
246
src/ffmpegCommand.js~
Normal file
|
@ -0,0 +1,246 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// A Wrapper for Fluent-FFMPEG with a custom command. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
const ffmpeg = require('fluent-ffmpeg');
|
||||||
|
const http = require('http');
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Declarations //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// This
|
||||||
|
let self;
|
||||||
|
|
||||||
|
// The Variable for the FFMpeg command.
|
||||||
|
let cmd = ffmpeg({
|
||||||
|
stdoutLines: 20
|
||||||
|
});
|
||||||
|
|
||||||
|
// The Config, Logger and a handle for the kill timeout.
|
||||||
|
let config, logger, stopHandle = false, connectHandle = false;
|
||||||
|
|
||||||
|
// True if stream should be restarted.
|
||||||
|
let restart = false;
|
||||||
|
|
||||||
|
// Error Texts
|
||||||
|
let errorDescriptions = ['Camera Disconnected',
|
||||||
|
'YoutTube Disconnected',
|
||||||
|
'Wrong ffmpeg executable.',
|
||||||
|
'Unknown Error - Restarting'];
|
||||||
|
|
||||||
|
// Internal Status
|
||||||
|
let status = {
|
||||||
|
streaming: false,
|
||||||
|
error: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// The stream source url. Yet to be set.
|
||||||
|
let source = "";
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Code //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface to the ffmpeg process. Uses fluent-ffmpeg.
|
||||||
|
* @param {Object} _config Configuration for the stream. @see config.js.example
|
||||||
|
* @param {Object} _logger Logger Instance from main module.
|
||||||
|
*/
|
||||||
|
let command = function(_config, _logger){
|
||||||
|
// TODO: Better Error Checking
|
||||||
|
if(!_config)
|
||||||
|
throw new Error("Invalid Config");
|
||||||
|
|
||||||
|
if(!_logger)
|
||||||
|
throw new Error("Invalid Logger");
|
||||||
|
|
||||||
|
config = _config;
|
||||||
|
|
||||||
|
self = this;
|
||||||
|
|
||||||
|
// (Re)Create the ffmpeg command and configure it.
|
||||||
|
let createCommand = function() {
|
||||||
|
// Clear inputs
|
||||||
|
cmd._inputs = [];
|
||||||
|
|
||||||
|
// TODO: Multi Protocol
|
||||||
|
source = 'rtsp://' + config.camIP + ':' + config.camPort + '/' + config.camProfile;
|
||||||
|
cmd.input(source);
|
||||||
|
|
||||||
|
// Custom config if any.
|
||||||
|
if (config.customOutputOptions !== "")
|
||||||
|
cmd.outputOptions(config.customOutputOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(','));
|
||||||
|
|
||||||
|
if (config.customAudioOptions !== "")
|
||||||
|
cmd.outputOptions(config.customAudioOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(','));
|
||||||
|
else
|
||||||
|
cmd.AudioCodec('copy');
|
||||||
|
|
||||||
|
if (config.customVideoOptions !== "")
|
||||||
|
cmd.outputOptions(config.customVideoOptions.replace(/\s+\,\s+/g, ',').replace(/\s+\-/g, ',-').split(','));
|
||||||
|
else
|
||||||
|
cmd.videoCodec('copy');
|
||||||
|
|
||||||
|
// Output Options.
|
||||||
|
cmd.outputFormat('flv')
|
||||||
|
.outputOptions(['-bufsize 50000k', '-tune film'])
|
||||||
|
.output('rtmp://a.rtmp.youtube.com/live2/' + config.key);
|
||||||
|
|
||||||
|
// Register events.
|
||||||
|
cmd.on('start', started);
|
||||||
|
|
||||||
|
cmd.on('end', stopped);
|
||||||
|
|
||||||
|
cmd.on('error', crashed);
|
||||||
|
};
|
||||||
|
|
||||||
|
let ffmpegCommand = function() {
|
||||||
|
return cmd;
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTE: Maybe better error resolving strategy.
|
||||||
|
// Start streaming.
|
||||||
|
let start = function() {
|
||||||
|
// Ignore if we try to reconnect.
|
||||||
|
if(connectHandle)
|
||||||
|
return;
|
||||||
|
|
||||||
|
cmd.run();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Restart the stream;
|
||||||
|
let restart = function() {
|
||||||
|
if(status.streaming) {
|
||||||
|
restart = true; // NOTE: not very nice
|
||||||
|
this.stop();
|
||||||
|
} else
|
||||||
|
this.start();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Stop streaming.
|
||||||
|
let stop = function() {
|
||||||
|
cmd.kill('SIGINT');
|
||||||
|
|
||||||
|
stopHandle = setTimeout(() => {
|
||||||
|
logger.log(logger.importance[3], "Force Stop!");
|
||||||
|
cmd.kill();
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
let setConfig = function(_conf) {
|
||||||
|
config = _conf;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Handle Stop and Restart
|
||||||
|
function stopped() {
|
||||||
|
status.streaming = false;
|
||||||
|
|
||||||
|
// Clear force kill Timeout
|
||||||
|
if(stopTimeout) {
|
||||||
|
clearTimeout(stopTimeout);
|
||||||
|
stopTimeout = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restart the stream;
|
||||||
|
if (restart) {
|
||||||
|
self.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.emit('stopped');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Restart = false in stopped?
|
||||||
|
// Hande Stat
|
||||||
|
function started() {
|
||||||
|
cmd.emit(restart ? 'restarted' : 'started');
|
||||||
|
restart = false;
|
||||||
|
status.error = false;
|
||||||
|
status.streaming = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error Handling
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log and handle crashes. Notify the main module.
|
||||||
|
* @param { Object } error - Error object from the crash.
|
||||||
|
* @param { String } stdout
|
||||||
|
* @param { String } stderr
|
||||||
|
*/
|
||||||
|
function crashed(error, stdout, stderr){
|
||||||
|
// Can't connect to the
|
||||||
|
if (err.message.indexOf(source) > -1)
|
||||||
|
status.error = 0;
|
||||||
|
|
||||||
|
// Can't connect to the Internet / YouTube
|
||||||
|
else if (err.message.indexOf(source + 'Input/output error') > -1 || err.message.indexOf('rtmp://a.rtmp.youtube.com/live2/' + config.key) > -1)
|
||||||
|
status.error = 1;
|
||||||
|
|
||||||
|
// Wrong FFMPEG Executable
|
||||||
|
else if (err.message.indexOf('spawn') > -1 || err.message.indexOf('niceness') > -1)
|
||||||
|
status.error = 2;
|
||||||
|
|
||||||
|
// Stopped by us - SIGINT Shouldn't lead to a crash.
|
||||||
|
else if (err.message.indexOf('SIGINT') > -1 || err.message.indexOf('SIGKILL') > -1){
|
||||||
|
stopped();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some unknown Problem, just try to restart.
|
||||||
|
else {
|
||||||
|
status.error = 3;
|
||||||
|
|
||||||
|
// Just restart in a Second
|
||||||
|
setTimeout(function(){
|
||||||
|
self.start();
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.log(logger.importance[2], `Crashed: ${erro}\nSTDERR: ${stderr}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Probe the connection to the host on the port and restart the stream afterwards.
|
||||||
|
* @param { string } host
|
||||||
|
* @param { number } port
|
||||||
|
*/
|
||||||
|
function tryReconnect( host, port ){
|
||||||
|
if (!host || !port)
|
||||||
|
return;
|
||||||
|
|
||||||
|
http.get({
|
||||||
|
host: host.split('/')[0],
|
||||||
|
port: port
|
||||||
|
}, function(res) {
|
||||||
|
// We have a response! Connection works.
|
||||||
|
setTimeout(function() {
|
||||||
|
// NOTE: Ugly!
|
||||||
|
|
||||||
|
// We have a response! Connection works.
|
||||||
|
connectHandle = false;
|
||||||
|
self.start();
|
||||||
|
}, 1000);
|
||||||
|
}).on("error", function(e) {
|
||||||
|
if (e.message == "socket hang up") {
|
||||||
|
setTimeout(function() {
|
||||||
|
// NOTE: Ugly!
|
||||||
|
|
||||||
|
// We have a response! Connection works.
|
||||||
|
connectHandle = false;
|
||||||
|
self.start();
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
// try again
|
||||||
|
connectHandle = setTimeout(function(){
|
||||||
|
tryReconnect(host, port);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
49
src/logger.js
Normal file
49
src/logger.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Winston Logger Wrapper with Custom Colors and Transports //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
let winston = require('winston');
|
||||||
|
|
||||||
|
const customLevels = {
|
||||||
|
levels: {
|
||||||
|
normal: 0,
|
||||||
|
info: 1,
|
||||||
|
warning: 2,
|
||||||
|
danger: 3,
|
||||||
|
success: 4
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
normal: 'white',
|
||||||
|
info: 'blue',
|
||||||
|
warning: 'orange',
|
||||||
|
danger: 'red',
|
||||||
|
success: 'green'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Set the Colors
|
||||||
|
winston.addColors(customLevels.colors);
|
||||||
|
|
||||||
|
let logger = new(winston.Logger)({
|
||||||
|
levels: customLevels.levels,
|
||||||
|
transports: [
|
||||||
|
new(winston.transports.Console)({
|
||||||
|
level: 'success'
|
||||||
|
}),
|
||||||
|
new(winston.transports.File)({
|
||||||
|
filename: __dirname + '/process.log',
|
||||||
|
colorize: true,
|
||||||
|
timestamp: true,
|
||||||
|
level: 'success',
|
||||||
|
json: true,
|
||||||
|
maxsize: 500000,
|
||||||
|
maxFiles: 10
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.importance = ['normal', 'info', 'warning', 'danger', 'success'];
|
||||||
|
|
||||||
|
// Export the Logger
|
||||||
|
module.exports = logger;
|
||||||
|
|
2
src/logger.js~
Normal file
2
src/logger.js~
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
|
||||||
|
|
117
src/status.js
Normal file
117
src/status.js
Normal file
|
@ -0,0 +1,117 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Redux wrapper to hold the status object and the master server about changes. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Declarations //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Reference to itself.
|
||||||
|
let self = false;
|
||||||
|
|
||||||
|
// The socket.io connection from the main-module.
|
||||||
|
let _socket;
|
||||||
|
|
||||||
|
// The Properties of the status.
|
||||||
|
let _properties;
|
||||||
|
|
||||||
|
// Allowed values for the properties.
|
||||||
|
let _allowed;
|
||||||
|
|
||||||
|
// Action names for the properties.
|
||||||
|
let _actions;
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Code //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper to hold the status object and the master server about changes.
|
||||||
|
*
|
||||||
|
* The state is a shallow object which contains non-object values.
|
||||||
|
* @param { Object } socket Socket.io instance from the main module..
|
||||||
|
* @param { Object } properties The state properties to be set and read.
|
||||||
|
* @param { Array } properties.name.allowed Array of allowed values. The first value is the default value.
|
||||||
|
* @param { String } properties.name.action Name of the action to send to the server.
|
||||||
|
*/
|
||||||
|
let state = function( socket, properties ){
|
||||||
|
// singleton
|
||||||
|
if(self)
|
||||||
|
return self;
|
||||||
|
|
||||||
|
self = this;
|
||||||
|
_socket = socket;
|
||||||
|
|
||||||
|
// Initialize the Properties
|
||||||
|
for(let prop in properties){
|
||||||
|
// When already there or invalid, skip.
|
||||||
|
if(this[prop] || !(properties[prop] instanceof Array))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Set prop and allowed;
|
||||||
|
_properties[prop] = properties[prop][0];
|
||||||
|
_allowed[prop] = Object.assign([], properties[prop]);
|
||||||
|
|
||||||
|
|
||||||
|
// Getter function.
|
||||||
|
(
|
||||||
|
function(prop){
|
||||||
|
Object.defineProperty(self, prop, {
|
||||||
|
get: function(){
|
||||||
|
return this.get(prop);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)(prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = state;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a state value.
|
||||||
|
* @param {String} prop Name of the state property.
|
||||||
|
* @return { * } The properties value or false if it is not found.x
|
||||||
|
*/
|
||||||
|
state.prototype.get = function(prop){
|
||||||
|
if(_properties[prop])
|
||||||
|
return _properties[prop];
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set a state value.
|
||||||
|
* @param {String} prop Name of the state property.
|
||||||
|
* @param { * } value Value to set the property to.
|
||||||
|
* @return { * } The properties new value or false if not allowed or not found.
|
||||||
|
*
|
||||||
|
* /**
|
||||||
|
* Set state values.
|
||||||
|
* @param { Object } properties An object with the properties to be changed.
|
||||||
|
* @return { * } The properties new value or false if not allowed or not found.
|
||||||
|
*/
|
||||||
|
state.prototype.set = function(prop, value){
|
||||||
|
let change;
|
||||||
|
|
||||||
|
if(!value){
|
||||||
|
if(typeof prop !== 'object')
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if(typeof value == 'Object')
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!_properties[prop] || !_properties[prop].allowed[value])
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// NOTE: Maybe unmutable!
|
||||||
|
_properties[prop].value = value;
|
||||||
|
return _properties[prop].value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utilities
|
||||||
|
*/
|
||||||
|
|
19
src/status.js~
Normal file
19
src/status.js~
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Wrapper to hold the status object and the master server about changes. //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Declarations //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
let _socket;
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Code //
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper to hold the status object and the master server about changes.
|
||||||
|
* @param {Object} _socket - Socket.io instance from the main module..
|
||||||
|
*/
|
||||||
|
module.exports = function( _socket ){};
|
Loading…
Add table
Reference in a new issue