node-red-contrib-hik-media-buffer 1.1.108 → 1.1.110
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/hik-media-buffer.js +159 -193
- package/package.json +1 -1
package/hik-media-buffer.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const axios = require('axios');
|
|
2
2
|
const AxiosDigestAuth = require('@mhoc/axios-digest-auth').default;
|
|
3
|
+
const https = require('https');
|
|
3
4
|
const fs = require('fs');
|
|
4
5
|
const path = require('path');
|
|
5
6
|
const os = require('os');
|
|
@@ -16,6 +17,8 @@ module.exports = function(RED) {
|
|
|
16
17
|
node.protocol = config.protocol || "http";
|
|
17
18
|
node.user = config.user;
|
|
18
19
|
node.pass = config.pass;
|
|
20
|
+
node.camPass = config.camPass || config.pass;
|
|
21
|
+
node.cameras = config.cameras || [];
|
|
19
22
|
|
|
20
23
|
let streamRequest = null;
|
|
21
24
|
let isClosing = false;
|
|
@@ -23,260 +26,227 @@ module.exports = function(RED) {
|
|
|
23
26
|
let nvrOnline = true;
|
|
24
27
|
let statoCamera = {};
|
|
25
28
|
|
|
29
|
+
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
30
|
+
const EventList = ["FieldDetection", "LineDetection"];
|
|
31
|
+
|
|
32
|
+
// CARTELLA TEMPORANEA
|
|
26
33
|
const baseStorage = path.join(os.tmpdir(), "hik_temp_media");
|
|
27
34
|
if (!fs.existsSync(baseStorage)) fs.mkdirSync(baseStorage, { recursive: true });
|
|
28
35
|
|
|
29
36
|
node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
|
|
30
37
|
|
|
31
|
-
|
|
38
|
+
function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
|
|
32
39
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
});
|
|
40
|
-
const match = res.data.toString().match(/<name>([^<]+)<\/name>/i);
|
|
41
|
-
if (match && match[1]) return match[1].trim();
|
|
42
|
-
return `Canale_${channelID}`;
|
|
43
|
-
} catch (e) { return `Camera_${channelID}`; }
|
|
44
|
-
}
|
|
40
|
+
// --- PRENDE IL NOME DELLA TELECAMERA ---
|
|
41
|
+
async function getCameraName(cam) {
|
|
42
|
+
const camAuth = new AxiosDigestAuth({
|
|
43
|
+
username: node.user,
|
|
44
|
+
password: node.camPass
|
|
45
|
+
});
|
|
45
46
|
|
|
46
|
-
async function checkCameras() {
|
|
47
|
-
if (isClosing || !nvrOnline) return;
|
|
48
47
|
try {
|
|
49
|
-
const res = await
|
|
48
|
+
const res = await camAuth.request({
|
|
50
49
|
method: 'GET',
|
|
51
|
-
url: `${node.protocol}://${
|
|
52
|
-
timeout:
|
|
50
|
+
url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}/overlays/channelNameOverlay`,
|
|
51
|
+
timeout: 5000,
|
|
52
|
+
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
53
53
|
});
|
|
54
|
-
const xml = res.data.toString();
|
|
55
|
-
const statusBlocks = xml.match(/<InputProxyChannelStatus>[\s\S]*?<\/InputProxyChannelStatus>/gi) || [];
|
|
56
54
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
const data = res.data.toString();
|
|
56
|
+
const match = data.match(/<name>([^<]+)<\/name>/i);
|
|
57
|
+
|
|
58
|
+
if (match && match[1]) {
|
|
59
|
+
return match[1].trim();
|
|
60
|
+
} else {
|
|
61
|
+
return `Canale_${cam.channel}`;
|
|
62
|
+
}
|
|
60
63
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
} catch (e) {
|
|
65
|
+
return `Camera_${cam.channel}`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
64
68
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
69
|
+
// --- CONTROLLO STATUS CAMERE ---
|
|
70
|
+
async function checkCameras() {
|
|
71
|
+
if (isClosing) return;
|
|
72
|
+
for (let cam of node.cameras) {
|
|
73
|
+
const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
|
|
74
|
+
try {
|
|
75
|
+
await camAuth.request({
|
|
76
|
+
method: 'GET',
|
|
77
|
+
url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
|
|
78
|
+
timeout: 5000,
|
|
79
|
+
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
80
|
+
});
|
|
81
|
+
if (statoCamera[cam.ip] === false) {
|
|
82
|
+
const nomeOnline = await getCameraName(cam);
|
|
83
|
+
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", nome_cliente: node.name, nome_telecamera: nomeOnline, ip_telecamera: cam.ip, channel: cam.channel, msg: "Camera ripristinata" } });
|
|
84
|
+
statoCamera[cam.ip] = true;
|
|
85
|
+
} else if (statoCamera[cam.ip] === undefined) {
|
|
86
|
+
statoCamera[cam.ip] = true;
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
if (statoCamera[cam.ip] !== false) {
|
|
90
|
+
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", nome_cliente: node.name, nome_telecamera: `Camera_${cam.channel}`, ip_telecamera: cam.ip, channel: cam.channel, msg: "Camera non raggiungibile" } });
|
|
91
|
+
statoCamera[cam.ip] = false;
|
|
73
92
|
}
|
|
74
93
|
}
|
|
75
|
-
}
|
|
94
|
+
}
|
|
76
95
|
updateNodeStatus();
|
|
77
96
|
}
|
|
78
97
|
|
|
79
98
|
function updateNodeStatus() {
|
|
80
99
|
const offlineCams = Object.values(statoCamera).filter(v => v === false).length;
|
|
81
|
-
if (!nvrOnline) {
|
|
82
|
-
|
|
83
|
-
else
|
|
100
|
+
if (!nvrOnline) {
|
|
101
|
+
node.status({fill:"red", shape:"ring", text:"NVR Offline"});
|
|
102
|
+
} else if (offlineCams > 0) {
|
|
103
|
+
node.status({fill:"yellow", shape:"dot", text: `${offlineCams} Cam Offline`});
|
|
104
|
+
} else {
|
|
105
|
+
node.status({fill:"green", shape:"ring", text:"In ascolto"});
|
|
106
|
+
}
|
|
84
107
|
}
|
|
85
108
|
|
|
86
109
|
const heartbeatInterval = setInterval(checkCameras, 30000);
|
|
87
110
|
|
|
88
|
-
// ---
|
|
111
|
+
// --- DOWNLOAD, SALVATAGGIO, CONVERSIONE E RIMOZIONE DOPO 2 MINUTI ---
|
|
89
112
|
async function downloadMedia(evento, channelID) {
|
|
90
|
-
const
|
|
113
|
+
const camera = node.cameras.find(c => c.channel == channelID);
|
|
114
|
+
if (!camera) return;
|
|
115
|
+
|
|
116
|
+
const nomeCamera = await getCameraName(camera);
|
|
117
|
+
|
|
91
118
|
const nowTime = Date.now();
|
|
92
|
-
if
|
|
93
|
-
|
|
94
|
-
|
|
119
|
+
if(!lastTriggerTime[camera.ip]){
|
|
120
|
+
lastTriggerTime[camera.ip] = 0;
|
|
121
|
+
}
|
|
122
|
+
if (nowTime - lastTriggerTime[camera.ip] < 5000) return;
|
|
123
|
+
lastTriggerTime[camera.ip] = nowTime;
|
|
95
124
|
|
|
96
|
-
const
|
|
125
|
+
const camAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
97
126
|
const referenceTime = new Date();
|
|
98
127
|
const timestamp = Math.floor(referenceTime.getTime() / 1000);
|
|
128
|
+
const startTime = toHikDate(new Date(referenceTime.getTime() - (10 * 1000)));
|
|
129
|
+
const endTime = toHikDate(new Date(referenceTime.getTime() + (10 * 1000)));
|
|
99
130
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const year = referenceTime.getFullYear();
|
|
103
|
-
const month = pad(referenceTime.getMonth() + 1);
|
|
104
|
-
const day = pad(referenceTime.getDate());
|
|
131
|
+
node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
|
|
132
|
+
await new Promise(resolve => setTimeout(resolve, 6000));
|
|
105
133
|
|
|
106
|
-
const
|
|
107
|
-
const endTime = `${year}-${month}-${day}T23:59:59 01:00`;
|
|
108
|
-
|
|
109
|
-
node.status({fill:"yellow", shape:"dot", text:`Generazione Sessione...`});
|
|
134
|
+
const baseUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt`;
|
|
110
135
|
|
|
136
|
+
// struttura del payload per Python
|
|
111
137
|
let output = {
|
|
112
|
-
tipo_messaggio: "evento",
|
|
113
|
-
|
|
114
|
-
|
|
138
|
+
tipo_messaggio: "evento",
|
|
139
|
+
nome_cliente: node.name,
|
|
140
|
+
nome_telecamera: nomeCamera,
|
|
141
|
+
ip_telecamera: camera.ip,
|
|
142
|
+
tipo_evento: evento,
|
|
143
|
+
timestamp_epoch: timestamp,
|
|
144
|
+
stato_telecamera: "ONLINE",
|
|
145
|
+
channel: channelID.toString(),
|
|
146
|
+
foto_base64: null,
|
|
147
|
+
video_base64: null
|
|
115
148
|
};
|
|
116
149
|
|
|
150
|
+
|
|
117
151
|
let fileDaCancellare = [];
|
|
118
152
|
|
|
119
153
|
try {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
method: 'GET',
|
|
124
|
-
url: `${node.protocol}://${node.host}:${node.port}/ISAPI/Security/sessionLogin/capabilities?format=json`,
|
|
125
|
-
timeout: 5000
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
// Estraiamo il cookie "WebSession" dagli header di risposta
|
|
129
|
-
const responseHeaders = loginRes.headers['set-cookie'] || [];
|
|
130
|
-
let webSessionValue = '';
|
|
131
|
-
let cookieKeyName = 'WebSession';
|
|
132
|
-
|
|
133
|
-
for (let cookie of responseHeaders) {
|
|
134
|
-
if (cookie.includes('WebSession')) {
|
|
135
|
-
const matchCookie = cookie.match(/(WebSession_[a-f0-9]+|WebSession)=([^;]+)/i);
|
|
136
|
-
if (matchCookie) {
|
|
137
|
-
cookieKeyName = matchCookie[1];
|
|
138
|
-
webSessionValue = matchCookie[2];
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Se l'NVR non ha sputato il cookie, ne generiamo uno fittizio in MD5 basandoci sul timestamp
|
|
144
|
-
// perché in molti firmware basta che l'header esista ed abbia lo stesso valore del cookie!
|
|
145
|
-
if (!webSessionValue) {
|
|
146
|
-
webSessionValue = require('crypto').createHash('md5').update(String(Date.now())).digest('hex');
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
node.warn(`[DEBUG SESSION] Clonazione Cookie -> ${cookieKeyName}=${webSessionValue}`);
|
|
150
|
-
|
|
151
|
-
// --- STEP 2: COSTRUZIONE QUERY DIRETTA CON DATI DI CHROME ---
|
|
152
|
-
const dynamicSearchId = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
153
|
-
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
|
154
|
-
return v.toString(16).toUpperCase();
|
|
155
|
-
});
|
|
154
|
+
const tracks = [{ id: "203" }, { id: "201" }];
|
|
155
|
+
for (let t of tracks) {
|
|
156
|
+
const searchXml = `<?xml version="1.0" encoding="utf-8"?><CMSearchDescription><searchID>LAST_EVENT</searchID><trackIDList><trackID>${t.id}</trackID></trackIDList><timeSpanList><timeSpan><startTime>2026-06-25T00:00:00Z</startTime><endTime>2026-06-26T23:59:59Z</endTime></timeSpan></timeSpanList><contentTypeList><contentType>metadata</contentType></contentTypeList><maxResults>30</maxResults><searchResultPostion>0</searchResultPostion><metadataList><metadataDescriptor>recordType.meta.hikvision.com/LineDetection</metadataDescriptor></metadataList></CMSearchDescription>`;
|
|
156
157
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
"searchResultPosition": 0,
|
|
161
|
-
"maxResults": 30,
|
|
162
|
-
"timeSpanList": [{ "startTime": startTime, "endTime": endTime }],
|
|
163
|
-
"type": "all",
|
|
164
|
-
"channels": [ parseInt(channelID) ],
|
|
165
|
-
"eventType": "behavior",
|
|
166
|
-
"behavior": { "behaviorEventType": evento.toLowerCase() }
|
|
167
|
-
}
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const tempJsonPath = path.join(baseStorage, `search_${channelID}_${timestamp}.json`);
|
|
171
|
-
fs.writeFileSync(tempJsonPath, JSON.stringify(searchPayload), 'utf8');
|
|
172
|
-
|
|
173
|
-
// --- STEP 3: LANCE DIRETTO VIA CURL CON I TRE ASSI NELLA MANICA ---
|
|
174
|
-
// 1. Content-Type form-urlencoded (anche se mandiamo un JSON, come fa Chrome!)
|
|
175
|
-
// 2. Cookie WebSession inserito a mano
|
|
176
|
-
// 3. sessiontag identico al valore del cookie
|
|
177
|
-
const targetUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/eventRecordSearch?format=json`;
|
|
178
|
-
|
|
179
|
-
const curlCommand = `curl -s -X POST --digest -u "${node.user}:${node.pass}" ` +
|
|
180
|
-
`-H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" ` +
|
|
181
|
-
`-H "X-Requested-With: XMLHttpRequest" ` +
|
|
182
|
-
`-H "sessiontag: ${webSessionValue}" ` +
|
|
183
|
-
`-b "${cookieKeyName}=${webSessionValue}" ` +
|
|
184
|
-
`-d "@${tempJsonPath}" "${targetUrl}"`;
|
|
185
|
-
|
|
186
|
-
node.warn(`[DEBUG JSON] Invio query protetta con token di sessione...`);
|
|
187
|
-
let jsonResponseRaw = await new Promise((resolve) => { exec(curlCommand, (err, stdout) => resolve(err ? null : stdout)); });
|
|
188
|
-
|
|
189
|
-
try { if (fs.existsSync(tempJsonPath)) fs.unlinkSync(tempJsonPath); } catch(e){}
|
|
190
|
-
|
|
191
|
-
if (!jsonResponseRaw) {
|
|
192
|
-
node.error("[DEBUG JSON] Nessuna risposta dall'NVR.");
|
|
193
|
-
updateNodeStatus();
|
|
194
|
-
return;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
const searchResult = JSON.parse(jsonResponseRaw);
|
|
198
|
-
const matches = searchResult?.EventSearchResult?.matchList || [];
|
|
199
|
-
|
|
200
|
-
if (matches.length === 0) {
|
|
201
|
-
node.warn(`[DEBUG JSON] Zero match trovati. La sessione non è bastata o i parametri differiscono.`);
|
|
202
|
-
updateNodeStatus();
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Se arriviamo qui, l'array si è sbloccato!
|
|
207
|
-
const matchItem = matches[0];
|
|
208
|
-
const playbackUri = matchItem?.mediaSegmentDescriptor?.playbackURI;
|
|
209
|
-
|
|
210
|
-
if (playbackUri) {
|
|
211
|
-
node.warn(`[DEBUG JSON] Match sbloccato con successo! URL: ${playbackUri}`);
|
|
158
|
+
const resSearch = await camAuth.request({
|
|
159
|
+
method: 'POST', url: `${baseUrl}/search`, data: searchXml, headers: { "Content-Type": "application/xml" }
|
|
160
|
+
});
|
|
212
161
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
if (
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
162
|
+
let xml = resSearch.data.replace(/<(\/?)\w+:/g, "<$1");
|
|
163
|
+
const uriMatch = xml.match(/<playbackURI>([^<]+)</);
|
|
164
|
+
|
|
165
|
+
if (uriMatch) {
|
|
166
|
+
const rawUri = uriMatch[1].replace(/&/g, '&');
|
|
167
|
+
const resDown = await camAuth.request({
|
|
168
|
+
method: 'GET',
|
|
169
|
+
url: `${baseUrl}/download`,
|
|
170
|
+
data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&')}</playbackURI></downloadRequest>`,
|
|
171
|
+
responseType: 'arraybuffer'
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
let buffer = Buffer.from(resDown.data);
|
|
175
|
+
if (t.id === "203") {
|
|
176
|
+
// SALVA FOTO IN LOCALE
|
|
177
|
+
const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
|
|
178
|
+
fs.writeFileSync(fullImgPath, buffer);
|
|
179
|
+
|
|
180
|
+
// La convertiamo subito in testo per il payload
|
|
226
181
|
output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
|
|
182
|
+
|
|
183
|
+
// Registriamo il file per la distruzione futura
|
|
227
184
|
fileDaCancellare.push(fullImgPath);
|
|
185
|
+
} else {
|
|
186
|
+
// SALVA VIDEO IN LOCALE-
|
|
187
|
+
if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
|
|
188
|
+
const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
|
|
189
|
+
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
190
|
+
fs.writeFileSync(rawPath, buffer);
|
|
191
|
+
|
|
192
|
+
// Eseguiamo ffmpeg localmente
|
|
193
|
+
await new Promise((resolve) => {
|
|
194
|
+
exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
195
|
+
if (!err && fs.existsSync(fixedPath)) {
|
|
196
|
+
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
197
|
+
fileDaCancellare.push(fixedPath);
|
|
198
|
+
} else {
|
|
199
|
+
output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
|
|
200
|
+
}
|
|
201
|
+
fileDaCancellare.push(rawPath);
|
|
202
|
+
resolve();
|
|
203
|
+
});
|
|
204
|
+
});
|
|
228
205
|
}
|
|
229
206
|
}
|
|
230
|
-
|
|
231
|
-
// --- DOWNLOAD VIDEO NATIVO REGISTRATO SU HDD VIA FFMPEG ---
|
|
232
|
-
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
233
|
-
const authenticatedRtspUrl = playbackUri.replace("rtsp://", `rtsp://${node.user}:${node.pass}@`);
|
|
234
|
-
const ffmpegRtspCmd = `ffmpeg -y -rtsp_transport tcp -i "${authenticatedRtspUrl}" -t 15 -c:v copy -c:a aac -movflags +faststart "${fixedPath}"`;
|
|
235
|
-
|
|
236
|
-
await new Promise((resolve) => { exec(ffmpegRtspCmd, { timeout: 25000 }, () => resolve()); });
|
|
237
|
-
|
|
238
|
-
if (fs.existsSync(fixedPath) && fs.statSync(fixedPath).size > 1000) {
|
|
239
|
-
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
240
|
-
fileDaCancellare.push(fixedPath);
|
|
241
|
-
}
|
|
242
207
|
}
|
|
243
208
|
|
|
209
|
+
// Spediamo il pacchetto completo verso l'HTTP Request tramite Node-RED
|
|
244
210
|
if (output.foto_base64 || output.video_base64) {
|
|
245
211
|
node.send({ payload: output });
|
|
212
|
+
|
|
213
|
+
// TIMER A 2 MINUTI PER LA PULIZIA DEL DISCO
|
|
246
214
|
setTimeout(() => {
|
|
247
215
|
for (let file of fileDaCancellare) {
|
|
248
|
-
try {
|
|
216
|
+
try {
|
|
217
|
+
if (fs.existsSync(file)) {
|
|
218
|
+
fs.unlinkSync(file);
|
|
219
|
+
}
|
|
220
|
+
} catch (err) {
|
|
221
|
+
node.error(`Errore durante la pulizia del file temporaneo ${file}: ${err.message}`);
|
|
222
|
+
}
|
|
249
223
|
}
|
|
250
|
-
},
|
|
224
|
+
}, 120000);
|
|
251
225
|
}
|
|
252
226
|
|
|
253
227
|
} catch (e) {
|
|
254
|
-
node.error(`Errore
|
|
228
|
+
node.error(`Errore Download Cam ${channelID}: ${e.message}`);
|
|
255
229
|
}
|
|
256
230
|
updateNodeStatus();
|
|
257
231
|
}
|
|
258
232
|
|
|
259
|
-
// ---
|
|
233
|
+
// --- ALERT STREAM ---
|
|
260
234
|
function startAlertStream() {
|
|
261
235
|
if (isClosing) return;
|
|
236
|
+
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
262
237
|
const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
|
|
263
|
-
|
|
264
|
-
nvrAuthAxios.request({ method: 'GET', url: url, responseType: 'stream' })
|
|
238
|
+
nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
|
|
265
239
|
.then(response => {
|
|
266
240
|
streamRequest = response;
|
|
267
|
-
if (!nvrOnline) {
|
|
268
|
-
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", ip: node.host, msg: "NVR Online", nome_cliente: node.name } });
|
|
269
|
-
nvrOnline = true;
|
|
270
|
-
}
|
|
241
|
+
if (!nvrOnline) { node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", ip: node.host, msg: "NVR Online", nome_cliente: node.name } }); nvrOnline = true; }
|
|
271
242
|
updateNodeStatus();
|
|
272
|
-
|
|
273
243
|
response.data.on('data', (chunk) => {
|
|
274
|
-
const data = chunk.toString();
|
|
275
|
-
if (data.
|
|
276
|
-
const chMatch = data.match(/<
|
|
244
|
+
const data = chunk.toString().toLowerCase();
|
|
245
|
+
if (data.includes("active")) {
|
|
246
|
+
const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
|
|
277
247
|
if (chMatch) {
|
|
278
|
-
let ev = "
|
|
279
|
-
if (data.
|
|
248
|
+
let ev = "Unknown";
|
|
249
|
+
for (let e of EventList) { if (data.includes(e.toLowerCase())) { ev = e; break; } }
|
|
280
250
|
downloadMedia(ev, chMatch[1]);
|
|
281
251
|
}
|
|
282
252
|
}
|
|
@@ -287,17 +257,13 @@ module.exports = function(RED) {
|
|
|
287
257
|
}
|
|
288
258
|
|
|
289
259
|
function handleNvrError() {
|
|
290
|
-
if (nvrOnline) {
|
|
291
|
-
node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", ip: node.host, msg: "NVR Offline", nome_cliente: node.name } });
|
|
292
|
-
nvrOnline = false;
|
|
293
|
-
}
|
|
260
|
+
if (nvrOnline) { node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", ip: node.host, msg: "NVR Offline", nome_cliente: node.name } }); nvrOnline = false; }
|
|
294
261
|
updateNodeStatus();
|
|
295
262
|
if (!isClosing) setTimeout(startAlertStream, 10000);
|
|
296
263
|
}
|
|
297
264
|
|
|
298
265
|
startAlertStream();
|
|
299
266
|
setTimeout(checkCameras, 2000);
|
|
300
|
-
|
|
301
267
|
node.on('close', (done) => {
|
|
302
268
|
isClosing = true;
|
|
303
269
|
clearInterval(heartbeatInterval);
|