compatibility

This commit is contained in:
Hiro Protagonist 2017-04-27 17:43:06 +12:00
parent 379e6bf9d7
commit d451ea6dd4
42 changed files with 6304 additions and 552 deletions

496
#main.js# Executable file
View file

@ -0,0 +1,496 @@
///////////////////////////////////////////////////////////////////////////////////
// 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();

View file

@ -1 +0,0 @@
hiro@ArLeenUX.857:1492754930

View file

@ -1 +1 @@
46703
38259

60
main_new.js Normal file
View file

@ -0,0 +1,60 @@
/**
* @module Main
* @description The main module and entry point.
*/
const redux = require('redux');
const ReduxThunk = require('redux-thunk').default;
const communicator = require('./src/communicator.js');
///////////////////////////////////////////////////////////////////////////////
// Redux //
///////////////////////////////////////////////////////////////////////////////
/**
* Inital State for the app.
* @property { Object } stream Stream Status.
* @property { bool } stream.running Indicates wether the stream is running.
* @property { number | bool } stream.error Error code. Any number outside the range of the Error array in @see ffmpeg-command.js will be treated as non error. Most conveniently set: false.
* @property { bool | string } stream.errorHandling Indicates wether the program tries to handle an error.
* @property { bool } stream.restaring Indicades wether the stream is currently being restarted.
* @property { Object } config Configuration of the camera.
*/
let initialState = {
name: 'Unnamed',
stream: {
running: 'STOPPED',
error: false,
reconnecting: false,
handleError: false,
restaring: false
},
config: false
};
// Reducer
const reducer = require('./src/reducers');
// Reference to the store of the application state.
const store = redux.createStore(reducer, initialState, redux.applyMiddleware(ReduxThunk, communicator.middleware));
// The dispatch function for the state.
const dispatch = store.dispatch;
// The function to get the state.
const getState = store.getState;
// Simple Action creators
const creators = require('./src/actions.js').creators;
///////////////////////////////////////////////////////////////////////////////
// Code //
///////////////////////////////////////////////////////////////////////////////
// Load the Config
store.dispatch(creators.updateConfig());
//const Commander = require('./src/commander.js')(getState);
// The server Communication
const Communicator = new communicator(getState);

55
main_new.js~ Normal file
View file

@ -0,0 +1,55 @@
/**
* @module Main
* @description The main module and entry point.
*/
const redux = require('redux');
const ReduxThunk = require('redux-thunk').default;
///////////////////////////////////////////////////////////////////////////////
// Redux //
///////////////////////////////////////////////////////////////////////////////
/**
* Inital State for the app.
* @property { Object } stream Stream Status.
* @property { bool } stream.running Indicates wether the stream is running.
* @property { number | bool } stream.error Error code. Any number outside the range of the Error array in @see ffmpeg-command.js will be treated as non error. Most conveniently set: false.
* @property { bool | string } stream.errorHandling Indicates wether the program tries to handle an error.
* @property { bool } stream.restaring Indicades wether the stream is currently being restarted.
* @property { Object } config Configuration of the camera.
*/
let initialState = {
stream: {
running: 'STOPPED',
error: false,
reconnecting: false,
handleError: false,
restaring: false
},
config: {}
};
// Reducer
const reducer = require('./src/reducers');
// Reference to the store of the application state.
const store = redux.createStore(reducer, initialState, redux.applyMiddleware(ReduxThunk));
// The dispatch function for the state.
const dispatch = store.dispatch;
// The function to get the state.
const getState = store.getState;
// Simple Action creators
const creators = require('./src/actions.js').creators;
///////////////////////////////////////////////////////////////////////////////
// Code //
///////////////////////////////////////////////////////////////////////////////
// Load the Config
store.dispatch(creators.updateConfig());
console.log(store.getState());
//const Commander = require('./src/commander.js')(getState);

View file

@ -50,13 +50,13 @@
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-State.html">State</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Sat Apr 22 2017 12:09:23 GMT+1200 (NZST)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:45 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>

100
out/main_new.js.html Normal file
View file

@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: main_new.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: main_new.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/**
* @module Main
* @description The main module and entry point.
*/
const redux = require('redux');
const ReduxThunk = require('redux-thunk').default;
///////////////////////////////////////////////////////////////////////////////
// Redux //
///////////////////////////////////////////////////////////////////////////////
/**
* Inital State for the app.
* @property { Object } stream Stream Status.
* @property { bool } stream.running Indicates wether the stream is running.
* @property { number | bool } stream.error Error code. Any number outside the range of the Error array in @see ffmpeg-command.js will be treated as non error. Most conveniently set: false.
* @property { bool | string } stream.errorHandling Indicates wether the program tries to handle an error.
* @property { bool } stream.restaring Indicades wether the stream is currently being restarted.
* @property { Object } config Configuration of the camera.
*/
let initialState = {
stream: {
running: 'STOPPED',
error: false,
reconnecting: false,
handleError: false,
restaring: false
},
config: {}
};
// Reducer
const reducer = require('./reducers');
// Reference to the store of the application state.
const store = redux.createStore(reducer, initialState);
// The dispatch function for the state.
const dispatch = store.dispatch;
// The function to get the state.
const getState = store.getState;
///////////////////////////////////////////////////////////////////////////////
// Code //
///////////////////////////////////////////////////////////////////////////////
const Commander = require('./src/commander.js')(getState);
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:45 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

295
out/module-Actions.html Normal file
View file

@ -0,0 +1,295 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Module: Actions</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Module: Actions</h1>
<section>
<header>
</header>
<article>
<div class="container-overview">
<div class="description">Outsourced definition of the actions and simple action creators for simple actions.</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_actions.js.html">src/actions.js</a>, <a href="src_actions.js.html#line1">line 1</a>
</li></ul></dd>
<dt class="tag-todo">To Do:</dt>
<dd class="tag-todo">
<ul>
<li>allowed values</li>
</ul>
</dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="~actions"><span class="type-signature">(inner) </span>actions<span class="type-signature"></span></h4>
<div class="description">
Actions
The action definitions.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_actions.js.html">src/actions.js</a>, <a href="src_actions.js.html#line13">line 13</a>
</li></ul></dd>
</dl>
<h4 class="name" id="~creators"><span class="type-signature">(inner) </span>creators<span class="type-signature"></span></h4>
<div class="description">
Trivial Action Creators
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_actions.js.html">src/actions.js</a>, <a href="src_actions.js.html#line27">line 27</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:45 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -0,0 +1,320 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: Commander</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: Commander</h1>
<section>
<header>
<h2>
<span class="ancestors"><a href="module-Commander.html">Commander</a>~</span>Commander</h2>
</header>
<article>
<div class="container-overview">
<h4 class="name" id="Commander"><span class="type-signature"></span>new Commander<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Interface to the ffmpeg process. Uses fluent-ffmpeg.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_commander.js.html">src/commander.js</a>, <a href="src_commander.js.html#line46">line 46</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="~createCommand"><span class="type-signature">(inner) </span>createCommand<span class="signature">(config)</span><span class="type-signature"> &rarr; {Object}</span></h4>
<div class="description">
(Re)Create the ffmpeg command and configure it.
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>config</code></td>
<td class="type">
<span class="param-type">Object</span>
</td>
<td class="description last">The configuration for the stream.</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_commander.js.html">src/commander.js</a>, <a href="src_commander.js.html#line62">line 62</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<div class="param-desc">
The fluent-ffmpeg command object.
</div>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">Object</span>
</dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:46 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

649
out/module-Commander.html Normal file
View file

