1889 lines
88 KiB
JavaScript
1889 lines
88 KiB
JavaScript
const fs = require('fs');
|
|
const events = require('events');
|
|
const spawn = require('child_process').spawn;
|
|
const exec = require('child_process').exec;
|
|
const Mp4Frag = require('mp4frag');
|
|
const onvif = require("shinobi-onvif");
|
|
const treekill = require('tree-kill');
|
|
const request = require('request');
|
|
const connectionTester = require('connection-tester')
|
|
const SoundDetection = require('shinobi-sound-detection')
|
|
const async = require("async");
|
|
const URL = require('url')
|
|
const {
|
|
Worker
|
|
} = require('worker_threads');
|
|
const { copyObject, createQueue, queryStringToObject, createQueryStringFromObject } = require('./common.js')
|
|
module.exports = function(s,config,lang){
|
|
const {
|
|
probeMonitor,
|
|
getStreamInfoFromProbe,
|
|
applyPartialToConfiguration,
|
|
createWarningsForConfiguration,
|
|
buildMonitorConfigPartialFromWarnings,
|
|
splitForFFPMEG,
|
|
} = require('./ffmpeg/utils.js')(s,config,lang)
|
|
const {
|
|
processKill,
|
|
cameraDestroy,
|
|
monitorConfigurationMigrator,
|
|
} = require('./monitor/utils.js')(s,config,lang)
|
|
const {
|
|
addEventDetailsToString,
|
|
closeEventBasedRecording,
|
|
triggerEvent,
|
|
} = require('./events/utils.js')(s,config,lang)
|
|
const {
|
|
setPresetForCurrentPosition
|
|
} = require('./control/ptz.js')(s,config,lang)
|
|
const {
|
|
scanForOrphanedVideos
|
|
} = require('./video/utils.js')(s,config,lang)
|
|
const startMonitorInQueue = createQueue(1, 3)
|
|
s.initiateMonitorObject = function(e){
|
|
if(!s.group[e.ke]){s.group[e.ke]={}};
|
|
if(!s.group[e.ke].activeMonitors){s.group[e.ke].activeMonitors={}}
|
|
if(!s.group[e.ke].activeMonitors[e.mid]){s.group[e.ke].activeMonitors[e.mid]={}}
|
|
const activeMonitor = s.group[e.ke].activeMonitors[e.mid]
|
|
|
|
if(!activeMonitor.streamIn){activeMonitor.streamIn={}};
|
|
if(!activeMonitor.emitterChannel){activeMonitor.emitterChannel={}};
|
|
if(!activeMonitor.mp4frag){activeMonitor.mp4frag={}};
|
|
if(!activeMonitor.firstStreamChunk){activeMonitor.firstStreamChunk={}};
|
|
if(!activeMonitor.contentWriter){activeMonitor.contentWriter={}};
|
|
if(!activeMonitor.childNodeStreamWriters){activeMonitor.childNodeStreamWriters={}};
|
|
if(!activeMonitor.eventBasedRecording){activeMonitor.eventBasedRecording={}};
|
|
if(!activeMonitor.watch){activeMonitor.watch={}};
|
|
if(!activeMonitor.fixingVideos){activeMonitor.fixingVideos={}};
|
|
// if(!activeMonitor.viewerConnection){activeMonitor.viewerConnection={}};
|
|
// if(!activeMonitor.viewerConnectionCount){activeMonitor.viewerConnectionCount=0};
|
|
if(!activeMonitor.parsedObjects){activeMonitor.parsedObjects={}};
|
|
if(!activeMonitor.detector_motion_count){activeMonitor.detector_motion_count=[]};
|
|
if(!activeMonitor.eventsCounted){activeMonitor.eventsCounted = {}};
|
|
if(!activeMonitor.isStarted){activeMonitor.isStarted = false};
|
|
if(!activeMonitor.pipe4BufferPieces){activeMonitor.pipe4BufferPieces = []};
|
|
if(!activeMonitor.secondaryDetectorOutput){activeMonitor.secondaryDetectorOutput = new events.EventEmitter()};
|
|
if(activeMonitor.delete){clearTimeout(activeMonitor.delete)}
|
|
if(!s.group[e.ke].rawMonitorConfigurations){s.group[e.ke].rawMonitorConfigurations={}}
|
|
if(!activeMonitor.criticalErrors)activeMonitor.criticalErrors = {
|
|
"404": false,
|
|
"453": false,
|
|
"500": false,
|
|
}
|
|
s.onMonitorInitExtensions.forEach(function(extender){
|
|
extender(e)
|
|
})
|
|
}
|
|
s.sendMonitorStatus = function(e){
|
|
s.group[e.ke].activeMonitors[e.id].monitorStatus = `${e.status}`
|
|
s.group[e.ke].activeMonitors[e.id].monitorStatusCode = `${e.code}`
|
|
s.tx(Object.assign(e,{f:'monitor_status'}),'GRP_'+e.ke)
|
|
}
|
|
s.getMonitorCpuUsage = function(e,callback){
|
|
if(s.group[e.ke].activeMonitors[e.mid] && s.group[e.ke].activeMonitors[e.mid].spawn){
|
|
const getUsage = function(callback2){
|
|
s.readFile("/proc/" + s.group[e.ke].activeMonitors[e.mid].spawn.pid + "/stat", function(err, data){
|
|
if(!err){
|
|
const elems = data.toString().split(' ');
|
|
const utime = parseInt(elems[13]);
|
|
const stime = parseInt(elems[14]);
|
|
|
|
callback2(utime + stime);
|
|
}else{
|
|
clearInterval(0)
|
|
}
|
|
})
|
|
}
|
|
getUsage(function(startTime){
|
|
setTimeout(function(){
|
|
getUsage(function(endTime){
|
|
const delta = endTime - startTime;
|
|
const percentage = 100 * (delta / 10000);
|
|
callback(percentage)
|
|
})
|
|
}, 1000)
|
|
})
|
|
}else{
|
|
callback(0)
|
|
}
|
|
}
|
|
s.buildMonitorUrl = function(e,noPath){
|
|
var authd = ''
|
|
var url
|
|
if(e.details.muser&&e.details.muser!==''&&e.host.indexOf('@')===-1) {
|
|
e.username = e.details.muser
|
|
e.password = e.details.mpass
|
|
authd = e.details.muser+':'+e.details.mpass+'@'
|
|
}
|
|
if(e.port==80&&e.details.port_force!=='1'){e.porty=''}else{e.porty=':'+e.port}
|
|
url = e.protocol+'://'+authd+e.host+e.porty
|
|
if(noPath !== true)url += e.path
|
|
return url
|
|
}
|
|
s.cleanMonitorObjectForDatabase = function(dirtyMonitor){
|
|
var cleanMonitor = {}
|
|
var acceptedFields = ['mid','ke','name','shto','shfr','details','type','ext','protocol','host','path','port','fps','mode','width','height']
|
|
Object.keys(dirtyMonitor).forEach(function(key){
|
|
if(acceptedFields.indexOf(key) > -1){
|
|
cleanMonitor[key] = dirtyMonitor[key]
|
|
}
|
|
})
|
|
return cleanMonitor
|
|
}
|
|
s.cleanMonitorObject = function(e){
|
|
x={keys:Object.keys(e),ar:{}};
|
|
x.keys.forEach(function(v){
|
|
if(v!=='last_frame'&&v!=='record'&&v!=='spawn'&&v!=='running'&&(v!=='time'&&typeof e[v]!=='function')){x.ar[v]=e[v];}
|
|
});
|
|
return x.ar;
|
|
}
|
|
s.getStreamsDirectory = (monitor) => {
|
|
return s.dir.streams + monitor.ke + '/' + monitor.mid + '/'
|
|
}
|
|
s.getRawSnapshotFromMonitor = function(monitor,options){
|
|
return new Promise((resolve,reject) => {
|
|
options = options instanceof Object ? options : {flags: ''}
|
|
s.checkDetails(monitor)
|
|
var inputOptions = []
|
|
var outputOptions = []
|
|
var streamDir = s.dir.streams + monitor.ke + '/' + monitor.mid + '/'
|
|
var url = options.url
|
|
var secondsInward = options.secondsInward || '0'
|
|
if(secondsInward.length === 1)secondsInward = '0' + secondsInward
|
|
var dynamicTimeout = (secondsInward * 1000) + 5000
|
|
if(options.flags)outputOptions.push(options.flags)
|
|
const checkExists = function(streamDir,callback){
|
|
s.fileStats(streamDir,function(err){
|
|
var response = false
|
|
if(err){
|
|
// s.debugLog(err)
|
|
}else{
|
|
response = true
|
|
}
|
|
callback(response)
|
|
})
|
|
}
|
|
const noIconChecks = function(){
|
|
const runExtraction = function(){
|
|
var sendTempImage = function(){
|
|
fs.readFile(temporaryImageFile,function(err,buffer){
|
|
if(!err){
|
|
resolve({
|
|
screenShot: buffer,
|
|
isStaticFile: false
|
|
})
|
|
fs.unlink(temporaryImageFile,function(){})
|
|
}else{
|
|
resolve({
|
|
screenShot: null,
|
|
isStaticFile: false
|
|
})
|
|
}
|
|
})
|
|
}
|
|
try{
|
|
var snapBuffer = []
|
|
var temporaryImageFile = streamDir + s.gid(5) + '.jpg'
|
|
var iconImageFile = streamDir + 'icon.jpg'
|
|
var ffmpegCmd = splitForFFPMEG(`-loglevel warning -re -probesize 100000 -analyzeduration 100000 ${inputOptions.join(' ')} -i "${url}" ${outputOptions.join(' ')} -f image2 -an -vf "fps=1" -vframes 1 "${temporaryImageFile}"`)
|
|
checkExists(streamDir, function(success) {
|
|
if (success === false) {
|
|
fs.mkdirSync(streamDir, {recursive: true}, (err) => {s.debugLog(err)})
|
|
}
|
|
})
|
|
const snapProcess = new Worker(__dirname + '/cameraThread/snapshot.js', {
|
|
workerData: {
|
|
jsonData: {
|
|
cmd: ffmpegCmd,
|
|
temporaryImageFile: temporaryImageFile,
|
|
iconImageFile: iconImageFile,
|
|
useIcon: options.useIcon,
|
|
rawMonitorConfig: s.group[monitor.ke].rawMonitorConfigurations[monitor.mid],
|
|
},
|
|
ffmpegAbsolutePath: config.ffmpegDir,
|
|
}
|
|
});
|
|
snapProcess.on('message', function(data){
|
|
s.debugLog(data)
|
|
})
|
|
snapProcess.on('error', (data) => {
|
|
console.log(data)
|
|
processKill(snapProcess)
|
|
})
|
|
snapProcess.on('exit', (code) => {
|
|
clearTimeout(snapProcessTimeout)
|
|
sendTempImage()
|
|
})
|
|
var snapProcessTimeout = setTimeout(function(){
|
|
processKill(snapProcess)
|
|
},dynamicTimeout)
|
|
}catch(err){
|
|
console.log(err)
|
|
}
|
|
}
|
|
if(url){
|
|
runExtraction()
|
|
}else{
|
|
checkExists(streamDir + 's.jpg',function(success){
|
|
if(success === false){
|
|
checkExists(streamDir + 'detectorStream.m3u8',function(success){
|
|
if(success === false){
|
|
checkExists(streamDir + 's.m3u8',function(success){
|
|
if(success === false){
|
|
switch(monitor.type){
|
|
case'h264':
|
|
switch(monitor.protocol){
|
|
case'rtsp':
|
|
if(
|
|
monitor.details.rtsp_transport
|
|
&& monitor.details.rtsp_transport !== ''
|
|
&& monitor.details.rtsp_transport !== 'no'
|
|
){
|
|
inputOptions.push('-rtsp_transport ' + monitor.details.rtsp_transport)
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
url = s.buildMonitorUrl(monitor)
|
|
}else{
|
|
outputOptions.push(`-ss 00:00:${secondsInward}`)
|
|
url = streamDir + 's.m3u8'
|
|
}
|
|
runExtraction()
|
|
})
|
|
}else{
|
|
outputOptions.push(`-ss 00:00:${secondsInward}`)
|
|
url = streamDir + 'detectorStream.m3u8'
|
|
runExtraction()
|
|
}
|
|
})
|
|
}else{
|
|
s.readFile(streamDir + 's.jpg',function(err,snapBuffer){
|
|
if(err){
|
|
sendTempImage()
|
|
}else{
|
|
resolve({
|
|
screenShot: snapBuffer,
|
|
isStaticFile: true
|
|
})
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
if(options.useIcon === true){
|
|
checkExists(streamDir + 'icon.jpg',function(success){
|
|
if(success === false){
|
|
noIconChecks()
|
|
}else{
|
|
var snapBuffer = fs.readFileSync(streamDir + 'icon.jpg')
|
|
resolve({
|
|
screenShot: snapBuffer,
|
|
isStaticFile: false
|
|
})
|
|
}
|
|
})
|
|
}else{
|
|
noIconChecks()
|
|
}
|
|
})
|
|
}
|
|
s.mergeDetectorBufferChunks = function(monitor,callback){
|
|
return new Promise((resolve,reject) => {
|
|
var pathDir = s.dir.streams+monitor.ke+'/'+monitor.id+'/'
|
|
var mergedFile = s.formattedTime()+'.mp4'
|
|
var mergedFilepath = pathDir+mergedFile
|
|
fs.readdir(pathDir,function(err,streamDirItems){
|
|
var items = []
|
|
var copiedItems = []
|
|
var videoLength = s.group[monitor.ke].rawMonitorConfigurations[monitor.id].details.detector_send_video_length
|
|
if(!videoLength || videoLength === '')videoLength = '10'
|
|
if(videoLength.length === 1)videoLength = '0' + videoLength
|
|
var createMerged = function(copiedItems){
|
|
var allts = pathDir+items.join('_')
|
|
s.fileStats(allts,function(err,stats){
|
|
if(err){
|
|
//not exist
|
|
var cat = 'cat '+copiedItems.join(' ')+' > '+allts
|
|
exec(cat,function(){
|
|
var merger = spawn(config.ffmpegDir,splitForFFPMEG(('-re -i '+allts+' -acodec copy -vcodec copy -t 00:00:' + videoLength + ' '+pathDir+mergedFile)))
|
|
merger.stderr.on('data',function(data){
|
|
s.userLog(monitor,{type:"Buffer Merge",msg:data.toString()})
|
|
})
|
|
merger.on('close',function(){
|
|
s.file('delete',allts)
|
|
copiedItems.forEach(function(copiedItem){
|
|
s.file('delete',copiedItem)
|
|
})
|
|
setTimeout(function(){
|
|
s.file('delete',mergedFilepath)
|
|
},1000 * 60 * 3)
|
|
delete(merger)
|
|
if(callback)callback(mergedFilepath,mergedFile)
|
|
resolve({
|
|
filePath: mergedFilepath,
|
|
filename: mergedFile,
|
|
})
|
|
})
|
|
})
|
|
}else{
|
|
//file exist
|
|
if(callback)callback(mergedFilepath,mergedFile)
|
|
resolve({
|
|
filePath: mergedFilepath,
|
|
filename: mergedFile,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
streamDirItems.forEach(function(filename){
|
|
if(filename.indexOf('detectorStream') > -1 && filename.indexOf('.m3u8') === -1){
|
|
items.push(filename)
|
|
}
|
|
})
|
|
items.sort()
|
|
// items = items.slice(items.length - 5,items.length)
|
|
items.forEach(function(filename){
|
|
try{
|
|
var tempFilename = filename.split('.')
|
|
tempFilename[0] = tempFilename[0] + 'm'
|
|
tempFilename = tempFilename.join('.')
|
|
var tempWriteStream = fs.createWriteStream(pathDir+tempFilename)
|
|
tempWriteStream.on('finish', function(){
|
|
copiedItems.push(pathDir+tempFilename)
|
|
if(copiedItems.length === items.length){
|
|
createMerged(copiedItems.sort())
|
|
}
|
|
})
|
|
fs.createReadStream(pathDir+filename).pipe(tempWriteStream)
|
|
}catch(err){
|
|
|
|
}
|
|
})
|
|
})
|
|
})
|
|
}
|
|
s.mergeRecordedVideos = function(videoRows,groupKey,callback){
|
|
var tempDir = s.dir.streams + groupKey + '/'
|
|
var pathDir = s.dir.fileBin + groupKey + '/'
|
|
var streamDirItems = fs.readdirSync(pathDir)
|
|
var items = []
|
|
var mergedFile = []
|
|
videoRows.forEach(function(video){
|
|
var filepath = s.getVideoDirectory(video) + s.formattedTime(video.time) + '.' + video.ext
|
|
if(
|
|
filepath.indexOf('.mp4') > -1
|
|
// || filename.indexOf('.webm') > -1
|
|
){
|
|
mergedFile.push(s.formattedTime(video.time))
|
|
items.push(filepath)
|
|
}
|
|
})
|
|
mergedFile.sort()
|
|
mergedFile = mergedFile.join('_') + '.mp4'
|
|
var mergedFilepath = pathDir + mergedFile
|
|
var mergedRawFilepath = pathDir + 'raw_' + mergedFile
|
|
items.sort()
|
|
s.fileStats(mergedFilepath,function(err,stats){
|
|
if(err){
|
|
//not exist
|
|
var tempScriptPath = tempDir + s.gid(5) + '.sh'
|
|
var cat = 'cat '+items.join(' ')+' > '+mergedRawFilepath
|
|
fs.writeFileSync(tempScriptPath,cat,'utf8')
|
|
exec('sh ' + tempScriptPath,function(){
|
|
s.userLog({
|
|
ke: groupKey,
|
|
mid: '$USER'
|
|
},{type:lang['Videos Merge'],msg:mergedFile})
|
|
var merger = spawn(config.ffmpegDir,splitForFFPMEG(('-re -loglevel warning -i ' + mergedRawFilepath + ' -acodec copy -vcodec copy ' + mergedFilepath)))
|
|
merger.stderr.on('data',function(data){
|
|
s.userLog({
|
|
ke: groupKey,
|
|
mid: '$USER'
|
|
},{type:lang['Videos Merge'],msg:data.toString()})
|
|
})
|
|
merger.on('close',function(){
|
|
s.file('delete',mergedRawFilepath)
|
|
s.file('delete',tempScriptPath)
|
|
setTimeout(function(){
|
|
s.fileStats(mergedFilepath,function(err,stats){
|
|
if(!err)s.file('delete',mergedFilepath)
|
|
})
|
|
},1000 * 60 * 60 * 24)
|
|
delete(merger)
|
|
callback(mergedFilepath,mergedFile)
|
|
})
|
|
})
|
|
}else{
|
|
//file exist
|
|
callback(mergedFilepath,mergedFile)
|
|
}
|
|
})
|
|
return items
|
|
}
|
|
const checkObjectsInDetails = function(e){
|
|
//parse Objects
|
|
(['detector_cascades','cords','detector_filters','input_map_choices']).forEach(function(v){
|
|
if(e.details && e.details[v]){
|
|
try{
|
|
if(!e.details[v] || e.details[v] === '')e.details[v] = '{}'
|
|
e.details[v] = s.parseJSON(e.details[v])
|
|
if(!e.details[v])e.details[v] = {}
|
|
s.group[e.ke].activeMonitors[e.id].details = e.details
|
|
switch(v){
|
|
case'cords':
|
|
s.group[e.ke].activeMonitors[e.id].parsedObjects[v] = Object.values(s.parseJSON(e.details[v]))
|
|
break;
|
|
default:
|
|
s.group[e.ke].activeMonitors[e.id].parsedObjects[v] = s.parseJSON(e.details[v])
|
|
break;
|
|
}
|
|
}catch(err){
|
|
|
|
}
|
|
}
|
|
});
|
|
//parse Arrays
|
|
(['stream_channels','input_maps']).forEach(function(v){
|
|
if(e.details&&e.details[v]&&(e.details[v] instanceof Array)===false){
|
|
try{
|
|
e.details[v]=JSON.parse(e.details[v]);
|
|
if(!e.details[v])e.details[v]=[];
|
|
}catch(err){
|
|
e.details[v]=[];
|
|
}
|
|
}
|
|
});
|
|
}
|
|
s.cameraControlOptionsFromUrl = function(e,monitorConfig){
|
|
URLobject = URL.parse(e)
|
|
if(monitorConfig.details.control_url_method === 'ONVIF' && monitorConfig.details.control_base_url === ''){
|
|
if(monitorConfig.details.onvif_port === ''){
|
|
monitorConfig.details.onvif_port = 8000
|
|
}
|
|
URLobject.port = monitorConfig.details.onvif_port
|
|
}else if(!URLobject.port){
|
|
URLobject.port = 80
|
|
}
|
|
const options = {
|
|
host: URLobject.hostname,
|
|
port: URLobject.port,
|
|
method: monitorConfig.details.control_url_method
|
|
}
|
|
const queryStringObjects = queryStringToObject(URLobject.query || "")
|
|
if (queryStringObjects && queryStringObjects.postData) {
|
|
options.postData = decodeURIComponent(queryStringObjects.postData)
|
|
options.path = URLobject.pathname + '?' + decodeURIComponent(createQueryStringFromObject(Object.assign(queryStringObjects,{postData: null})))
|
|
} else if(URLobject.query){
|
|
options.path = URLobject.pathname + '?' + URLobject.query
|
|
} else {
|
|
options.path = URLobject.pathname
|
|
}
|
|
if(URLobject.username&&URLobject.password){
|
|
options.username = URLobject.username
|
|
options.password = URLobject.password
|
|
options.auth=URLobject.username+':'+URLobject.password
|
|
}else if(URLobject.auth){
|
|
var auth = URLobject.auth.split(':')
|
|
options.auth=URLobject.auth
|
|
options.username = auth[0]
|
|
options.password = auth[1]
|
|
}
|
|
return options
|
|
}
|
|
s.cameraSendSnapshot = async (e,options) => {
|
|
options = Object.assign({
|
|
flags: '-s 500x500'
|
|
},options || {})
|
|
s.checkDetails(e)
|
|
if(e.ke && config.doSnapshot === true){
|
|
if(s.group[e.ke] && s.group[e.ke].rawMonitorConfigurations && s.group[e.ke].rawMonitorConfigurations[e.mid] && s.group[e.ke].rawMonitorConfigurations[e.mid].mode !== 'stop'){
|
|
if(s.group[e.ke].activeMonitors[e.mid].onvifConnection){
|
|
const screenShot = await s.getSnapshotFromOnvif({
|
|
username: onvifUsername,
|
|
password: onvifPassword,
|
|
uri: cameraResponse.uri,
|
|
});
|
|
s.tx({
|
|
f: 'monitor_snapshot',
|
|
snapshot: screenShot.toString('base64'),
|
|
snapshot_format: 'b64',
|
|
mid: e.mid,
|
|
ke: e.ke
|
|
},'GRP_'+e.ke)
|
|
}else{
|
|
var pathDir = s.dir.streams+e.ke+'/'+e.mid+'/'
|
|
const {screenShot, isStaticFile} = await s.getRawSnapshotFromMonitor(s.group[e.ke].rawMonitorConfigurations[e.mid],options)
|
|
if(screenShot){
|
|
s.tx({
|
|
f: 'monitor_snapshot',
|
|
snapshot: screenShot.toString('base64'),
|
|
snapshot_format: 'b64',
|
|
mid: e.mid,
|
|
ke: e.ke
|
|
},'GRP_'+e.ke)
|
|
}else{
|
|
s.debugLog('Damaged Snapshot Data')
|
|
s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
|
|
}
|
|
}
|
|
}else{
|
|
s.tx({f:'monitor_snapshot',snapshot:'Disabled',snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
|
|
}
|
|
}else{
|
|
s.tx({f:'monitor_snapshot',snapshot:e.mon.name,snapshot_format:'plc',mid:e.mid,ke:e.ke},'GRP_'+e.ke)
|
|
}
|
|
}
|
|
s.getCameraSnapshot = async (e,options) => {
|
|
const getDefaultImage = async () => {
|
|
return await fs.promises.readFile(config.defaultMjpeg)
|
|
}
|
|
options = Object.assign({
|
|
flags: '-s 500x500'
|
|
},options || {})
|
|
if(e.ke && config.doSnapshot === true){
|
|
if(s.group[e.ke] && s.group[e.ke].rawMonitorConfigurations && s.group[e.ke].rawMonitorConfigurations[e.mid] && s.group[e.ke].rawMonitorConfigurations[e.mid].mode !== 'stop'){
|
|
var pathDir = s.dir.streams+e.ke+'/'+e.mid+'/'
|
|
const {screenShot, isStaticFile} = await s.getRawSnapshotFromMonitor(s.group[e.ke].rawMonitorConfigurations[e.mid],options)
|
|
if(screenShot){
|
|
return screenShot
|
|
}else{
|
|
return await getDefaultImage()
|
|
}
|
|
}else{
|
|
return await getDefaultImage()
|
|
}
|
|
}else{
|
|
return await getDefaultImage()
|
|
}
|
|
}
|
|
const createRecordingDirectory = function(e,callback){
|
|
var directory
|
|
if(e.details && e.details.dir && e.details.dir !== '' && config.childNodes.mode !== 'child'){
|
|
//addStorage choice
|
|
directory = s.checkCorrectPathEnding(e.details.dir) + e.ke + '/'
|
|
fs.mkdir(directory,function(err){
|
|
s.handleFolderError(err)
|
|
directory = directory + e.id + '/'
|
|
fs.mkdir(directory,function(err){
|
|
s.handleFolderError(err)
|
|
callback(err,directory)
|
|
})
|
|
})
|
|
}else{
|
|
//MAIN videos dir
|
|
directory = s.dir.videos + e.ke + '/'
|
|
fs.mkdir(directory,function(err){
|
|
s.handleFolderError(err)
|
|
directory = s.dir.videos + e.ke + '/' + e.id + '/'
|
|
fs.mkdir(directory,function(err){
|
|
s.handleFolderError(err)
|
|
callback(err,directory)
|
|
})
|
|
})
|
|
}
|
|
}
|
|
const createTimelapseDirectory = function(e,callback){
|
|
var directory = s.getTimelapseFrameDirectory(e)
|
|
fs.mkdir(directory,function(err){
|
|
s.handleFolderError(err)
|
|
callback(err,directory)
|
|
})
|
|
}
|
|
const createFileBinDirectory = function(e,callback){
|
|
var directory = s.dir.fileBin + e.ke + '/'
|
|
fs.mkdir(directory,function(err){
|
|
s.handleFolderError(err)
|
|
directory = s.dir.fileBin + e.ke + '/' + e.id + '/'
|
|
fs.mkdir(directory,function(err){
|
|
s.handleFolderError(err)
|
|
callback(err,directory)
|
|
})
|
|
})
|
|
}
|
|
const createStreamDirectory = function(e,callback){
|
|
callback = callback || function(){}
|
|
var directory = s.dir.streams + e.ke + '/'
|
|
fs.mkdir(directory,function(err){
|
|
directory = s.dir.streams + e.ke + '/' + e.id + '/'
|
|
s.handleFolderError(err)
|
|
fs.mkdir(directory,function(err){
|
|
if (err){
|
|
s.handleFolderError(err)
|
|
s.file('deleteFolder',directory + '*',function(err){
|
|
callback(err,directory)
|
|
})
|
|
}else{
|
|
callback(err,directory)
|
|
}
|
|
})
|
|
})
|
|
}
|
|
const createCameraFolders = function(e,callback){
|
|
//set the recording directory
|
|
var activeMonitor = s.group[e.ke].activeMonitors[e.id]
|
|
createStreamDirectory(e,function(err,directory){
|
|
activeMonitor.sdir = directory
|
|
e.sdir = directory
|
|
createRecordingDirectory(e,function(err,directory){
|
|
activeMonitor.dir = directory
|
|
e.dir = directory
|
|
createTimelapseDirectory(e,function(err,directory){
|
|
activeMonitor.dirTimelapse = directory
|
|
e.dirTimelapse = directory
|
|
createFileBinDirectory(e,function(err){
|
|
if(callback)callback()
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|
|
const forceMonitorRestart = (monitor,restartMessage) => {
|
|
const monitorConfig = Object.assign(s.group[monitor.ke].rawMonitorConfigurations[monitor.mid],{})
|
|
s.sendMonitorStatus({
|
|
id: monitor.mid,
|
|
ke: monitor.ke,
|
|
status: lang.Restarting,
|
|
code: 4,
|
|
})
|
|
launchMonitorProcesses(monitorConfig)
|
|
s.userLog({
|
|
ke: monitor.ke,
|
|
mid: monitor.mid,
|
|
},restartMessage)
|
|
scanForOrphanedVideos({
|
|
ke: monitor.ke,
|
|
mid: monitor.mid,
|
|
},{
|
|
forceCheck: true,
|
|
checkMax: 2
|
|
})
|
|
}
|
|
s.stripAuthFromHost = function(e){
|
|
var host = e.host.split('@');
|
|
if(host[1]){
|
|
//username and password found
|
|
host = host[1]
|
|
}else{
|
|
//no username or password in `host` string
|
|
host = host[0]
|
|
}
|
|
return host
|
|
}
|
|
const resetRecordingCheck = function(e){
|
|
clearTimeout(s.group[e.ke].activeMonitors[e.id].recordingChecker)
|
|
const segmentLengthInMinutes = !isNaN(parseFloat(e.details.cutoff)) ? parseFloat(e.details.cutoff) : '15'
|
|
const segmentLength = e.type === 'dashcam' ? segmentLengthInMinutes * 100 : segmentLengthInMinutes
|
|
s.group[e.ke].activeMonitors[e.id].recordingChecker = setTimeout(function(){
|
|
if(s.group[e.ke].activeMonitors[e.id].isStarted === true && s.group[e.ke].rawMonitorConfigurations[e.id].mode === 'record'){
|
|
forceMonitorRestart({
|
|
ke: e.ke,
|
|
mid: e.id,
|
|
},{
|
|
type: lang['Camera is not recording'],
|
|
msg: {
|
|
msg: lang['Restarting Process']
|
|
}
|
|
})
|
|
}
|
|
},60000 * segmentLength * 1.3);
|
|
}
|
|
const resetStreamCheck = function(e){
|
|
clearTimeout(s.group[e.ke].activeMonitors[e.id].streamChecker)
|
|
s.group[e.ke].activeMonitors[e.id].streamChecker = setTimeout(function(){
|
|
if(s.group[e.ke].activeMonitors[e.id] && s.group[e.ke].activeMonitors[e.id].isStarted === true){
|
|
forceMonitorRestart({
|
|
ke: e.ke,
|
|
mid: e.id,
|
|
},{
|
|
type: lang['Camera is not streaming'],
|
|
msg: {
|
|
msg: lang['Restarting Process']
|
|
}
|
|
})
|
|
}
|
|
},60000*1);
|
|
}
|
|
const onDetectorJpegOutputAlone = (e,d) => {
|
|
if(s.isAtleatOneDetectorPluginConnected){
|
|
s.ocvTx({
|
|
f: 'frame',
|
|
mon: s.group[e.ke].rawMonitorConfigurations[e.id].details,
|
|
ke: e.ke,
|
|
id: e.id,
|
|
time: s.formattedTime(),
|
|
frame: d
|
|
})
|
|
}
|
|
}
|
|
const onDetectorJpegOutputSecondary = (e,buffer) => {
|
|
if(s.isAtleatOneDetectorPluginConnected){
|
|
const activeMonitor = s.group[e.ke].activeMonitors[e.id || e.mid]
|
|
const theArray = activeMonitor.pipe4BufferPieces
|
|
theArray.push(buffer)
|
|
if(buffer[buffer.length-2] === 0xFF && buffer[buffer.length-1] === 0xD9){
|
|
activeMonitor.secondaryDetectorOutput.emit('data',Buffer.concat(theArray))
|
|
activeMonitor.pipe4BufferPieces = []
|
|
}
|
|
}
|
|
}
|
|
const createCameraFfmpegProcess = (e) => {
|
|
//launch ffmpeg (main)
|
|
s.tx({
|
|
f: 'monitor_starting',
|
|
mode: e.functionMode,
|
|
mid: e.id,
|
|
time: s.formattedTime()
|
|
},'GRP_'+e.ke)
|
|
try{
|
|
s.group[e.ke].activeMonitors[e.id].spawn = s.ffmpeg(e)
|
|
}catch(err){
|
|
console.log('failed to launch, try again')
|
|
setTimeout(() => {
|
|
s.group[e.ke].activeMonitors[e.id].spawn = s.ffmpeg(e)
|
|
},3000)
|
|
}
|
|
s.sendMonitorStatus({
|
|
id: e.id,
|
|
ke: e.ke,
|
|
status: e.wantedStatus,
|
|
code: e.wantedStatusCode
|
|
});
|
|
//on unexpected exit restart
|
|
s.group[e.ke].activeMonitors[e.id].spawn_exit = function(){
|
|
if(s.group[e.ke].activeMonitors[e.id].isStarted === true){
|
|
if(e.details.loglevel!=='quiet'){
|
|
s.userLog(e,{type:lang['Process Unexpected Exit'],msg:{msg:lang.unexpectedExitText,cmd:s.group[e.ke].activeMonitors[e.id].ffmpeg}});
|
|
}
|
|
fatalError(e,'Process Unexpected Exit');
|
|
scanForOrphanedVideos(e,{
|
|
forceCheck: true,
|
|
checkMax: 2
|
|
})
|
|
s.onMonitorUnexpectedExitExtensions.forEach(function(extender){
|
|
extender(Object.assign(s.group[e.ke].rawMonitorConfigurations[e.id],{}),e)
|
|
})
|
|
}
|
|
}
|
|
s.group[e.ke].activeMonitors[e.id].spawn.on('end',s.group[e.ke].activeMonitors[e.id].spawn_exit)
|
|
s.group[e.ke].activeMonitors[e.id].spawn.on('exit',s.group[e.ke].activeMonitors[e.id].spawn_exit)
|
|
s.group[e.ke].activeMonitors[e.id].spawn.on('error',function(er){
|
|
s.userLog(e,{type:'Spawn Error',msg:er});fatalError(e,'Spawn Error')
|
|
})
|
|
s.userLog(e,{type:lang['Process Started'],msg:{cmd:s.group[e.ke].activeMonitors[e.id].ffmpeg}})
|
|
if(s.isWin === false){
|
|
var strippedHost = s.stripAuthFromHost(e)
|
|
var sendProcessCpuUsage = function(){
|
|
s.getMonitorCpuUsage(e,function(percent){
|
|
s.group[e.ke].activeMonitors[e.id].currentCpuUsage = percent
|
|
s.tx({
|
|
f: 'camera_cpu_usage',
|
|
ke: e.ke,
|
|
id: e.id,
|
|
percent: percent
|
|
},'MON_STREAM_'+e.ke+e.id)
|
|
})
|
|
}
|
|
clearInterval(s.group[e.ke].activeMonitors[e.id].getMonitorCpuUsage)
|
|
s.group[e.ke].activeMonitors[e.id].getMonitorCpuUsage = setInterval(function(){
|
|
if(e.details.skip_ping !== '1'){
|
|
connectionTester.test(strippedHost,e.port,2000,function(err,response){
|
|
if(response.success){
|
|
sendProcessCpuUsage()
|
|
}else{
|
|
launchMonitorProcesses(e)
|
|
}
|
|
})
|
|
}else{
|
|
sendProcessCpuUsage()
|
|
}
|
|
},1000 * 60)
|
|
}
|
|
}
|
|
const createEventCounter = function(monitor){
|
|
if(monitor.details.detector_obj_count === '1'){
|
|
const activeMonitor = s.group[monitor.ke].activeMonitors[monitor.id]
|
|
activeMonitor.eventsCountStartTime = new Date()
|
|
clearInterval(activeMonitor.objectCountIntervals)
|
|
activeMonitor.objectCountIntervals = setInterval(() => {
|
|
const eventsCounted = activeMonitor.eventsCounted || {}
|
|
const countsToSave = Object.assign(eventsCounted,{})
|
|
activeMonitor.eventsCounted = {}
|
|
const groupKey = monitor.ke
|
|
const monitorId = monitor.id
|
|
const startTime = new Date(activeMonitor.eventsCountStartTime + 0)
|
|
const endTime = new Date()
|
|
const countedKeys = Object.keys(countsToSave)
|
|
activeMonitor.eventsCountStartTime = new Date()
|
|
if(countedKeys.length > 0)countedKeys.forEach((tag) => {
|
|
const tagInfo = countsToSave[tag]
|
|
const count = Object.keys(tagInfo.count)
|
|
const times = tagInfo.times
|
|
const realTag = tagInfo.tag
|
|
s.knexQuery({
|
|
action: "insert",
|
|
table: "Events Counts",
|
|
insert: {
|
|
ke: groupKey,
|
|
mid: monitorId,
|
|
details: JSON.stringify({
|
|
times: times,
|
|
count: count,
|
|
}),
|
|
time: startTime,
|
|
end: endTime,
|
|
count: count.length,
|
|
tag: realTag
|
|
}
|
|
})
|
|
})
|
|
},60000) //every minute
|
|
}
|
|
}
|
|
const createCameraStreamHandlers = function(e){
|
|
s.group[e.ke].activeMonitors[e.id].spawn.stdio[5].on('data',function(data){
|
|
resetStreamCheck(e)
|
|
})
|
|
//emitter for mjpeg
|
|
if(!e.details.stream_mjpeg_clients||e.details.stream_mjpeg_clients===''||isNaN(e.details.stream_mjpeg_clients)===false){e.details.stream_mjpeg_clients=20;}else{e.details.stream_mjpeg_clients=parseInt(e.details.stream_mjpeg_clients)}
|
|
s.group[e.ke].activeMonitors[e.id].emitter = new events.EventEmitter().setMaxListeners(e.details.stream_mjpeg_clients);
|
|
if(e.details.detector_audio === '1'){
|
|
if(s.group[e.ke].activeMonitors[e.id].audioDetector){
|
|
s.group[e.ke].activeMonitors[e.id].audioDetector.stop()
|
|
delete(s.group[e.ke].activeMonitors[e.id].audioDetector)
|
|
}
|
|
var triggerLevel
|
|
var triggerLevelMax
|
|
if(e.details.detector_audio_min_db && e.details.detector_audio_min_db !== ''){
|
|
triggerLevel = parseInt(e.details.detector_audio_min_db)
|
|
}else{
|
|
triggerLevel = 5
|
|
}
|
|
if(e.details.detector_audio_max_db && e.details.detector_audio_max_db !== ''){
|
|
triggerLevelMax = parseInt(e.details.detector_audio_max_db)
|
|
}
|
|
var audioDetector = new SoundDetection({
|
|
format: {
|
|
bitDepth: 16,
|
|
numberOfChannels: 1,
|
|
signed: true
|
|
},
|
|
triggerLevel: triggerLevel,
|
|
triggerLevelMax: triggerLevelMax
|
|
},function(dB) {
|
|
triggerEvent({
|
|
f:'trigger',
|
|
id:e.id,
|
|
ke:e.ke,
|
|
name: 'db',
|
|
details:{
|
|
plug:'audio',
|
|
name:'db',
|
|
reason:'soundChange',
|
|
confidence:dB
|
|
},
|
|
plates:[],
|
|
imgHeight:e.details.detector_scale_y,
|
|
imgWidth:e.details.detector_scale_x
|
|
})
|
|
})
|
|
s.group[e.ke].activeMonitors[e.id].audioDetector = audioDetector
|
|
audioDetector.start()
|
|
s.group[e.ke].activeMonitors[e.id].spawn.stdio[6].pipe(audioDetector.streamDecoder,{ end: false })
|
|
}
|
|
if(e.details.record_timelapse === '1'){
|
|
var timelapseRecordingDirectory = s.getTimelapseFrameDirectory(e)
|
|
s.group[e.ke].activeMonitors[e.id].spawn.stdio[7].on('data',function(data){
|
|
var fileStream = s.group[e.ke].activeMonitors[e.id].recordTimelapseWriter
|
|
if(!fileStream){
|
|
var currentDate = s.formattedTime(null,'YYYY-MM-DD')
|
|
var filename = s.formattedTime() + '.jpg'
|
|
var location = timelapseRecordingDirectory + currentDate + '/'
|
|
if(!fs.existsSync(location)){
|
|
fs.mkdirSync(location)
|
|
}
|
|
fileStream = fs.createWriteStream(location + filename)
|
|
fileStream.on('close', function () {
|
|
s.group[e.ke].activeMonitors[e.id].recordTimelapseWriter = null
|
|
s.createTimelapseFrameAndInsert(e,location,filename)
|
|
})
|
|
s.group[e.ke].activeMonitors[e.id].recordTimelapseWriter = fileStream
|
|
}
|
|
fileStream.write(data)
|
|
clearTimeout(s.group[e.ke].activeMonitors[e.id].recordTimelapseWriterTimeout)
|
|
s.group[e.ke].activeMonitors[e.id].recordTimelapseWriterTimeout = setTimeout(function(){
|
|
fileStream.end()
|
|
},900)
|
|
})
|
|
}
|
|
if(e.details.detector === '1'){
|
|
s.ocvTx({f:'init_monitor',id:e.id,ke:e.ke})
|
|
//frames from motion detect
|
|
if(e.details.detector_pam === '1'){
|
|
// s.group[e.ke].activeMonitors[e.id].spawn.stdio[3].pipe(s.group[e.ke].activeMonitors[e.id].p2p).pipe(s.group[e.ke].activeMonitors[e.id].pamDiff)
|
|
s.group[e.ke].activeMonitors[e.id].spawn.stdio[3].on('data',function(buf){
|
|
let theJson
|
|
try{
|
|
buf.toString().split('}{').forEach((object,n)=>{
|
|
theJson = object
|
|
if(object.substr(object.length - 1) !== '}')theJson += '}'
|
|
if(object.substr(0,1) !== '{')theJson = '{' + theJson
|
|
try{
|
|
var data = JSON.parse(theJson)
|
|
}catch(err){
|
|
var data = JSON.parse(theJson + '}')
|
|
}
|
|
switch(data.f){
|
|
case'trigger':
|
|
triggerEvent(data)
|
|
break;
|
|
case's.tx':
|
|
s.tx(data.data,data.to)
|
|
break;
|
|
}
|
|
})
|
|
}catch(err){
|
|
console.log(theJson)
|
|
console.log('There was an error parsing a detector event')
|
|
console.log(err)
|
|
}
|
|
})
|
|
if(e.details.detector_use_detect_object === '1'){
|
|
s.group[e.ke].activeMonitors[e.id].spawn.stdio[4].on('data',function(data){
|
|
onDetectorJpegOutputSecondary(e,data)
|
|
})
|
|
}
|
|
}else if(e.details.detector_use_detect_object === '1' && e.details.detector_send_frames !== '1'){
|
|
s.group[e.ke].activeMonitors[e.id].spawn.stdio[4].on('data',function(data){
|
|
onDetectorJpegOutputSecondary(e,data)
|
|
})
|
|
}else{
|
|
s.group[e.ke].activeMonitors[e.id].spawn.stdio[4].on('data',function(data){
|
|
onDetectorJpegOutputAlone(e,data)
|
|
})
|
|
}
|
|
}
|
|
//frames to stream
|
|
var frameToStreamPrimary
|
|
switch(e.details.stream_type){
|
|
case'mp4':
|
|
delete(s.group[e.ke].activeMonitors[e.id].mp4frag['MAIN'])
|
|
if(!s.group[e.ke].activeMonitors[e.id].mp4frag['MAIN'])s.group[e.ke].activeMonitors[e.id].mp4frag['MAIN'] = new Mp4Frag()
|
|
s.group[e.ke].activeMonitors[e.id].mp4frag['MAIN'].on('error',function(error){
|
|
s.userLog(e,{type:lang['Mp4Frag'],msg:{error:error}})
|
|
})
|
|
s.group[e.ke].activeMonitors[e.id].spawn.stdio[1].pipe(s.group[e.ke].activeMonitors[e.id].mp4frag['MAIN'],{ end: false })
|
|
break;
|
|
case'flv':
|
|
frameToStreamPrimary = function(d){
|
|
if(!s.group[e.ke].activeMonitors[e.id].firstStreamChunk['MAIN'])s.group[e.ke].activeMonitors[e.id].firstStreamChunk['MAIN'] = d;
|
|
frameToStreamPrimary = function(d){
|
|
resetStreamCheck(e)
|
|
s.group[e.ke].activeMonitors[e.id].emitter.emit('data',d)
|
|
}
|
|
frameToStreamPrimary(d)
|
|
}
|
|
break;
|
|
case'mjpeg':
|
|
frameToStreamPrimary = function(d){
|
|
resetStreamCheck(e)
|
|
s.group[e.ke].activeMonitors[e.id].emitter.emit('data',d)
|
|
}
|
|
break;
|
|
case'h265':
|
|
frameToStreamPrimary = function(d){
|
|
resetStreamCheck(e)
|
|
s.group[e.ke].activeMonitors[e.id].emitter.emit('data',d)
|
|
}
|
|
break;
|
|
case'b64':case undefined:case null:case'':
|
|
var buffer
|
|
frameToStreamPrimary = function(d){
|
|
resetStreamCheck(e)
|
|
if(!buffer){
|
|
buffer=[d]
|
|
}else{
|
|
buffer.push(d)
|
|
}
|
|
if((d[d.length-2] === 0xFF && d[d.length-1] === 0xD9)){
|
|
s.group[e.ke].activeMonitors[e.id].emitter.emit('data',Buffer.concat(buffer))
|
|
buffer = null
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
if(frameToStreamPrimary){
|
|
s.group[e.ke].activeMonitors[e.id].spawn.stdout.on('data',frameToStreamPrimary)
|
|
}
|
|
if(e.details.stream_channels && e.details.stream_channels !== ''){
|
|
var createStreamEmitter = function(channel,number){
|
|
var pipeNumber = number+config.pipeAddition;
|
|
if(!s.group[e.ke].activeMonitors[e.id].emitterChannel[pipeNumber]){
|
|
s.group[e.ke].activeMonitors[e.id].emitterChannel[pipeNumber] = new events.EventEmitter().setMaxListeners(0);
|
|
}
|
|
var frameToStreamAdded
|
|
switch(channel.stream_type){
|
|
case'mp4':
|
|
delete(s.group[e.ke].activeMonitors[e.id].mp4frag[pipeNumber])
|
|
if(!s.group[e.ke].activeMonitors[e.id].mp4frag[pipeNumber])s.group[e.ke].activeMonitors[e.id].mp4frag[pipeNumber] = new Mp4Frag();
|
|
s.group[e.ke].activeMonitors[e.id].spawn.stdio[pipeNumber].pipe(s.group[e.ke].activeMonitors[e.id].mp4frag[pipeNumber],{ end: false })
|
|
break;
|
|
case'mjpeg':
|
|
frameToStreamAdded = function(d){
|
|
s.group[e.ke].activeMonitors[e.id].emitterChannel[pipeNumber].emit('data',d)
|
|
}
|
|
break;
|
|
case'flv':
|
|
frameToStreamAdded = function(d){
|
|
if(!s.group[e.ke].activeMonitors[e.id].firstStreamChunk[pipeNumber])s.group[e.ke].activeMonitors[e.id].firstStreamChunk[pipeNumber] = d;
|
|
frameToStreamAdded = function(d){
|
|
s.group[e.ke].activeMonitors[e.id].emitterChannel[pipeNumber].emit('data',d)
|
|
}
|
|
frameToStreamAdded(d)
|
|
}
|
|
break;
|
|
case'h264':
|
|
frameToStreamAdded = function(d){
|
|
s.group[e.ke].activeMonitors[e.id].emitterChannel[pipeNumber].emit('data',d)
|
|
}
|
|
break;
|
|
}
|
|
if(frameToStreamAdded){
|
|
s.group[e.ke].activeMonitors[e.id].spawn.stdio[pipeNumber].on('data',frameToStreamAdded)
|
|
}
|
|
}
|
|
e.details.stream_channels.forEach(createStreamEmitter)
|
|
}
|
|
}
|
|
const catchNewSegmentNames = function(e){
|
|
var checkLog = function(d,x){return d.indexOf(x)>-1}
|
|
s.group[e.ke].activeMonitors[e.id].spawn.stdio[8].on('data',function(d){
|
|
d=d.toString();
|
|
if(/T[0-9][0-9]-[0-9][0-9]-[0-9][0-9]./.test(d)){
|
|
var filename = d.split('.')[0].split(' [')[0].trim()+'.'+e.ext
|
|
s.insertCompletedVideo(e,{
|
|
file: filename,
|
|
events: s.group[e.ke].activeMonitors[e.id].detector_motion_count
|
|
},function(err){
|
|
s.userLog(e,{type:lang['Video Finished'],msg:{filename:d}})
|
|
if(
|
|
e.details.detector === '1' &&
|
|
s.group[e.ke].activeMonitors[e.id].isStarted === true &&
|
|
e.details &&
|
|
e.details.detector_record_method === 'del'&&
|
|
e.details.detector_delete_motionless_videos === '1'&&
|
|
s.group[e.ke].activeMonitors[e.id].detector_motion_count.length === 0
|
|
){
|
|
if(e.details.loglevel !== 'quiet'){
|
|
s.userLog(e,{type:lang['Delete Motionless Video'],msg:filename})
|
|
}
|
|
s.deleteVideo({
|
|
filename : filename,
|
|
ke : e.ke,
|
|
id : e.id
|
|
})
|
|
}
|
|
s.group[e.ke].activeMonitors[e.id].detector_motion_count = []
|
|
})
|
|
resetRecordingCheck(e)
|
|
}
|
|
})
|
|
}
|
|
const cameraFilterFfmpegLog = function(e){
|
|
var checkLog = function(d,x){return d.indexOf(x)>-1}
|
|
s.group[e.ke].activeMonitors[e.id].spawn.stderr.on('data',function(d){
|
|
d=d.toString();
|
|
switch(true){
|
|
case checkLog(d,'Not Enough Bandwidth'):
|
|
s.group[e.ke].activeMonitors[e.id].criticalErrors['453'] = true
|
|
break;
|
|
case checkLog(d,'No space left on device'):
|
|
s.checkUserPurgeLock(e.ke)
|
|
s.purgeDiskForGroup(e.ke)
|
|
break;
|
|
case checkLog(d,'error parsing AU headers'):
|
|
s.userLog(e,{type:lang['Error While Decoding'],msg:lang.ErrorWhileDecodingTextAudio});
|
|
break;
|
|
case checkLog(d,'error while decoding'):
|
|
s.userLog(e,{type:lang['Error While Decoding'],msg:lang.ErrorWhileDecodingText});
|
|
break;
|
|
case checkLog(d,'[hls @'):
|
|
case checkLog(d,'Past duration'):
|
|
case checkLog(d,'Last message repeated'):
|
|
case checkLog(d,'pkt->duration = 0'):
|
|
case checkLog(d,'Non-monotonous DTS'):
|
|
case checkLog(d,'NULL @'):
|
|
case checkLog(d,'RTP: missed'):
|
|
case checkLog(d,'deprecated pixel format used'):
|
|
return
|
|
break;
|
|
case checkLog(d,'Could not find tag for vp8'):
|
|
case checkLog(d,'Only VP8 or VP9 Video'):
|
|
case checkLog(d,'Could not write header'):
|
|
return s.userLog(e,{type:lang['Incorrect Settings Chosen'],msg:{msg:d}})
|
|
break;
|
|
case checkLog(d,'Connection refused'):
|
|
case checkLog(d,'Connection timed out'):
|
|
//restart
|
|
setTimeout(function(){
|
|
s.userLog(e,{type:lang['Connection timed out'],msg:lang['Retrying...']});
|
|
fatalError(e,'Connection timed out');
|
|
},1000)
|
|
break;
|
|
// case checkLog(d,'Immediate exit requested'):
|
|
case checkLog(d,'mjpeg_decode_dc'):
|
|
case checkLog(d,'bad vlc'):
|
|
case checkLog(d,'error dc'):
|
|
case checkLog(d,'No route to host'):
|
|
launchMonitorProcesses(e)
|
|
break;
|
|
}
|
|
s.userLog(e,{type:"FFMPEG STDERR",msg:d})
|
|
})
|
|
}
|
|
//formerly known as "No Motion" Detector
|
|
s.setNoEventsDetector = function(e){
|
|
var monitorId = e.id || e.mid
|
|
var detector_notrigger_timeout = (parseFloat(e.details.detector_notrigger_timeout) || 10) * 1000 * 60
|
|
var currentConfig = s.group[e.ke].rawMonitorConfigurations[monitorId].details
|
|
clearInterval(s.group[e.ke].activeMonitors[monitorId].detector_notrigger_timeout)
|
|
s.group[e.ke].activeMonitors[monitorId].detector_notrigger_timeout = setInterval(function(){
|
|
if(currentConfig.detector_notrigger_webhook === '1' && !s.group[e.ke].activeMonitors[monitorId].detector_notrigger_webhook){
|
|
s.group[e.ke].activeMonitors[monitorId].detector_notrigger_webhook = s.createTimeout('detector_notrigger_webhook',s.group[e.ke].activeMonitors[monitorId],currentConfig.detector_notrigger_webhook_timeout,10)
|
|
var detector_notrigger_webhook_url = addEventDetailsToString(e,currentConfig.detector_notrigger_webhook_url)
|
|
var webhookMethod = currentConfig.detector_notrigger_webhook_method
|
|
if(!webhookMethod || webhookMethod === '')webhookMethod = 'GET'
|
|
request(detector_notrigger_webhook_url,{method: webhookMethod,encoding:null},function(err,data){
|
|
if(err){
|
|
s.userLog(d,{type:lang["Event Webhook Error"],msg:{error:err,data:data}})
|
|
}
|
|
})
|
|
}
|
|
if(currentConfig.detector_notrigger_command_enable === '1' && !s.group[e.ke].activeMonitors[monitorId].detector_notrigger_command){
|
|
s.group[e.ke].activeMonitors[monitorId].detector_notrigger_command = s.createTimeout('detector_notrigger_command',s.group[e.ke].activeMonitors[monitorId],currentConfig.detector_notrigger_command_timeout,10)
|
|
var detector_notrigger_command = addEventDetailsToString(e,currentConfig.detector_notrigger_command)
|
|
if(detector_notrigger_command === '')return
|
|
exec(detector_notrigger_command,{detached: true},function(err){
|
|
if(err)s.debugLog(err)
|
|
})
|
|
}
|
|
s.onDetectorNoTriggerTimeoutExtensions.forEach(function(extender){
|
|
extender(e)
|
|
})
|
|
},detector_notrigger_timeout)
|
|
}
|
|
//set master based process launcher
|
|
const launchMonitorProcesses = function(e){
|
|
const activeMonitor = s.group[e.ke].activeMonitors[e.id]
|
|
// e = monitor object
|
|
clearTimeout(activeMonitor.resetFatalErrorCountTimer)
|
|
activeMonitor.resetFatalErrorCountTimer = setTimeout(()=>{
|
|
activeMonitor.errorFatalCount = 0
|
|
},1000 * 60)
|
|
//create host string without username and password
|
|
var strippedHost = s.stripAuthFromHost(e)
|
|
var doOnThisMachine = function(callback){
|
|
createCameraFolders(e,async function(){
|
|
activeMonitor.allowStdinWrite = false
|
|
if(e.details.detector_trigger === '1'){
|
|
clearTimeout(activeMonitor.motion_lock)
|
|
activeMonitor.motion_lock = setTimeout(function(){
|
|
clearTimeout(activeMonitor.motion_lock)
|
|
delete(activeMonitor.motion_lock)
|
|
},15000)
|
|
}
|
|
//start "no motion" checker
|
|
if(e.details.detector === '1' && e.details.detector_notrigger === '1'){
|
|
s.setNoEventsDetector(e)
|
|
}
|
|
if(e.details.snap === '1'){
|
|
// var resetSnapCheck = function(){
|
|
// clearTimeout(activeMonitor.checkSnap)
|
|
// activeMonitor.checkSnap = setTimeout(function(){
|
|
// if(activeMonitor.isStarted === true){
|
|
// s.fileStats(e.sdir+'s.jpg',function(err,snap){
|
|
// var notStreaming = function(){
|
|
// launchMonitorProcesses(e)
|
|
// s.userLog(e,{type:lang['Camera is not streaming'],msg:{msg:lang['Restarting Process']}})
|
|
// scanForOrphanedVideos(e,{
|
|
// forceCheck: true,
|
|
// checkMax: 2
|
|
// })
|
|
// }
|
|
// if(err){
|
|
// notStreaming()
|
|
// }else{
|
|
// if(!activeMonitor.checkSnapTime)activeMonitor.checkSnapTime = snap.mtime
|
|
// if(err || activeMonitor.checkSnapTime === snap.mtime){
|
|
// activeMonitor.checkSnapTime = snap.mtime
|
|
// notStreaming()
|
|
// }else{
|
|
// resetSnapCheck()
|
|
// }
|
|
// }
|
|
// })
|
|
// }
|
|
// },60000*1);
|
|
// }
|
|
// resetSnapCheck()
|
|
}
|
|
if(config.childNodes.mode !== 'child' && s.platform!=='darwin' && (e.functionMode === 'record' || (e.functionMode === 'start'&&e.details.detector_record_method==='sip'))){
|
|
if(activeMonitor.fswatch && activeMonitor.fswatch.close){
|
|
activeMonitor.fswatch.close()
|
|
}
|
|
activeMonitor.fswatch = fs.watch(e.dir, {encoding : 'utf8'}, (event, filename) => {
|
|
switch(event){
|
|
case'change':
|
|
resetRecordingCheck(e)
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
if(
|
|
//is MacOS
|
|
s.platform !== 'darwin' &&
|
|
//is Watch-Only or Record
|
|
(e.functionMode === 'start' || e.functionMode === 'record') &&
|
|
//if JPEG API enabled or Stream Type is HLS
|
|
(
|
|
e.details.stream_type === 'jpeg' ||
|
|
e.details.stream_type === 'hls' ||
|
|
e.details.snap === '1'
|
|
)
|
|
){
|
|
if(activeMonitor.fswatchStream && activeMonitor.fswatchStream.close){
|
|
activeMonitor.fswatchStream.close()
|
|
}
|
|
activeMonitor.fswatchStream = fs.watch(activeMonitor.sdir, {encoding : 'utf8'}, () => {
|
|
resetStreamCheck(e)
|
|
})
|
|
}
|
|
if(!activeMonitor.criticalErrors['453'])s.cameraSendSnapshot({mid:e.id,ke:e.ke,mon:e},{useIcon: true})
|
|
//check host to see if has password and user in it
|
|
clearTimeout(activeMonitor.recordingChecker)
|
|
if(activeMonitor.isStarted === true){
|
|
try{
|
|
cameraDestroy(e)
|
|
}catch(err){
|
|
|
|
}
|
|
startVideoProcessor = function(err,pingResponse){
|
|
pingResponse = pingResponse ? pingResponse : {success: true}
|
|
if(pingResponse.success === true){
|
|
activeMonitor.isRecording = true
|
|
try{
|
|
createCameraFfmpegProcess(e)
|
|
createCameraStreamHandlers(e)
|
|
createEventCounter(e)
|
|
if(e.type === 'dashcam' || e.type === 'socket'){
|
|
setTimeout(function(){
|
|
activeMonitor.allowStdinWrite = true
|
|
s.txToDashcamUsers({
|
|
f : 'enable_stream',
|
|
ke : e.ke,
|
|
mid : e.id
|
|
},e.ke)
|
|
},30000)
|
|
}
|
|
if(
|
|
e.functionMode === 'record' ||
|
|
e.type === 'mjpeg' ||
|
|
e.type === 'h264' ||
|
|
e.type === 'local'
|
|
){
|
|
catchNewSegmentNames(e)
|
|
cameraFilterFfmpegLog(e)
|
|
}
|
|
clearTimeout(activeMonitor.onMonitorStartTimer)
|
|
activeMonitor.onMonitorStartTimer = setTimeout(() => {
|
|
s.onMonitorStartExtensions.forEach(function(extender){
|
|
extender(Object.assign(s.group[e.ke].rawMonitorConfigurations[e.id],{}),e)
|
|
})
|
|
},10000)
|
|
}catch(err){
|
|
console.log('Failed to Load',e.id,e.ke)
|
|
console.log(err)
|
|
}
|
|
}else{
|
|
s.onMonitorPingFailedExtensions.forEach(function(extender){
|
|
extender(Object.assign(s.group[e.ke].rawMonitorConfigurations[e.id],{}),e)
|
|
})
|
|
s.userLog(e,{type:lang["Ping Failed"],msg:lang.skipPingText1});
|
|
fatalError(e,"Ping Failed");return;
|
|
}
|
|
}
|
|
if(
|
|
e.type !== 'socket' &&
|
|
e.type !== 'dashcam' &&
|
|
e.protocol !== 'udp' &&
|
|
e.type !== 'local' &&
|
|
e.details.skip_ping !== '1'
|
|
){
|
|
try{
|
|
connectionTester.test(strippedHost,e.port,2000,startVideoProcessor);
|
|
}catch(err){
|
|
startVideoProcessor()
|
|
}
|
|
}else{
|
|
startVideoProcessor()
|
|
}
|
|
}else{
|
|
cameraDestroy(e)
|
|
}
|
|
if(callback)callback()
|
|
})
|
|
}
|
|
var doOnChildMachine = function(){
|
|
startVideoProcessor = function(){
|
|
s.cx({
|
|
//function
|
|
f : 'cameraStart',
|
|
//mode
|
|
mode : e.functionMode,
|
|
//data, options
|
|
d : s.group[e.ke].rawMonitorConfigurations[e.id]
|
|
},activeMonitor.childNodeId)
|
|
}
|
|
if(
|
|
e.type !== 'socket' &&
|
|
e.type !== 'dashcam' &&
|
|
e.protocol !== 'udp' &&
|
|
e.type !== 'local' &&
|
|
e.details.skip_ping !== '1'
|
|
){
|
|
connectionTester.test(strippedHost,e.port,2000,startVideoProcessor)
|
|
}else{
|
|
startVideoProcessor()
|
|
}
|
|
}
|
|
try{
|
|
if(config.childNodes.enabled === true && config.childNodes.mode === 'master'){
|
|
var copiedMonitorObject = s.cleanMonitorObject(s.group[e.ke].rawMonitorConfigurations[e.id])
|
|
var childNodeList = Object.keys(s.childNodes)
|
|
if(childNodeList.length > 0){
|
|
e.childNodeFound = false
|
|
var selectNode = function(ip){
|
|
e.childNodeFound = true
|
|
e.childNodeSelected = ip
|
|
}
|
|
var nodeWithLowestActiveCamerasCount = 65535
|
|
var nodeWithLowestActiveCameras = null
|
|
childNodeList.forEach(function(ip){
|
|
delete(s.childNodes[ip].activeCameras[e.ke+e.id])
|
|
var nodeCameraCount = Object.keys(s.childNodes[ip].activeCameras).length
|
|
if(!s.childNodes[ip].dead && nodeCameraCount < nodeWithLowestActiveCamerasCount && s.childNodes[ip].cpu < 75){
|
|
nodeWithLowestActiveCamerasCount = nodeCameraCount
|
|
nodeWithLowestActiveCameras = ip
|
|
}
|
|
})
|
|
if(nodeWithLowestActiveCameras)selectNode(nodeWithLowestActiveCameras)
|
|
if(e.childNodeFound === true){
|
|
s.childNodes[e.childNodeSelected].activeCameras[e.ke+e.id] = copiedMonitorObject
|
|
activeMonitor.childNode = e.childNodeSelected
|
|
activeMonitor.childNodeId = s.childNodes[e.childNodeSelected].cnid;
|
|
s.cx({f:'sync',sync:s.group[e.ke].rawMonitorConfigurations[e.id],ke:e.ke,mid:e.id},activeMonitor.childNodeId);
|
|
doOnChildMachine()
|
|
}else{
|
|
startMonitorInQueue.push(doOnThisMachine,function(){})
|
|
}
|
|
}else{
|
|
startMonitorInQueue.push(doOnThisMachine,function(){})
|
|
}
|
|
}else{
|
|
startMonitorInQueue.push(doOnThisMachine,function(){})
|
|
}
|
|
}catch(err){
|
|
startMonitorInQueue.push(doOnThisMachine,function(){})
|
|
console.log(err)
|
|
}
|
|
}
|
|
const fatalError = function(e,errorMessage){
|
|
const activeMonitor = s.group[e.ke].activeMonitors[e.id]
|
|
clearTimeout(activeMonitor.err_fatal_timeout);
|
|
++activeMonitor.errorFatalCount;
|
|
if(activeMonitor.isStarted === true){
|
|
activeMonitor.err_fatal_timeout = setTimeout(function(){
|
|
if(e.details.fatal_max !== 0 && activeMonitor.errorFatalCount > e.details.fatal_max){
|
|
s.camera('stop',{id:e.id,ke:e.ke})
|
|
}else{
|
|
launchMonitorProcesses(s.cleanMonitorObject(e))
|
|
};
|
|
},5000);
|
|
}else{
|
|
cameraDestroy(e)
|
|
}
|
|
s.sendMonitorStatus({
|
|
id: e.id,
|
|
ke: e.ke,
|
|
status: lang.Died,
|
|
code: 7
|
|
})
|
|
clearTimeout(activeMonitor.onMonitorStartTimer)
|
|
s.onMonitorDiedExtensions.forEach(function(extender){
|
|
extender(Object.assign(s.group[e.ke].rawMonitorConfigurations[e.id],{}),e)
|
|
})
|
|
}
|
|
// s.isWatchCountable = function(d){
|
|
// try{
|
|
// var variableMethodsToAllow = [
|
|
// 'mp4ws', //Poseidon over Websocket
|
|
// 'flvws',
|
|
// 'h265ws',
|
|
// ];
|
|
// var indefiniteIgnore = [
|
|
// 'mjpeg',
|
|
// 'h264',
|
|
// ];
|
|
// var monConfig = s.group[d.ke].rawMonitorConfigurations[d.id]
|
|
// if(
|
|
// variableMethodsToAllow.indexOf(monConfig.details.stream_type + monConfig.details.stream_flv_type) > -1 &&
|
|
// indefiniteIgnore.indexOf(monConfig.details.stream_type) === -1
|
|
// ){
|
|
// return true
|
|
// }
|
|
// }catch(err){}
|
|
// return false
|
|
// }
|
|
s.addOrEditMonitor = function(form,callback,user){
|
|
var endData = {
|
|
ok: false
|
|
}
|
|
if(!form.mid){
|
|
endData.msg = lang['No Monitor ID Present in Form']
|
|
callback(endData)
|
|
return
|
|
}
|
|
form.mid = form.mid.replace(/[^\w\s]/gi,'').replace(/ /g,'')
|
|
form = s.cleanMonitorObjectForDatabase(form)
|
|
s.knexQuery({
|
|
action: "select",
|
|
columns: "*",
|
|
table: "Monitors",
|
|
where: [
|
|
['ke','=',form.ke],
|
|
['mid','=',form.mid],
|
|
]
|
|
},(err,r) => {
|
|
var affectMonitor = false
|
|
var monitorQuery = {}
|
|
var txData = {
|
|
f: 'monitor_edit',
|
|
mid: form.mid,
|
|
ke: form.ke,
|
|
mon: form
|
|
}
|
|
if(r&&r[0]){
|
|
txData.new = false
|
|
Object.keys(form).forEach(function(v){
|
|
if(
|
|
form[v] !== undefined &&
|
|
form[v] !== `undefined` &&
|
|
form[v] !== null &&
|
|
form[v] !== `null` &&
|
|
form[v] !== false &&
|
|
form[v] !== `false`
|
|
){
|
|
if(form[v] instanceof Object){
|
|
form[v] = s.s(form[v])
|
|
}
|
|
monitorQuery[v] = form[v]
|
|
}
|
|
})
|
|
s.userLog(form,{type:'Monitor Updated',msg:'by user : '+user.uid})
|
|
endData.msg = user.lang['Monitor Updated by user']+' : '+user.uid
|
|
s.knexQuery({
|
|
action: "update",
|
|
table: "Monitors",
|
|
update: monitorQuery,
|
|
where: [
|
|
['ke','=',form.ke],
|
|
['mid','=',form.mid],
|
|
]
|
|
})
|
|
affectMonitor = true
|
|
}else if(
|
|
!s.group[form.ke].init.max_camera ||
|
|
s.group[form.ke].init.max_camera === '' ||
|
|
Object.keys(s.group[form.ke].activeMonitors).length <= parseInt(s.group[form.ke].init.max_camera)
|
|
){
|
|
txData.new = true
|
|
Object.keys(form).forEach(function(v){
|
|
if(form[v] && form[v] !== ''){
|
|
if(form[v] instanceof Object){
|
|
form[v] = s.s(form[v])
|
|
}
|
|
monitorQuery[v] = form[v]
|
|
}
|
|
})
|
|
s.userLog(form,{type:'Monitor Added',msg:'by user : '+user.uid})
|
|
endData.msg = user.lang['Monitor Added by user']+' : '+user.uid
|
|
s.knexQuery({
|
|
action: "insert",
|
|
table: "Monitors",
|
|
insert: monitorQuery
|
|
})
|
|
affectMonitor = true
|
|
}else{
|
|
txData.f = 'monitor_edit_failed'
|
|
txData.ff = 'max_reached'
|
|
endData.msg = user.lang.monitorEditFailedMaxReached
|
|
}
|
|
if(affectMonitor === true){
|
|
form.details = JSON.parse(form.details)
|
|
endData.ok = true
|
|
s.initiateMonitorObject({mid:form.mid,ke:form.ke})
|
|
s.group[form.ke].rawMonitorConfigurations[form.mid] = s.cleanMonitorObject(form)
|
|
if(form.mode === 'stop'){
|
|
s.camera('stop',form)
|
|
}else{
|
|
s.camera('stop',Object.assign(s.group[form.ke].rawMonitorConfigurations[form.mid]))
|
|
setTimeout(function(){
|
|
s.camera(form.mode,Object.assign(s.group[form.ke].rawMonitorConfigurations[form.mid]))
|
|
},5000)
|
|
}
|
|
s.tx(txData,'STR_'+form.ke)
|
|
}
|
|
s.tx(txData,'GRP_'+form.ke)
|
|
callback(!endData.ok,endData)
|
|
s.onMonitorSaveExtensions.forEach(function(extender){
|
|
extender(Object.assign(s.group[form.ke].rawMonitorConfigurations[form.mid],{}),form,endData)
|
|
})
|
|
})
|
|
}
|
|
s.camera = async (x,e,cn) => {
|
|
// x = function or mode
|
|
// e = monitor object
|
|
// cn = socket connection or callback or options (depends on function chosen)
|
|
if(cn && cn.ke && !e.ke){e.ke = cn.ke}
|
|
e.functionMode = x
|
|
if(!e.mode){e.mode = x}
|
|
s.checkDetails(e)
|
|
checkObjectsInDetails(e)
|
|
s.initiateMonitorObject({ke:e.ke,mid:e.id})
|
|
switch(e.functionMode){
|
|
case'watch_on'://live streamers - join
|
|
if(!cn.monitorsCurrentlyWatching){cn.monitorsCurrentlyWatching = {}}
|
|
if(!cn.monitorsCurrentlyWatching[e.id]){cn.monitorsCurrentlyWatching[e.id]={ke:e.ke}}
|
|
s.group[e.ke].activeMonitors[e.id].watch[cn.id]={};
|
|
var numberOfViewers = Object.keys(s.group[e.ke].activeMonitors[e.id].watch).length
|
|
s.tx({
|
|
viewers: numberOfViewers,
|
|
ke: e.ke,
|
|
id: e.id
|
|
},'MON_'+e.ke+e.id)
|
|
break;
|
|
case'watch_off'://live streamers - leave
|
|
if(cn.monitorsCurrentlyWatching){delete(cn.monitorsCurrentlyWatching[e.id])}
|
|
var numberOfViewers = 0
|
|
delete(s.group[e.ke].activeMonitors[e.id].watch[cn.id]);
|
|
numberOfViewers = Object.keys(s.group[e.ke].activeMonitors[e.id].watch).length
|
|
s.tx({
|
|
viewers: numberOfViewers,
|
|
ke: e.ke,
|
|
id: e.id
|
|
},'MON_'+e.ke+e.id)
|
|
break;
|
|
case'restart'://restart monitor
|
|
s.sendMonitorStatus({
|
|
id: e.id,
|
|
ke: e.ke,
|
|
status: 'Restarting',
|
|
code: 4,
|
|
});
|
|
s.camera('stop',e)
|
|
setTimeout(function(){
|
|
s.camera(e.mode,e)
|
|
},1300)
|
|
break;
|
|
case'idle':case'stop'://stop monitor
|
|
if(!s.group[e.ke]||!s.group[e.ke].activeMonitors[e.id]){return}
|
|
if(config.childNodes.enabled === true && config.childNodes.mode === 'master' && s.group[e.ke].activeMonitors[e.id].childNode && s.childNodes[s.group[e.ke].activeMonitors[e.id].childNode].activeCameras[e.ke+e.id]){
|
|
s.group[e.ke].activeMonitors[e.id].isStarted = false
|
|
s.cx({
|
|
//function
|
|
f : 'cameraStop',
|
|
//data, options
|
|
d : s.group[e.ke].rawMonitorConfigurations[e.id]
|
|
},s.group[e.ke].activeMonitors[e.id].childNodeId)
|
|
s.cx({f:'sync',sync:s.group[e.ke].rawMonitorConfigurations[e.id],ke:e.ke,mid:e.id},s.group[e.ke].activeMonitors[e.id].childNodeId);
|
|
}else{
|
|
closeEventBasedRecording(e)
|
|
if(s.group[e.ke].activeMonitors[e.id].fswatch){s.group[e.ke].activeMonitors[e.id].fswatch.close();delete(s.group[e.ke].activeMonitors[e.id].fswatch)}
|
|
if(s.group[e.ke].activeMonitors[e.id].fswatchStream){s.group[e.ke].activeMonitors[e.id].fswatchStream.close();delete(s.group[e.ke].activeMonitors[e.id].fswatchStream)}
|
|
if(s.group[e.ke].activeMonitors[e.id].last_frame){delete(s.group[e.ke].activeMonitors[e.id].last_frame)}
|
|
if(s.group[e.ke].activeMonitors[e.id].isStarted !== true){return}
|
|
cameraDestroy(e)
|
|
clearTimeout(s.group[e.ke].activeMonitors[e.id].trigger_timer)
|
|
delete(s.group[e.ke].activeMonitors[e.id].trigger_timer)
|
|
clearInterval(s.group[e.ke].activeMonitors[e.id].detector_notrigger_timeout)
|
|
clearTimeout(s.group[e.ke].activeMonitors[e.id].err_fatal_timeout);
|
|
s.group[e.ke].activeMonitors[e.id].isStarted = false
|
|
s.group[e.ke].activeMonitors[e.id].isRecording = false
|
|
s.tx({f:'monitor_stopping',mid:e.id,ke:e.ke,time:s.formattedTime()},'GRP_'+e.ke);
|
|
s.cameraSendSnapshot({mid:e.id,ke:e.ke,mon:e},{useIcon: true})
|
|
if(e.functionMode === 'stop'){
|
|
s.userLog(e,{type:lang['Monitor Stopped'],msg:lang.MonitorStoppedText});
|
|
clearTimeout(s.group[e.ke].activeMonitors[e.id].delete)
|
|
if(e.delete===1){
|
|
s.group[e.ke].activeMonitors[e.id].delete=setTimeout(function(){
|
|
delete(s.group[e.ke].activeMonitors[e.id]);
|
|
delete(s.group[e.ke].rawMonitorConfigurations[e.id]);
|
|
},1000*60);
|
|
}
|
|
}else{
|
|
s.tx({f:'monitor_idle',mid:e.id,ke:e.ke,time:s.formattedTime()},'GRP_'+e.ke);
|
|
s.userLog(e,{type:lang['Monitor Idling'],msg:lang.MonitorIdlingText});
|
|
}
|
|
}
|
|
var wantedStatus = lang.Stopped
|
|
var wantedStatusCode = 5
|
|
if(e.functionMode === 'idle'){
|
|
wantedStatus = lang.Idle
|
|
wantedStatusCode = 6
|
|
}
|
|
s.sendMonitorStatus({
|
|
id: e.id,
|
|
ke: e.ke,
|
|
status: wantedStatus,
|
|
code: wantedStatusCode,
|
|
})
|
|
setTimeout(() => {
|
|
scanForOrphanedVideos({
|
|
ke: e.ke,
|
|
mid: e.id,
|
|
},{
|
|
forceCheck: true,
|
|
checkMax: 2
|
|
})
|
|
},2000)
|
|
clearTimeout(s.group[e.ke].activeMonitors[e.id].onMonitorStartTimer)
|
|
s.onMonitorStopExtensions.forEach(function(extender){
|
|
extender(Object.assign(s.group[e.ke].rawMonitorConfigurations[e.id],{}),e)
|
|
})
|
|
break;
|
|
case'start':case'record'://watch or record monitor url
|
|
monitorConfigurationMigrator(e)
|
|
s.initiateMonitorObject({ke:e.ke,mid:e.id})
|
|
const activeMonitor = s.group[e.ke].activeMonitors[e.id]
|
|
if(!s.group[e.ke].rawMonitorConfigurations[e.id]){s.group[e.ke].rawMonitorConfigurations[e.id]=s.cleanMonitorObject(e);}
|
|
if(activeMonitor.isStarted === true){
|
|
//stop action, monitor already started or recording
|
|
return
|
|
}
|
|
if(config.probeMonitorOnStart === true){
|
|
const probeResponse = await probeMonitor(s.group[e.ke].rawMonitorConfigurations[e.id],2000,true)
|
|
const probeStreams = getStreamInfoFromProbe(probeResponse.result)
|
|
activeMonitor.probeResult = probeStreams
|
|
const warnings = createWarningsForConfiguration(s.group[e.ke].rawMonitorConfigurations[e.id],probeStreams)
|
|
activeMonitor.warnings = warnings
|
|
if(warnings.length > 0){
|
|
const configPartial = buildMonitorConfigPartialFromWarnings(warnings)
|
|
applyPartialToConfiguration(e,configPartial)
|
|
applyPartialToConfiguration(activeMonitor,configPartial)
|
|
applyPartialToConfiguration(s.group[e.ke].rawMonitorConfigurations[e.id],configPartial)
|
|
}
|
|
}
|
|
s.sendMonitorStatus({
|
|
id: e.id,
|
|
ke: e.ke,
|
|
status: lang.Starting,
|
|
code: 1
|
|
});
|
|
activeMonitor.isStarted = true
|
|
if(e.details && e.details.dir && e.details.dir !== ''){
|
|
activeMonitor.addStorageId = e.details.dir
|
|
}else{
|
|
activeMonitor.addStorageId = null
|
|
}
|
|
//set recording status
|
|
e.wantedStatus = lang.Watching
|
|
e.wantedStatusCode = 2
|
|
if(e.functionMode === 'record'){
|
|
e.wantedStatus = lang.Recording
|
|
e.wantedStatusCode = 3
|
|
activeMonitor.isRecording = true
|
|
}else{
|
|
activeMonitor.isRecording = false
|
|
}
|
|
//set up fatal error handler
|
|
if(e.details.fatal_max === ''){
|
|
e.details.fatal_max = 0
|
|
}else{
|
|
e.details.fatal_max = parseFloat(e.details.fatal_max)
|
|
}
|
|
activeMonitor.errorFatalCount = 0;
|
|
//start drawing files
|
|
delete(activeMonitor.childNode)
|
|
if(e.details.detector_ptz_follow === '1'){
|
|
setTimeout(() => {
|
|
setPresetForCurrentPosition({
|
|
ke: e.ke,
|
|
id: e.id
|
|
},(endData) => {
|
|
if(endData.ok === false){
|
|
setTimeout(() => {
|
|
setPresetForCurrentPosition({
|
|
ke: e.ke,
|
|
id: e.id
|
|
},(endData) => {
|
|
if(endData.ok === false){
|
|
setTimeout(() => {
|
|
setPresetForCurrentPosition({
|
|
ke: e.ke,
|
|
id: e.id
|
|
},(endData) => {
|
|
console.log(endData)
|
|
})
|
|
},5000)
|
|
}
|
|
})
|
|
},5000)
|
|
}
|
|
})
|
|
},5000)
|
|
}
|
|
launchMonitorProcesses(e)
|
|
break;
|
|
default:
|
|
console.log(x)
|
|
break;
|
|
}
|
|
if(typeof cn === 'function'){setTimeout(function(){cn()},1000)}
|
|
}
|
|
//
|
|
s.activateMonitorStates = function(groupKey,stateName,user,callback){
|
|
var endData = {
|
|
ok: false
|
|
}
|
|
s.findPreset([groupKey,'monitorStates',stateName],function(notFound,preset){
|
|
if(notFound === false){
|
|
var sqlQuery = 'SELECT * FROM Monitors WHERE ke=? AND '
|
|
var monitorQuery = []
|
|
var monitorPresets = {}
|
|
preset.details.monitors.forEach(function(monitor){
|
|
const whereConditions = {}
|
|
if(monitorQuery.length === 0){
|
|
whereConditions.ke = groupKey
|
|
monitorQuery.push(['ke','=',groupKey])
|
|
}else{
|
|
monitorQuery.push(['or','ke','=',groupKey])
|
|
}
|
|
monitorQuery.push(['mid','=',monitor.mid])
|
|
monitorPresets[monitor.mid] = monitor
|
|
})
|
|
s.knexQuery({
|
|
action: "select",
|
|
columns: "*",
|
|
table: "Monitors",
|
|
where: monitorQuery
|
|
},function(err,monitors){
|
|
if(monitors && monitors[0]){
|
|
monitors.forEach(function(monitor){
|
|
s.checkDetails(monitor)
|
|
s.checkDetails(monitorPresets[monitor.mid])
|
|
var monitorPreset = monitorPresets[monitor.mid]
|
|
monitorPreset.details = Object.assign(monitor.details,monitorPreset.details)
|
|
monitor = s.cleanMonitorObjectForDatabase(Object.assign(monitor,monitorPreset))
|
|
monitor.details = JSON.stringify(monitor.details)
|
|
s.addOrEditMonitor(Object.assign(monitor,{}),function(err,endData){
|
|
|
|
},user)
|
|
})
|
|
endData.ok = true
|
|
s.tx({f:'change_group_state',ke:groupKey,name:stateName},'GRP_'+groupKey)
|
|
callback(endData)
|
|
}else{
|
|
endData.msg = user.lang['State Configuration has no monitors associated']
|
|
callback(endData)
|
|
}
|
|
})
|
|
}else{
|
|
endData.msg = user.lang['State Configuration Not Found']
|
|
callback(endData)
|
|
}
|
|
})
|
|
}
|
|
s.getCamerasForMultiTrigger = function(monitor){
|
|
var list={}
|
|
var cameras=[]
|
|
var group
|
|
try{
|
|
group=JSON.parse(monitor.details.group_detector_multi)
|
|
if(!group){group=[]}
|
|
}catch(err){
|
|
group=[]
|
|
}
|
|
group.forEach(function(b){
|
|
Object.keys(s.group[monitor.ke].rawMonitorConfigurations).forEach(function(v){
|
|
try{
|
|
var groups = JSON.parse(s.group[monitor.ke].rawMonitorConfigurations[v].details.groups)
|
|
if(!groups){
|
|
groups=[]
|
|
}
|
|
}catch(err){
|
|
groups=[]
|
|
}
|
|
if(!list[v]&&groups.indexOf(b)>-1){
|
|
list[v]={}
|
|
if(s.group[monitor.ke].rawMonitorConfigurations[v].mode !== 'stop'){
|
|
cameras.push(Object.assign({},s.group[monitor.ke].rawMonitorConfigurations[v]))
|
|
}
|
|
}
|
|
})
|
|
})
|
|
return cameras
|
|
}
|
|
s.getMonitorRestrictions = (permissions,monitorId) => {
|
|
const monitorRestrictions = []
|
|
if(
|
|
!monitorId &&
|
|
permissions.sub &&
|
|
permissions.monitors &&
|
|
permissions.allmonitors !== '1'
|
|
){
|
|
try{
|
|
permissions.monitors = s.parseJSON(permissions.monitors)
|
|
permissions.monitors.forEach(function(v,n){
|
|
if(n === 0){
|
|
monitorRestrictions.push(['mid','=',v])
|
|
}else{
|
|
monitorRestrictions.push(['or','mid','=',v])
|
|
}
|
|
})
|
|
}catch(er){
|
|
}
|
|
}else if(
|
|
monitorId && (
|
|
!permissions.sub ||
|
|
permissions.allmonitors !== '0' ||
|
|
permissions.monitors.indexOf(monitorId) >- 1
|
|
)
|
|
){
|
|
monitorRestrictions.push(['mid','=',monitorId])
|
|
}else if(
|
|
!monitorId &&
|
|
permissions.sub &&
|
|
permissions.allmonitors !== '0'
|
|
){}
|
|
return monitorRestrictions
|
|
}
|
|
// s.checkViewerConnectionsForMonitor = function(monitorObject){
|
|
// var monitorConfig = s.group[monitorObject.ke].rawMonitorConfigurations[monitorObject.mid]
|
|
// if(monitorConfig.mode === 'start'){
|
|
//
|
|
// }
|
|
// }
|
|
// s.addViewerConnectionForMonitor = function(monitorObject,viewerDetails){
|
|
// s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnection[viewerDetails.viewerId] = viewerDetails
|
|
// s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnectionCount += 1
|
|
// return s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnectionCount
|
|
// }
|
|
// s.removeViewerConnectionForMonitor = function(monitorObject,viewerDetails){
|
|
// delete(s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnection[viewerDetails.viewerId])
|
|
// s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnectionCount -= 1
|
|
// return s.group[monitorObject.ke].activeMonitors[monitorObject.mid].viewerConnectionCount
|
|
// }
|
|
}
|