@ -0,0 +1,649 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Module: Commander</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Module: Commander</h1>
<section>
<header>
</header>
<article>
<div class="container-overview">
<div class="description">A Wrapper for Fluent-FFMPEG with a custom command.</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_commander.js.html">src/commander.js</a>, <a href="src_commander.js.html#line1">line 1</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Classes</h3>
<dl>
<dt><a href="module-Commander-Commander.html">Commander</a></dt>
<dd></dd>
</dl>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="~cmd"><span class="type-signature">(inner) </span>cmd<span class="type-signature"></span></h4>
<div class="description">
The FFMPEG command.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_commander.js.html">src/commander.js</a>, <a href="src_commander.js.html#line22">line 22</a>
</li></ul></dd>
</dl>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="~crashed"><span class="type-signature">(inner) </span>crashed<span class="signature">(error, stdout, stderr)</span><span class="type-signature"></span></h4>
<div class="description">
Log and handle crashes. Notify the main module.
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>error</code></td>
<td class="type">
<span class="param-type">Object</span>
</td>
<td class="description last">Error object from the crash.</td>
</tr>
<tr>
<td class="name"><code>stdout</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last"></td>
</tr>
<tr>
<td class="name"><code>stderr</code></td>
<td class="type">
<span class="param-type">String</span>
</td>
<td class="description last"></td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_commander.js.html">src/commander.js</a>, <a href="src_commander.js.html#line174">line 174</a>
</li></ul></dd>
</dl>
<h4 class="name" id="~stopped"><span class="type-signature">(inner) </span>stopped<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Utilities
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_commander.js.html">src/commander.js</a>, <a href="src_commander.js.html#line138">line 138</a>
</li></ul></dd>
</dl>
<h4 class="name" id="~tryReconnect"><span class="type-signature">(inner) </span>tryReconnect<span class="signature">(host, port)</span><span class="type-signature"></span></h4>
<div class="description">
Probe the connection to the host on the port and restart the stream afterwards.
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>host</code></td>
<td class="type">
<span class="param-type">string</span>
</td>
<td class="description last"></td>
</tr>
<tr>
<td class="name"><code>port</code></td>
<td class="type">
<span class="param-type">number</span>
</td>
<td class="description last"></td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_commander.js.html">src/commander.js</a>, <a href="src_commander.js.html#line211">line 211</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:45 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

423
out/module-Main.html Normal file
View file

@ -0,0 +1,423 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Module: Main</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Module: Main</h1>
<section>
<header>
</header>
<article>
<div class="container-overview">
<div class="description">The main module and entry point.</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="main_new.js.html">main_new.js</a>, <a href="main_new.js.html#line1">line 1</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Members</h3>
<h4 class="name" id="~initialState"><span class="type-signature">(inner) </span>initialState<span class="type-signature"></span></h4>
<div class="description">
Inital State for the app.
</div>
<h5 class="subsection-title">Properties:</h5>
<table class="props">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>stream</code></td>
<td class="type">
<span class="param-type">Object</span>
</td>
<td class="description last">Stream Status.
<h6>Properties</h6>
<table class="props">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>running</code></td>
<td class="type">
<span class="param-type">bool</span>
</td>
<td class="description last">Indicates wether the stream is running.</td>
</tr>
<tr>
<td class="name"><code>error</code></td>
<td class="type">
<span class="param-type">number</span>
|
<span class="param-type">bool</span>
</td>
<td class="description last">Error code. Any number outside the range of the Error array in @see ffmpeg-command.js will be treated as non error. Most conveniently set: false.</td>
</tr>
<tr>
<td class="name"><code>errorHandling</code></td>
<td class="type">
<span class="param-type">bool</span>
|
<span class="param-type">string</span>
</td>
<td class="description last">Indicates wether the program tries to handle an error.</td>
</tr>
<tr>
<td class="name"><code>restaring</code></td>
<td class="type">
<span class="param-type">bool</span>
</td>
<td class="description last">Indicades wether the stream is currently being restarted.</td>
</tr>
</tbody>
</table>
</td>
</tr>
<tr>
<td class="name"><code>config</code></td>
<td class="type">
<span class="param-type">Object</span>
</td>
<td class="description last">Configuration of the camera.</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="main_new.js.html">main_new.js</a>, <a href="main_new.js.html#line22">line 22</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:46 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

581
out/module-Reducers.html Normal file
View file

@ -0,0 +1,581 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Module: Reducers</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Module: Reducers</h1>
<section>
<header>
</header>
<article>
<div class="container-overview">
<div class="description">Outsourced definition of the Reducers for redux.</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_reducers.js.html">src/reducers.js</a>, <a href="src_reducers.js.html#line1">line 1</a>
</li></ul></dd>
<dt class="tag-see">See:</dt>
<dd class="tag-see">
<ul>
<li><a href="http://redux.js.org/docs/basics/Reducers.html">http://redux.js.org/docs/basics/Reducers.html</a></li>
</ul>
</dd>
</dl>
</div>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="~config"><span class="type-signature">(inner) </span>config<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Updates the config state.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_reducers.js.html">src/reducers.js</a>, <a href="src_reducers.js.html#line81">line 81</a>
</li></ul></dd>
</dl>
<h4 class="name" id="~error"><span class="type-signature">(inner) </span>error<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Set the error code. @see ffmpegCommand.js for the error code descriptions.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_reducers.js.html">src/reducers.js</a>, <a href="src_reducers.js.html#line21">line 21</a>
</li></ul></dd>
</dl>
<h4 class="name" id="~handleError"><span class="type-signature">(inner) </span>handleError<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Set the error handling procedure.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_reducers.js.html">src/reducers.js</a>, <a href="src_reducers.js.html#line36">line 36</a>
</li></ul></dd>
</dl>
<h4 class="name" id="~running"><span class="type-signature">(inner) </span>running<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Set the running flag.
It can either be RUNNING, STARTING, STOPPING or STOPPED
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_reducers.js.html">src/reducers.js</a>, <a href="src_reducers.js.html#line53">line 53</a>
</li></ul></dd>
</dl>
<h4 class="name" id="~stream"><span class="type-signature">(inner) </span>stream<span class="signature">()</span><span class="type-signature"></span></h4>
<div class="description">
Stream Root Reducer.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_reducers.js.html">src/reducers.js</a>, <a href="src_reducers.js.html#line68">line 68</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:46 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -0,0 +1,460 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: Streamer</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: Streamer</h1>
<section>
<header>
<h2>
<span class="ancestors"><a href="module-Streamer.html">Streamer</a>~</span>Streamer</h2>
<div class="class-description">Streamer
The central control Object (singleton for now) which has the permission to change state.</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="Streamer"><span class="type-signature"></span>new Streamer<span class="signature">(communicator)</span><span class="type-signature"></span></h4>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Default</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>communicator</code></td>
<td class="type">
<span class="param-type">Object</span>
</td>
<td class="default">
false
</td>
<td class="description last">A communicator object. @see communicator.js - Optional //TODO: Find proper tag.</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_streamer.js.html">src/streamer.js</a>, <a href="src_streamer.js.html#line59">line 59</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="getConfig"><span class="type-signature"></span>getConfig<span class="signature">()</span><span class="type-signature"> &rarr; {*}</span></h4>
<div class="description">
Get the current config.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_streamer.js.html">src/streamer.js</a>, <a href="src_streamer.js.html#line91">line 91</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<div class="param-desc">
The current config.
</div>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">*</span>
</dd>
</dl>
<h4 class="name" id="setCommunicator"><span class="type-signature"></span>setCommunicator<span class="signature">(communicator)</span><span class="type-signature"></span></h4>
<div class="description">
Set the communicator object and enable the state sync with the server.
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>communicator</code></td>
<td class="type">
<span class="param-type">Object</span>
</td>
<td class="description last">A communicator object. @see communicator.js</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_streamer.js.html">src/streamer.js</a>, <a href="src_streamer.js.html#line80">line 80</a>
</li></ul></dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:46 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Module: State</title>
<title>JSDoc: Module: Streamer</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
@ -17,7 +17,7 @@
<div id="main">
<h1 class="page-title">Module: State</h1>
<h1 class="page-title">Module: Streamer</h1>
@ -38,7 +38,7 @@
<div class="container-overview">
<div class="description">Redux Wrapper to hold the state and communicate with the Server</div>
<div class="description">The central interface to the streaming process.</div>
@ -87,7 +87,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="status.js.html">status.js</a>, <a href="status.js.html#line1">line 1</a>
<a href="src_streamer.js.html">src/streamer.js</a>, <a href="src_streamer.js.html#line1">line 1</a>
</li></ul></dd>
@ -122,6 +122,13 @@
<h3 class="subsection-title">Classes</h3>
<dl>
<dt><a href="module-Streamer-Streamer.html">Streamer</a></dt>
<dd></dd>
</dl>
@ -132,13 +139,13 @@
<h4 class="name" id="~ActionDispatchers"><span class="type-signature">(inner) </span>Action Dispatchers<span class="type-signature"></span></h4>
<h4 class="name" id="~_communicator"><span class="type-signature">(inner) </span>_communicator<span class="type-signature"></span></h4>
<div class="description">
The central redux state manager. The module exposes central action dispatchers. Updates to the state get communicated to the server.
The socket.io connection from the main-module.
</div>
@ -176,7 +183,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="status.js.html">status.js</a>, <a href="status.js.html#line54">line 54</a>
<a href="src_streamer.js.html">src/streamer.js</a>, <a href="src_streamer.js.html#line35">line 35</a>
</li></ul></dd>
@ -407,7 +414,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="status.js.html">status.js</a>, <a href="status.js.html#line23">line 23</a>
<a href="src_streamer.js.html">src/streamer.js</a>, <a href="src_streamer.js.html#line22">line 22</a>
</li></ul></dd>
@ -424,24 +431,14 @@
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="~reducer"><span class="type-signature">(inner, constant) </span>reducer<span class="type-signature"></span></h4>
<h4 class="name" id="~applyDispatchers"><span class="type-signature">(inner) </span>applyDispatchers<span class="signature">(creators)</span><span class="type-signature"></span></h4>
<div class="description">
Adds prototypes for the action dispatchers to the State.
Redux Actions
</div>
@ -450,61 +447,6 @@
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>creators</code></td>
<td class="type">
<span class="param-type">Object</span>
</td>
<td class="description last"></td>
</tr>
</tbody>
</table>
<dl class="details">
@ -534,7 +476,7 @@
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="status.js.html">status.js</a>, <a href="status.js.html#line114">line 114</a>
<a href="src_streamer.js.html">src/streamer.js</a>, <a href="src_streamer.js.html#line41">line 41</a>
</li></ul></dd>
@ -550,46 +492,9 @@
<h5>Throws:</h5>
<dl>
<dt>
<div class="param-desc">
If a method with the creators name already exists. @see actionCreators.js
</div>
</dt>
<dd></dd>
<dt>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">Error</span>
</dd>
</dl>
</dt>
<dd></dd>
</dl>
@ -605,13 +510,13 @@
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-State.html">State</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Sat Apr 22 2017 12:09:23 GMT+1200 (NZST)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:46 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>

View file

@ -0,0 +1,423 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Module: Utilities/Config</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Module: Utilities/Config</h1>
<section>
<header>
</header>
<article>
<div class="container-overview">
<div class="description">Config read/write utilities.</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_utility_config.js.html">src/utility/config.js</a>, <a href="src_utility_config.js.html#line1">line 1</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id=".read"><span class="type-signature">(static) </span>read<span class="signature">()</span><span class="type-signature"> &rarr; {Object|bool}</span></h4>
<div class="description">
Read the JSON config file.
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_utility_config.js.html">src/utility/config.js</a>, <a href="src_utility_config.js.html#line18">line 18</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<div class="param-desc">
Returns the parsed config or false in case of an error.
</div>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">Object</span>
|
<span class="param-type">bool</span>
</dd>
</dl>
<h4 class="name" id=".write"><span class="type-signature">(static) </span>write<span class="signature">(state)</span><span class="type-signature"> &rarr; {Promise}</span></h4>
<div class="description">
Writes the config to disk.
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>state</code></td>
<td class="type">
<span class="param-type">Object</span>
</td>
<td class="description last">A function to retrieve the current state. This makes it harder to accidentially write arbitrary code.</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="src_utility_config.js.html">src/utility/config.js</a>, <a href="src_utility_config.js.html#line32">line 32</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<div class="param-desc">
A promise for writing the configuration.
</div>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">Promise</span>
</dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:46 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

137
out/src_actions.js.html Normal file
View file

@ -0,0 +1,137 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: src/actions.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: src/actions.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/** Outsourced definition of the actions and simple action creators for simple actions.
* @module Actions
* @todo allowed values
*/
const Promise = require('promise');
const writeConfig = require('./utility/config.js').write;
/**
* Actions
* The action definitions.
*/
let actions = {
UPDATE_CONFIG: 'UPDATE_CONFIG',
REQUEST_START: 'REQUEST_START',
SET_STARTED: 'SET_STATED',
REQUEST_STOP: 'REQUEST_STOP',
SET_STOPPED: 'SET_STOPPED',
REQUEST_RESTART: 'REQUEST_RESTART',
SET_ERROR: 'SET_ERROR',
TRY_RECONECT: 'TRY_RECONNECT'
};
/**
* Trivial Action Creators
*/
let creators = {};
creators.updateConfig = function(update) {
return function(dispatch, getState) {
dispatch({
type: actions.UPDATE_CONFIG,
data: update
});
return writeConfig(getState());
};
};
creators.requestStart = function() {
return {
type: actions.REQUEST_START
};
};
creators.requestStop = function() {
return {
type: actions.REQUEST_STOP
};
};
creators.setStarted = function() {
return {
type: actions.SET_STARTED
};
};
creators.setStopped = function() {
return {
type: actions.SET_STOPPED
};
};
creators.requestRestart = function() {
return {
type: actions.REQUEST_RESTART
};
};
creators.setError = function(error) {
return {
type: actions.SET_ERROR,
data: error
};
};
creators.tryReconnect = function() {
return {
type: actions.TRY_RECONECT
};
};
module.exports = {
actions,
creators
};
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:45 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

294
out/src_commander.js.html Normal file
View file

@ -0,0 +1,294 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: src/commander.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: src/commander.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/**
* @module Commander
* @description A Wrapper for Fluent-FFMPEG with a custom command.
*/
const ffmpeg = require('fluent-ffmpeg');
const http = require('http');
const logger = require('./logger');
///////////////////////////////////////////////////////////////////////////////
// Declarations //
///////////////////////////////////////////////////////////////////////////////
// Reference to itself. (Object oriented this. Only used to call public methods.)
let self = false;
/**
* The FFMPEG command.
* @member
*/
let cmd = ffmpeg({
stdoutLines: 20
});
// The Config, Logger and a handle for the kill timeout.
let _config, _stopHandle = false, _connectHandle = false;
// Error Texts //TODO put them into separate module
let errorDescriptions = ['Camera Disconnected',
'YoutTube Disconnected',
'Wrong ffmpeg executable.',
'Unknown Error - Restarting'];
// The stream source url. Yet to be set.
let source = "";
///////////////////////////////////////////////////////////////////////////////
// Code //
///////////////////////////////////////////////////////////////////////////////
/**
* Interface to the ffmpeg process. Uses fluent-ffmpeg.
* @constructor
*/
let Commander = function(){
// singleton
if(self)
return self;
// Make `new` optional.
if(!(this instanceof Commander))
return new Commander();
self = this;
/**
* (Re)Create the ffmpeg command and configure it.
* @param { Object } config The configuration for the stream.
* @returns { Object } The fluent-ffmpeg command object.
*/
let createCommand = function(config) {
// 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);
return cmd;
};
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);
};
};
/**
* 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);
}
});
}
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:45 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

130
out/src_logger.js.html Normal file
View file

@ -0,0 +1,130 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: src/logger.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: src/logger.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>///////////////////////////////////////////////////////////////////////////////
// Winston Logger Wrapper with Custom Colors and Transports //
///////////////////////////////////////////////////////////////////////////////
let winston = require('winston');
///////////////////////////////////////////////////////////////////////////////
// Declarations //
///////////////////////////////////////////////////////////////////////////////
// Object oriented `this`.
let self = false;
// Custom log levels.
const customLevels = {
levels: {
normal: 0,
info: 1,
warning: 2,
danger: 3,
success: 4
},
colors: {
normal: 'white',
info: 'blue',
warning: 'orange',
danger: 'red',
success: 'green'
}
};
///////////////////////////////////////////////////////////////////////////////
// Code //
///////////////////////////////////////////////////////////////////////////////
/**
* Monkey Patching.
*/
/**
* Calls the logging function with arguments, if the node enviroment isn't in production mode.
*/
winston.Logger.prototype.dbgmsg = function() {
if (process.env.NODE_ENV !== 'production')
this.log.apply(undefined, arguments);
};
// Set the Colors
winston.addColors(customLevels.colors);
let logger = (function() {
if(!self)
self = 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
})
]
});
return self;
})();
logger.importance = ['normal', 'info', 'warning', 'danger', 'success'];
// Export the Logger
module.exports = logger;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:45 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

146
out/src_reducers.js.html Normal file
View file

@ -0,0 +1,146 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: src/reducers.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: src/reducers.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/** Outsourced definition of the Reducers for redux.
* @see {@link http://redux.js.org/docs/basics/Reducers.html}
* @module Reducers
*/
const redux = srequire('redux');
const { UPDATE_CONFIG,
REQUEST_START,
SET_STARTED,
REQUEST_STOP,
SET_STOPPED,
REQUEST_RESTART,
SET_ERROR,
TRY_RECONECT}= require('./actions');
let reducers = {};
/**
* Set the error code. @see ffmpegCommand.js for the error code descriptions.
*/
function error(state = false, action) {
switch (action.type) {
case SET_ERROR:
return action.data;
case SET_STARTED:
return false;
default:
return state;
}
};
// TODO: Add central definition
/**
* Set the error handling procedure.
*/
function handleError(state = false, action) {
switch (action.type) {
case SET_STARTED:
return false;
case TRY_RECONECT:
return 'RECONNECTING';
default:
return state;
}
}
/**
* Set the running flag.
* It can either be RUNNING, STARTING, STOPPING or STOPPED
*/
function running(state = 'STOPPED', action) {
switch (action.type) {
case REQUEST_START:
return 'STARTING';
case SET_STARTED:
return 'RUNNING';
case REQUEST_STOP:
return 'STOPPING';
case SET_STOPPED:
return 'STOPPED';
default:
return state;
}
}
/**
* @function stream
* @description Stream Root Reducer.
*/
reducers.stream = function(state = {}, action){
return {
running: running(state, action),
error: error(state, action),
handleError: handleError(state, action),
restarting: restarting(state, action)
};
};
/**
* @function config
* @description Updates the config state.
*/
reducers.config = function(state = {}, action) {
switch (action.type) {
case UPDATE_CONFIG:
return Object.assign({}, state, action.data);
break;
default:
return state;
}
};
module.exports = redux.combineReducers(reducers);
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:45 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -2,7 +2,7 @@
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: status.js</title>
<title>JSDoc: Source: src/streamer.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
@ -17,7 +17,7 @@
<div id="main">
<h1 class="page-title">Source: status.js</h1>
<h1 class="page-title">Source: src/streamer.js</h1>
@ -26,11 +26,10 @@
<section>
<article>
<pre class="prettyprint source linenums"><code>/** Redux Wrapper to hold the state and communicate with the Server
* @module State
<pre class="prettyprint source linenums"><code>/** The central interface to the streaming process.
* @module Streamer
*/
const redux = require('redux');
const logger = require('./logger');
///////////////////////////////////////////////////////////////////////////////
@ -50,23 +49,22 @@ let self = false;
*/
let initialState = {
stream: {
running: false,
running: 'STOPPED',
error: -1,
reconnecting: false
reconnecting: false,
restarting: false
},
config: {}
}; // NOTE: Maybe in main.js
// The socket.io connection from the main-module.
/**
* The socket.io connection from the main-module.
*/
let _communicator;
// Actions
const actions = require('./actions');
// Action Creators
const creators = require('./actionCreators');
const dispatchers = redux.bindActionCreators(creators);
/**
* Redux Actions
*/
// Reducer
const reducer = require('./reducers');
@ -80,20 +78,20 @@ const store = redux.createStore(reducer, initialState);
// TODO: Log state changes.
/**
* The central redux state manager. The module exposes central action dispatchers. Updates to the state get communicated to the server.
*
* @class Streamer
* The central control Object (singleton for now) which has the permission to change state.
*
* @constructor
* @member [Action Dispatchers] The action creators (@see actionCreators.js) are bound to the dispatch function and made member of the State object.
* @param { Object } communicator A communicator object. @see communicator.js - Optional //TODO: Find proper tag.
*/
function State(communicator = false) {
function Streamer(communicator = false) {
// singleton
if (self)
return self;
// Make `new` optional.
if (!(this instanceof State))
return new State(_communicator);
if (!(this instanceof Streamer))
return new Streamer(_communicator);
_communicator = communicator;
@ -101,58 +99,31 @@ function State(communicator = false) {
return this;
};
applyDispatchers(creators);
module.exports = Streamer;
/**
* Set the communicator object and enable the state sync with the server.
* @param {Object} communicator A communicator object. @see communicator.js
*/
State.prototype.setCommunicator = function(communicator) {
Streamer.prototype.setCommunicator = function(communicator) {
if (_communicator)
_communicator = communicator;
else
logger.dbgmsdg("Invalid Communicator");
};
/**
* Wrapper for redux.
* @returns { Object } The current state.
*/
State.prototype.getState = function(){
return store.getState();
};
/**
* Get the current config.
* @returns { * } The current config.
*/
State.prototype.getConfig = function(){
return store.getState().config;
Streamer.prototype.getConfig = function(){
return store.getStreamer().config;
};
/**
* Utilities
*/
/**
* Adds prototypes for the action dispatchers to the State.
* @param { Object } creators
* @throws { Error } If a method with the creators name already exists. @see actionCreators.js
*/
function applyDispatchers( creators ){
for(let creator in creators){
if(State.prototype[creator])
throw new Error(`State has already a meber '${creator}'.`);
State.prototype[creator] = function(){
store.dispatch(creators[creator].apply(undefined, arguments));
};
}
}
module.exports = State;
</code></pre>
@ -165,13 +136,13 @@ module.exports = State;
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-State.html">State</a></li></ul>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Sat Apr 22 2017 12:09:23 GMT+1200 (NZST)
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:45 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>

View file

@ -0,0 +1,94 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: src/utility/config.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: src/utility/config.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/** Config read/write utilities.
* @module Utilities/Config
*/
const Promise = require('promise');
const fs = require('fs');
const logger = require('./logger.js');
// TODO: Config path in defs
module.exports = {};
// TODO Default CFG
/**
* Read the JSON config file.
* @returns { Object | bool } Returns the parsed config or false in case of an error.
*/
module.exports.read = function() {
try {
return JSON.parse(fs.readFileSync(__dirname + '/config.js'));
} catch (e) {
logger.dbgmsg(e);
return false;
}
};
/**
* Writes the config to disk.
* @param {Object} state A function to retrieve the current state. This makes it harder to accidentially write arbitrary code.
* @returns { Promise } A promise for writing the configuration.
*/
module.exports.write = function( getState ){
return new Promise((resolve, reject) => {
fs.writeFile(__dirname + '/config.js',
JSON.stringify(getState().config, undefined, 2),
err => {
if(err)
reject(err);
else
resolve();
});
});
};
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-Actions.html">Actions</a></li><li><a href="module-Commander.html">Commander</a></li><li><a href="module-Main.html">Main</a></li><li><a href="module-Reducers.html">Reducers</a></li><li><a href="module-Streamer.html">Streamer</a></li><li><a href="module-Utilities_Config.html">Utilities/Config</a></li></ul><h3>Classes</h3><ul><li><a href="module-Commander-Commander.html">Commander</a></li><li><a href="module-Streamer-Streamer.html">Streamer</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.4.3</a> on Mon Apr 24 2017 18:52:45 GMT+1200 (NZST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View file

@ -10,7 +10,9 @@
"license": "GPL-3.0",
"dependencies": {
"fluent-ffmpeg": "^2.1.0",
"promise": "^7.1.1",
"redux": "^3.6.0",
"redux-thunk": "^2.2.0",
"socket.io-client": "^1.4.8",
"winston": "^2.2.0"
}

72
src/#logger.js# Normal file
View file

@ -0,0 +1,72 @@
///////////////////////////////////////////////////////////////////////////////
// Winston Logger Wrapper with Custom Colors and Transports //
///////////////////////////////////////////////////////////////////////////////
let winston = require('winston');
///////////////////////////////////////////////////////////////////////////////
// Declarations //
///////////////////////////////////////////////////////////////////////////////
// Object oriented `this`.
let self = false;
// Custom log levels.
const customLevels = {
levels: {
normal: 0,
info: 1,
warning: 2,
danger: 3,
success: 4
},
colors: {
normal: 'white',
info: 'blue',
warning: 'orange',
danger: 'red',
success: 'green'
}
};
///////////////////////////////////////////////////////////////////////////////
// Code //
///////////////////////////////////////////////////////////////////////////////
// Monkey Patching.
/**
* Calls the logging function with arguments, if the node enviroment isn't in production mode.
*/
// Set the Colors
winston.addColors(customLevels.colors);
let logger = (function() {
if(!self)
self = new(winston.Logger)({
levels: customLevels.levels,
transports: [
new(winston.transports.Console)({
p level: 'success'
}),
new(winston.transports.File)({
filename: __dirname + '/process.log',
colorize: true,
timestamp: true,
level: 'success',
json: true,
maxsize: 500000,
maxFiles: 10
})
]
});
return self;
})();
logger.importance = ['normal', 'info', 'warning', 'danger', 'success'];
// Export the Logger
module.exports = logger;

View file

@ -1,70 +0,0 @@
///////////////////////////////////////////////////////////////////////////////
// Reducers //
///////////////////////////////////////////////////////////////////////////////
const redux = require('redux');
const actions = require('./actions');
let reducers = {};
/**
* Set the streaming/error state.
*/
reducers.error = function(state = -1, action) {
switch (action.type) {
case actions.SET_ERROR:
if((typeof action.data) == 'undefined') {
return action.data;
break;
}
default:
return state;
}
};
/**
* Set the streaming state.
*/
reducers.streaming = function(state = 'STOPPED', action) {
switch (action.type) {
case actions.REQUEST_START:
case actions.REQEST_STOP:
return 'STOPPING'
default:
return state;
}
};
/**
* Set the restart flag.
*
* The restart flag get's automaticly unset, as soon as the start action gets reduced.
*/
reducers.restart = function(state = false, action) {
switch (action.type) {
case actions.RESTART:
return true;
case actions.START:
return false;
default:
return state;
}
};
/**
* Update the config.
*/
reducers.config = function(state = {}, action) {
switch (action.type) {
case actions.UPDATE_CONFIG:
return Object.assign({}, state, action.data);
break;
default:
return state;
}
};
module.exports = redux.combineReducers(reducers);

71
src/#streamer.js# Normal file
View file

@ -0,0 +1,71 @@
/** The central interface to the streaming process.
* @module Streamer
*/
const logger = require('./logger');
///////////////////////////////////////////////////////////////////////////////
// Declarations //
///////////////////////////////////////////////////////////////////////////////
// Object oriented `this`.
let self = false;
///////////////////////////////////////////////////////////////////////////////
// Code //
///////////////////////////////////////////////////////////////////////////////
// TODO: Log state changes.
/**
* @class Streamer
* The central control Object (singleton for now) which has the permission to change state.
*
* @constructor
* @param { Object } communicator A communicator object. @see communicator.js - Optional //TODO: Find proper tag.
*/
function Streamer(communicator = false) {
// singleton
if (self)
return self;
// Make `new` optional.
if (!(this instanceof Streamer))
return new Streamer(_communicator);
_communicator = communicator;
self = this;
return this;
};
module.exports = Streamer;
/**
* Set the communicator object and enable the state sync with the server.
* @param {Object} communicator A communicator object. @see communicator.js
*/
Streamer.prototype.setCommunicator = function(communicator) {
if (_communicator)
_communicator = communicator;
else
logger.dbgmsdg("Invalid Communicator");
};
/**
* Get the current config.
* @returns { * } The current config.
*/
Streamer.prototype.getConfig = function(){
return store.getStreamer().config;
};
/**
* Utilities
*/

1
src/.#logger.js Symbolic link
View file

@ -0,0 +1 @@
hiro@ArLeenUX.3138:1493100086

View file

@ -1 +0,0 @@
hiro@ArLeenUX.857:1492754930

View file

@ -1,66 +0,0 @@
/**
* Action Creators
*
* The action creators create actions to alter the state. In this implementation they have no side effects.
*/
const actions = require('./actions');
let creators = {};
/**
* Creates a start action.
* @returns { Oject } Action
*/
creators.start = function() {
return {
type: actions.START
};
};
/**
* Creates a stop action.
* @returns { Oject } Action
*/
creators.stop = function() {
return {
type: actions.STOP
};
};
/**
* Creates a restart action.
* @returns { Oject } Action
*/
creators.restart = function() {
return {
type: actions.RESTART
};
};
/**
* Creates an action to set the Error.
* @param { Number } error The error code. @see ffmpegCommand.js
* @returns { Object } Action
*/
creators.setError = function(error) {
return {
type: actions.SET_ERROR,
data: error
};
};
/**
* Creates an action to update the config.
* @param { Object } configUpdate The changes to the config.
* @returns { Object } Action
*/
creators.updateConfig = function(configUpdate) {
return {
type: actions.UPDATE_CONFIG,
data: configUpdate
};
};
module.exports = creators;

View file

@ -1,8 +1,16 @@
///////////////////////////////////////////////////////////////////////////////
// The Actions to change the State //
///////////////////////////////////////////////////////////////////////////////
/** Outsourced definition of the actions and simple action creators for trivial actions.
* @module Actions
* @todo allowed values
*/
let actions = module.exports = {
const Promise = require('promise');
const writeConfig = require('./utility/config.js').write;
const readConfig = require('./utility/config.js').read;
/**
* Actions
* The action definitions.
*/
let actions = {
UPDATE_CONFIG: 'UPDATE_CONFIG',
REQUEST_START: 'REQUEST_START',
SET_STARTED: 'SET_STATED',
@ -10,5 +18,101 @@ let actions = module.exports = {
SET_STOPPED: 'SET_STOPPED',
REQUEST_RESTART: 'REQUEST_RESTART',
SET_ERROR: 'SET_ERROR',
TRY_RECONECT: 'TRY_RECONNECT'
TRY_RECONECT: 'TRY_RECONNECT',
SET_NAME: 'SET_NAME',
// Master Server Related
HYDRATE: 'HYDRATE'
};
/**
* Trivial Action Creators
*/
let creators = {};
/**
* Updates or initializes the configuration state. Updating and initializing are sync actions. The disk write happens asynchronously. Also sets the name if it is mentioned in the update.
* @param { Object } update The new (partial) config. If it is undefined, then the configuration will be read from disk.
* @returns { Function } A thunk for redux-thunk.
* @throws { Error } In case the Config can't be read.
*/
creators.updateConfig = function(update) {
return (dispatch, getState) => {
let init = false;
if(!update) {
init = true;
update = readConfig();
// TODO: Proper handling.
if(!update)
throw new Error('Could not load config.');
}
dispatch({
type: actions.UPDATE_CONFIG,
data: update
});
if(update.name)
dispatch(creators.setName(update.name));
return init ? Promise.resolve() : writeConfig(getState);
};
};
creators.requestStart = function() {
return {
type: actions.REQUEST_START
};
};
creators.requestStop = function() {
return {
type: actions.REQUEST_STOP
};
};
creators.setStarted = function() {
return {
type: actions.SET_STARTED
};
};
creators.setStopped = function() {
return {
type: actions.SET_STOPPED
};
};
creators.requestRestart = function() {
return {
type: actions.REQUEST_RESTART
};
};
creators.setError = function(error) {
return {
type: actions.SET_ERROR,
data: error
};
};
creators.tryReconnect = function() {
return {
type: actions.TRY_RECONECT
};
};
creators.setName = function(name) {
return {
type: actions.SET_NAME,
data: name
};
};
module.exports = {
actions,
creators
};

View file

@ -1,6 +1,8 @@
///////////////////////////////////////////////////////////////////////////////
// A Wrapper for Fluent-FFMPEG with a custom command. //
///////////////////////////////////////////////////////////////////////////////
/**
* @module Commander
* @description A Wrapper for Fluent-FFMPEG with a custom command.
*/
const ffmpeg = require('fluent-ffmpeg');
const http = require('http');
@ -13,7 +15,10 @@ const logger = require('./logger');
// Reference to itself. (Object oriented this. Only used to call public methods.)
let self = false;
// The Variable for the FFMpeg command.
/**
* The FFMPEG command.
* @member
*/
let cmd = ffmpeg({
stdoutLines: 20
});
@ -21,7 +26,7 @@ let cmd = ffmpeg({
// The Config, Logger and a handle for the kill timeout.
let _config, _stopHandle = false, _connectHandle = false;
// Error Texts
// Error Texts //TODO put them into separate module
let errorDescriptions = ['Camera Disconnected',
'YoutTube Disconnected',
'Wrong ffmpeg executable.',
@ -30,8 +35,8 @@ let errorDescriptions = ['Camera Disconnected',
// The stream source url. Yet to be set.
let source = "";
// The State Object.
let State;
// The function to get the State.
let getState;
///////////////////////////////////////////////////////////////////////////////
// Code //
@ -39,29 +44,29 @@ let State;
/**
* Interface to the ffmpeg process. Uses fluent-ffmpeg.
* @param {Object} config Configuration for the stream. @see config.js.example
* @constructor
*/
let Commander = function(State){
let Commander = function(_getState){
// singleton
if(self)
return self;
// Make `new` optional.
if(!(this instanceof Commander))
return new Commander(config, logger);
// TODO: Better Error Checkinglet
if(!config)
throw new Error("Invalid Config");
if(!logger)
throw new Error("Invalid Logger");
return new Commander();
if (!_getState) {
throw new Error('Invalid getState() function.');
}
getState = _getState;
self = this;
// (Re)Create the ffmpeg command and configure it.
/**
* (Re)Create the ffmpeg command and configure it.
* @param { Object } config The configuration for the stream.
* @returns { Object } The fluent-ffmpeg command object.
*/
let createCommand = function() {
// Clear inputs
cmd._inputs = [];
@ -131,16 +136,20 @@ let Commander = function(State){
cmd.kill();
}, 3000);
};
let setConfig = function(_conf) {
config = _conf;
};
};
}
/**
* Utilities
*/
/**
* Get the current config.
* @returns {}
*/
function config() {
return getState().config;
}
// Handle Stop and Restart
function stopped() {
status.streaming = false;

259
src/commander.js~ Normal file
View file

@ -0,0 +1,259 @@
/**
* @module Commander
* @description A Wrapper for Fluent-FFMPEG with a custom command.
*/
const ffmpeg = require('fluent-ffmpeg');
const http = require('http');
const logger = require('./logger');
///////////////////////////////////////////////////////////////////////////////
// Declarations //
///////////////////////////////////////////////////////////////////////////////
// Reference to itself. (Object oriented this. Only used to call public methods.)
let self = false;
/**
* The FFMPEG command.
* @member
*/
let cmd = ffmpeg({
stdoutLines: 20
});
// The Config, Logger and a handle for the kill timeout.
let _config, _stopHandle = false, _connectHandle = false;
// Error Texts //TODO put them into separate module
let errorDescriptions = ['Camera Disconnected',
'YoutTube Disconnected',
'Wrong ffmpeg executable.',
'Unknown Error - Restarting'];
// The stream source url. Yet to be set.
let source = "";
// The function to get the State.
let getState;
///////////////////////////////////////////////////////////////////////////////
// Code //
///////////////////////////////////////////////////////////////////////////////
/**
* Interface to the ffmpeg process. Uses fluent-ffmpeg.
* @constructor
*/
let Commander = function(_getState){
// singleton
if(self)
return self;
// Make `new` optional.
if(!(this instanceof Commander))
return new Commander();
if (!_getState) {
throw new Error('Invalid getState() function.');
}
getState = _getState;
self = this;
/**
* (Re)Create the ffmpeg command and configure it.
* @param { Object } config The configuration for the stream.
* @returns { Object } The fluent-ffmpeg command object.
*/
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);
return cmd;
};
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);
};
}
/**
* Utilities
*/
/**
* Get the current config.
* @returns {}
*/
function config() {
return getState().config;
}
// 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);
}
});
}

232
src/communicator.js Normal file
View file

@ -0,0 +1,232 @@
///////////////////////////////////////////////////////////////////////////////
// A socket.js wrapper to abstract the communication with the server. //
///////////////////////////////////////////////////////////////////////////////
/**
* @module Communicator
* @description Abstracts the communication with the master server.
*/
const socketio = require('socket.io-client');
const logger = require('./logger.js');
/*const { HYDRATE } = require('./actions').actions[C]*/
const { UPDATE_CONFIG,
REQUEST_START,
SET_STARTED,
REQUEST_STOP,
SET_STOPPED,
REQUEST_RESTART,
SET_ERROR,
TRY_RECONECT,
SET_NAME,
HYDRATE}= require('./actions').actions;;
///////////////////////////////////////////////////////////////////////////////
// Declarations //
///////////////////////////////////////////////////////////////////////////////
// Object oriented `this`.
let self = false;
/**
* The socket.io client connection.
*/
let socket;
// Get the current application state.
let getState;
///////////////////////////////////////////////////////////////////////////////
// Code //
///////////////////////////////////////////////////////////////////////////////
class Communicator {
constructor(_getState) {
// singleton
if (self)
return self;
if (!_getState) {
throw new Error('Invalid getState() function.');
}
if(!_getState().config)
throw new Error('Invalid Config. Initialize config first!'); // TODO: Lookup if implemented everywhere
self = this;
// define the locals
getState = _getState;
initSocket();
return this;
}
/**
* Connection status getter. Forwards the socket-io status.
* @returns { Bool } Connection status.
*/
get connected() {
return socket.connected;
}
};
/**
* Relay an action to the server.
* @param {Object} action The action object.
*/
Communicator.prototype.sendAction = function(action){
if(!this.connected || !action.type)
return;
// TODO: Filter
/*
socket.emit('action', action);
[C]*/
let type, change, state = getState();
switch (action.type) {
case SET_STARTED:
case SET_STOPPED:
type = 'startStop',
change = {
running: state.stream.runnning ? 1 : 0,
error: state.stream.error || -1
};
break;
case SET_ERROR:
type = 'error';
change = {
running: 2,
error: state.stream.error
};
break;
case UPDATE_CONFIG:
type = 'settings';
change = {
config: state.config
};
break;
default:
return;
}
socket.emit('change', {type, change});
};
/**
* Sends a message to a command issuer on the web-interface.
* @param {String} title Title of the message.
* @param {String} type Type of the message.
* @param {String} text The message text.
* @param {String} to Recepient (socket id).
*/
Communicator.prototype.sendMessage = function(title, type, text, to){
if(!this.connected)
return;
/*socket.emit('message', {
title,
type,
text
}, to);[C]*/
socket.emit('data', {
type: 'message',
data: {
title: 'Error',
type: 'error',
text: 'Could not start SSH tunnels!'
}
}, to);
};
/**
* Sends a snapshot to a command issuer on the web-interface.
* @param {Object} snapshot Snapshot to send.
* @param {String} to Recepient (socket id).
*/
Communicator.prototype.sendSnapshot = function(snapshot, to){
if(!this.connected)
return;
socket.emit('data', {
type: 'snap', //TODO New with new software
data: snapshot
}, to);
};
module.exports = Communicator;
/**
* Utilities
*/
/**
* Initialize the socket connection and register the events.
*/
function initSocket() {
// Savety it will be called only once anyway.
socket = socketio(getState().config.master + '/pi', {
query: getState().config.unconfigured ? "unconfigured=true" : undefined
});
socket.on('connect', socketConnected);
socket.on('command', handleCommand);
socket.on('disconnect', socketDisconnected);
}
/**
* Event Handlers
*/
/**
* Handle an established connection.
*/
function socketConnected() {
logger.log(logger.importance[4], 'Connected to the master server.');
// Handle SSH Connection //TODO Implement - Action to set SSH. Auto Retry //TODO central DEFINITION
//ssh.connect();
self.sendAction({
type: HYDRATE,
data: getState()
});
}
/**
* Handle a disconnection.
*/
function socketDisconnected() {
socket.disconnect();
// And just reconnect.
logger.log(logger.importance[2], 'Lost connection to the master server.');
initSocket();
}
/**
* Handle Commands //TODO Implement
*/
function handleCommand() {
}
/**
* Redux Middleware
*/
Communicator.middleware = store => next => action => {
let result = next(action);
if(self && self.connected){
console.log("self");
self.sendAction(action); //TODO: Filter
}
return result;
};

View file

@ -11,8 +11,36 @@ const socketio = require('socket.io-client');
// Object oriented `this`.
let self = false;
// Get the current application state.
let getState;
///////////////////////////////////////////////////////////////////////////////
// Code //
///////////////////////////////////////////////////////////////////////////////
module.exports = function Communicator(){};
module.exports = function Communicator(_getState){
// singleton
if(self)
return self;
// Make `new` optional.
if(!(this instanceof Communicator))
return new Communicator();
if (!_getState) {
throw new Error('Invalid getState() function.');
}
// define the locals
getState = _getState;
init();
self = this;
return this;
};
/**
* Utilities
*/

View file

@ -33,18 +33,11 @@ const customLevels = {
// Code //
///////////////////////////////////////////////////////////////////////////////
/**
* Monkey Patching.
*/
// Monkey Patching.
/**
* Calls the logging function with arguments, if the node enviroment isn't in production mode.
*/
winston.Logger.prototype.dbgmsg = function() {
if (process.env.NODE_ENV !== 'production')
this.log.apply(undefined, arguments);
};
// Set the Colors
winston.addColors(customLevels.colors);

View file

@ -1,70 +1,119 @@
///////////////////////////////////////////////////////////////////////////////
// Reducers //
///////////////////////////////////////////////////////////////////////////////
/** Outsourced definition of the Reducers for redux.
* @see {@link http://redux.js.org/docs/basics/Reducers.html}
* @module Reducers
*/
const redux = require('redux');
const actions = require('./actions');
const { UPDATE_CONFIG,
REQUEST_START,
SET_STARTED,
REQUEST_STOP,
SET_STOPPED,
REQUEST_RESTART,
SET_ERROR,
TRY_RECONECT,
SET_NAME}= require('./actions').actions;
let reducers = {};
/**
* Set the streaming/error state.
* Set the error code. @see ffmpegCommand.js for the error code descriptions.
*/
reducers.error = function(state = -1, action) {
function error(state = false, action) {
switch (action.type) {
case actions.SET_ERROR:
if((typeof action.data) == 'undefined') {
return action.data;
break;
}
default:
return state;
}
};
/**
* Set the streaming state.
*/
reducers.streaming = function(state = 'STOPPED', action) {
switch (action.type) {
case actions.START:
case actions.STOP:
return action.type == actions.START;
break;
default:
return state;
}
};
/**
* Set the restart flag.
*
* The restart flag get's automaticly unset, as soon as the start action gets reduced.
*/
reducers.restart = function(state = false, action) {
switch (action.type) {
case actions.RESTART:
return true;
case actions.START:
case SET_ERROR:
return action.data;
case SET_STARTED:
return false;
default:
return state;
}
};
// TODO: Add central definition
/**
* Set the error handling procedure.
*/
function handleError(state = false, action) {
switch (action.type) {
case SET_STARTED:
return false;
case TRY_RECONECT:
return 'RECONNECTING';
default:
return state;
}
}
/**
* Update the config.
* Set the running flag.
* It can either be RUNNING, STARTING, STOPPING or STOPPED
*/
reducers.config = function(state = {}, action) {
function running(state = 'STOPPED', action) {
switch (action.type) {
case actions.UPDATE_CONFIG:
return Object.assign({}, state, action.data);
break;
default:
return state;
case REQUEST_START:
return 'STARTING';
case SET_STARTED:
return 'RUNNING';
case REQUEST_STOP:
return 'STOPPING';
case SET_STOPPED:
return 'STOPPED';
default:
return state;
}
}
/**
* Set the restarting flag.
*/
function restarting(state = false, action) {
switch (action.type) {
case REQUEST_RESTART:
return true;
default:
return state;
}
}
/**
* @function stream
* @description Stream Root Reducer.
*/
reducers.stream = function(state = {}, action){
return {
running: running(state.running, action),
error: error(state.error, action),
handleError: handleError(state.handleError, action),
restarting: restarting(state.restarting, action)
};
};
/**
* @function config
* @description Updates the config state.
*/
reducers.config = function(state = false, action) {
switch (action.type) {
case UPDATE_CONFIG:
return Object.assign({}, state, action.data);
default:
return state;
}
};
/**
* @function name
* @description Set the name state.
*/
reducers.name = function(state = 'Unnamed', action) {
switch (action.type) {
case SET_NAME:
return action.data;
default:
return state;
}
};

View file

@ -1,109 +0,0 @@
///////////////////////////////////////////////////////////////////////////////
// Redux Wrapper to hold the state and communicate with the Server. //
///////////////////////////////////////////////////////////////////////////////
/**
* All actions are proxied via Socket.js
*
* The connection is managed by the main module.
*/
const redux = require('redux');
///////////////////////////////////////////////////////////////////////////////
// Declarations //
///////////////////////////////////////////////////////////////////////////////
// Object oriented `this`.
let self = false;
// Reference to the store.
let store = false;
/**
* Inital State for the app.
* @property { bool } streaming Indicates if the streaming process is running.
* @property { number } error Error code. Any number outside the range of the Error array in @see ffmpeg-command.js will be treated as non error. Most conveniently set: -1.
* @property { bool } configured Indicates if the camera is configured or not.
*/
let initialState = {
streaming: false,
error: -1,
config: {}
}; // NOTE: Maybe in main.js
// Reducer
let streaming, error, config;
const reducers = redux.combineReducers({
streaming,
error,
config
});
// The socket.io connection from the main-module.
let _socket;
/**
* Actions
*/
let SEND_MESSAGE = 'SEND_MESSAGE';
let UPDATE_CONFIG = 'UPDATE_CONFIG';
let START_STOP = 'START_STOP';
let SET_ERROR = 'SET_ERROR';
///////////////////////////////////////////////////////////////////////////////
// Code //
///////////////////////////////////////////////////////////////////////////////
/**
* Wrapper to expose action creators and the store.
* @param { Object } socket Socket.io instance from the main module..
*/
module.exports = function state( socket ){
// singleton
if(self)
return self;
// Make `new` optional.
if(!(this instanceof createStore))
return new createStore(_socket);
_socket = socket;
self = this;
return this;
};
/**
* Reducers
*/
// TODO: share the code!
/**
* Set the streaming/error state.
*/
streaming = error = function(state, action) {
switch(action) {
case SET_ERROR:
case START_STOP:
return action.data;
break;
default:
return state;
}
};
/**
* Update the config.
*/
config = function(state, action) {
switch(action) {
case UPDATE_CONFIG:
return Object.assign({}, state, action.data);
break;
default:
return state;
}
}

View file

@ -1,19 +0,0 @@
///////////////////////////////////////////////////////////////////////////////
// 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 ){};

100
src/streamer.js Normal file
View file

@ -0,0 +1,100 @@
/** The central interface to the streaming process.
* @module Streamer
*/
const logger = require('./logger');
///////////////////////////////////////////////////////////////////////////////
// Declarations //
///////////////////////////////////////////////////////////////////////////////
// Object oriented `this`.
let self = false;
/**
* Inital State for the app.
* @property { Object } stream Stream Status.
* @property { bool } stream.running Indicates wether the stream is running.
* @property { number } stream.error Error code. Any number outside the range of the Error array in @see ffmpeg-command.js will be treated as non error. Most conveniently set: -1.
* @property { bool } stream.reconnecting Indicates wether the program tries to reconnect to the camera or the internet.
* @property { Object } config Configuration of the camera.
*/
let initialState = {
stream: {
running: 'STOPPED',
error: -1,
reconnecting: false,
restarting: false
},
config: {}
}; // NOTE: Maybe in main.js
/**
* The socket.io connection from the main-module.
*/
let _communicator;
/**
* Redux Actions
*/
// Reducer
const reducer = require('./reducers');
// Reference to the store of the application state.
const store = redux.createStore(reducer, initialState);
///////////////////////////////////////////////////////////////////////////////
// Code //
///////////////////////////////////////////////////////////////////////////////
// TODO: Log state changes.
/**
* @class Streamer
* The central control Object (singleton for now) which has the permission to change state.
*
* @constructor
* @param { Object } communicator A communicator object. @see communicator.js - Optional //TODO: Find proper tag.
*/
function Streamer(communicator = false) {
// singleton
if (self)
return self;
// Make `new` optional.
if (!(this instanceof Streamer))
return new Streamer(_communicator);
_communicator = communicator;
self = this;
return this;
};
module.exports = Streamer;
/**
* Set the communicator object and enable the state sync with the server.
* @param {Object} communicator A communicator object. @see communicator.js
*/
Streamer.prototype.setCommunicator = function(communicator) {
if (_communicator)
_communicator = communicator;
else
logger.dbgmsdg("Invalid Communicator");
};
/**
* Get the current config.
* @returns { * } The current config.
*/
Streamer.prototype.getConfig = function(){
return store.getStreamer().config;
};
/**
* Utilities
*/

View file

@ -3,6 +3,8 @@
*/
const redux = require('redux');
const ReduxThunk = require('redux-thunk').default;
const logger = require('./logger');
///////////////////////////////////////////////////////////////////////////////
@ -22,23 +24,20 @@ let self = false;
*/
let initialState = {
stream: {
running: false,
running: 'STOPPED',
error: -1,
reconnecting: false
reconnecting: false,
restarting: false
},
config: {}
}; // NOTE: Maybe in main.js
// The socket.io connection from the main-module.
let _communicator;
// Actions
const actions = require('./actions');
// Action Creators
const creators = require('./actionCreators');
const dispatchers = redux.bindActionCreators(creators);
/**
* Redux Actions
*/
// Reducer
const reducer = require('./reducers');
@ -52,20 +51,20 @@ const store = redux.createStore(reducer, initialState);
// TODO: Log state changes.
/**
* The central redux state manager. The module exposes central action dispatchers. Updates to the state get communicated to the server.
*
* @class Streamer
* The central control Object (singleton for now) which has the permission to change state.
*
* @constructor
* @member [Action Dispatchers] The action creators (@see actionCreators.js) are bound to the dispatch function and made member of the State object.
* @param { Object } communicator A communicator object. @see communicator.js - Optional //TODO: Find proper tag.
*/
function State(communicator = false) {
function Streamer(communicator = false) {
// singleton
if (self)
return self;
// Make `new` optional.
if (!(this instanceof State))
return new State(_communicator);
if (!(this instanceof Streamer))
return new Streamer(_communicator);
_communicator = communicator;
@ -79,7 +78,7 @@ applyDispatchers(creators);
* Set the communicator object and enable the state sync with the server.
* @param {Object} communicator A communicator object. @see communicator.js
*/
State.prototype.setCommunicator = function(communicator) {
Streamer.prototype.setCommunicator = function(communicator) {
if (_communicator)
_communicator = communicator;
else
@ -90,16 +89,16 @@ State.prototype.setCommunicator = function(communicator) {
* Wrapper for redux.
* @returns { Object } The current state.
*/
State.prototype.getState = function(){
return store.getState();
Streamer.prototype.getStreamer = function(){
return store.getStreamer();
};
/**
* Get the current config.
* @returns { * } The current config.
*/
State.prototype.getConfig = function(){
return store.getState().config;
Streamer.prototype.getConfig = function(){
return store.getStreamer().config;
};
/**
@ -107,23 +106,23 @@ State.prototype.getConfig = function(){
*/
/**
* Adds prototypes for the action dispatchers to the State.
* Adds prototypes for the action dispatchers to the Streamer.
* @param { Object } creators
* @throws { Error } If a method with the creators name already exists. @see actionCreators.js
*/
function applyDispatchers( creators ){
for(let creator in creators){
if(State.prototype[creator])
throw new Error(`State has already a meber '${creator}'.`);
if(Streamer.prototype[creator])
throw new Error(`Streamer has already a meber '${creator}'.`);
State.prototype[creator] = function(){
Streamer.prototype[creator] = function(){
store.dispatch(creators[creator].apply(undefined, arguments));
};
}
}
module.exports = State;
module.exports = Streamer;

42
src/utility/config.js~ Normal file
View file

@ -0,0 +1,42 @@
/** Config read/write utilities.
* @module Utilities/Config
*/
const Promise = require('promise');
const fs = require('fs');
const logger = require('../logger.js');
// TODO: Config path in defs
module.exports = {};
// TODO Default CFG
/**
* Read the JSON config file.
* @returns { Object | bool } Returns the parsed config or false in case of an error.
*/
module.exports.read = function() {
try {
return JSON.parse(fs.readFileSync(__dirname + '/../../config.js')); //TODO: Nicer
} catch (e) {
return false; // Log that.
}
};
/**
* Writes the config to disk.
* @param {Object} state A function to retrieve the current state. This makes it harder to accidentially write arbitrary code.
* @returns { Promise } A promise for writing the configuration.
*/
module.exports.write = function( getState ){
return new Promise((resolve, reject) => {
fs.writeFile(__dirname + '/config.js',
JSON.stringify(getState().config, undefined, 2),
err => {
if(err)
reject(err);
else
resolve();
});
});
};

518
test.js Normal file
View file

@ -0,0 +1,518 @@
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;
let mustBe = false;
let restart = false;
let config, source, snapSource, stopTimeout;
const importance = ['normal', 'info', 'warning', 'danger', 'success'];
var customLevels = {
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 = {
status: 0,
error: -1
}
let errors = ['Camera Disconnected', 'YoutTube Disconnected', 'Wrong ffmpeg executable.'];
let cmd;
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(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(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(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(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(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(importance[1], "Stop Command!");
mustBe = true;
stopFFMPEG();
socket.emit('data', {
type: 'message',
data: {
title: 'Success',
type: 'success',
text: 'Stopped!'
}
}, command.sender);
} else {
logger.log(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(importance[1], "Restart Command!");
mustBe = true;
restart = true;
stopFFMPEG();
} else {
logger.log(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(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');
console.log('test');
initSocket();
status.name = config.name;
if (!cmd)
spawn();
} else {
console.log('test');
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() {
console.log('herde')
logger.log(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();