node-red-contrib-hik-media-buffer 1.1.70 → 1.1.72

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.
Files changed (2) hide show
  1. package/hik-media-buffer.js +136 -213
  2. package/package.json +1 -1
@@ -5,6 +5,7 @@ const fs = require('fs');
5
5
  const path = require('path');
6
6
  const os = require('os');
7
7
  const { exec } = require('child_process');
8
+ const { channel } = require('diagnostics_channel');
8
9
 
9
10
  module.exports = function(RED) {
10
11
  function HikMediaBufferNode(config) {
@@ -12,88 +13,91 @@ module.exports = function(RED) {
12
13
  const node = this;
13
14
 
14
15
  node.name = config.name || "TEST";
15
- node.host = config.host;
16
+ node.host = config.host;
16
17
  node.port = config.port || "80";
17
18
  node.protocol = config.protocol || "http";
18
19
  node.user = config.user;
19
20
  node.pass = config.pass;
21
+ node.camPass = config.camPass || config.pass;
22
+ node.cameras = config.cameras || [];
20
23
 
21
24
  let streamRequest = null;
22
25
  let isClosing = false;
23
26
  let lastTriggerTime = {};
24
27
  let nvrOnline = true;
25
- let statoCanale = {};
28
+ let statoCamera = {};
26
29
 
27
30
  const httpsAgent = new https.Agent({ rejectUnauthorized: false });
28
31
  const EventList = ["FieldDetection", "LineDetection"];
29
32
 
33
+ // CARTELLA TEMPORANEA
30
34
  const baseStorage = path.join(os.tmpdir(), "hik_temp_media");
31
35
  if (!fs.existsSync(baseStorage)) fs.mkdirSync(baseStorage, { recursive: true });
32
36
 
33
37
  node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
34
38
 
35
- async function getCameraName(channelID) {
36
- const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
39
+ function toHikDate(d) { return d.toISOString().split('.')[0] + "Z"; }
40
+
41
+ // --- PRENDE IL NOME DELLA TELECAMERA ---
42
+ async function getCameraName(cam) {
43
+ const camAuth = new AxiosDigestAuth({
44
+ username: node.user,
45
+ password: node.camPass
46
+ });
47
+
37
48
  try {
38
- const res = await nvrAuth.request({
49
+ const res = await camAuth.request({
39
50
  method: 'GET',
40
- url: `${node.protocol}://${node.host}:${node.port}/ISAPI/System/Video/inputs/channels/${channelID}/overlays/channelNameOverlay`,
51
+ url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/Video/inputs/channels/${cam.channel}/overlays/channelNameOverlay`,
41
52
  timeout: 5000,
42
53
  httpsAgent: node.protocol === "https" ? httpsAgent : undefined
43
54
  });
55
+
44
56
  const data = res.data.toString();
45
57
  const match = data.match(/<name>([^<]+)<\/name>/i);
46
- return match && match[1] ? match[1].trim() : `Canale_${channelID}`;
58
+
59
+ if (match && match[1]) {
60
+ return match[1].trim();
61
+ } else {
62
+ return `Canale_${cam.channel}`;
63
+ }
64
+
47
65
  } catch (e) {
48
- return `Canale_${channelID}`;
66
+ return `Camera_${cam.channel}`;
49
67
  }
50
68
  }
51
69
 
70
+ // --- CONTROLLO STATUS CAMERE ---
52
71
  async function checkCameras() {
53
72
  if (isClosing) return;
54
- const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
55
- try {
56
- const res = await nvrAuth.request({
57
- method: 'GET',
58
- url: `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt/InputProxy/channels/status`,
59
- timeout: 5000,
60
- httpsAgent: node.protocol === "https" ? httpsAgent : undefined
61
- });
62
- nvrOnline = true;
63
- const xmlData = res.data.toString();
64
- const bloccoRegex = /<InputProxyChannelStatus[\s\\S]*?>([\s\S]*?)<\/InputProxyChannelStatus>/gi;
65
- let matchBlocco;
66
-
67
- while ((matchBlocco = bloccoRegex.exec(xmlData)) !== null) {
68
- const bloccoContenuto = matchBlocco[1];
69
- const idMatch = bloccoContenuto.match(/<id>(\d+)<\/id>/i);
70
- const statusMatch = bloccoContenuto.match(/<status>([^<]+)</i);
71
-
72
- if (idMatch && statusMatch) {
73
- const ch = idMatch[1];
74
- const statusStr = statusMatch[1].toLowerCase();
75
- const isOnline = statusStr.includes("online") || statusStr.includes("recording");
76
-
77
- if (isOnline && statoCanale[ch] === false) {
78
- const nomeOnline = await getCameraName(ch);
79
- node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", nome_cliente: node.name, nome_telecamera: nomeOnline, ip_nvr: node.host, channel: ch.toString(), msg: "Camera ripristinata su NVR" } });
80
- statoCanale[ch] = true;
81
- } else if (!isOnline && statoCanale[ch] !== false && statoCanale[ch] !== undefined) {
82
- node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "offline", nome_cliente: node.name, nome_telecamera: `Canale_${ch}`, ip_nvr: node.host, channel: ch.toString(), msg: "Camera scollegata o offline su NVR" } });
83
- statoCanale[ch] = false;
84
- } else if (statoCanale[ch] === undefined) {
85
- statoCanale[ch] = isOnline;
86
- }
73
+ for (let cam of node.cameras) {
74
+ const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
75
+ try {
76
+ await camAuth.request({
77
+ method: 'GET',
78
+ url: `${node.protocol}://${cam.ip}:${node.port}/ISAPI/System/deviceInfo`,
79
+ timeout: 5000,
80
+ httpsAgent: node.protocol === "https" ? httpsAgent : undefined
81
+ });
82
+ if (statoCamera[cam.ip] === false) {
83
+ const nomeOnline = await getCameraName(cam);
84
+ 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" } });
85
+ statoCamera[cam.ip] = true;
86
+ } else if (statoCamera[cam.ip] === undefined) {
87
+ statoCamera[cam.ip] = true;
88
+ }
89
+ } catch (e) {
90
+ if (statoCamera[cam.ip] !== false) {
91
+ 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" } });
92
+ statoCamera[cam.ip] = false;
87
93
  }
88
94
  }
89
- } catch (e) {
90
- nvrOnline = false;
91
95
  }
92
96
  updateNodeStatus();
93
97
  }
94
98
 
95
99
  function updateNodeStatus() {
96
- const offlineCams = Object.values(statoCanale).filter(v => v === false).length;
100
+ const offlineCams = Object.values(statoCamera).filter(v => v === false).length;
97
101
  if (!nvrOnline) {
98
102
  node.status({fill:"red", shape:"ring", text:"NVR Offline"});
99
103
  } else if (offlineCams > 0) {
@@ -105,58 +109,38 @@ module.exports = function(RED) {
105
109
 
106
110
  const heartbeatInterval = setInterval(checkCameras, 30000);
107
111
 
108
- // --- STRATEGIA DI DOWNLOAD MULTIMEDIALE 100% JSON + FFMPEG EXTRACTION ---
109
- // --- STRATEGIA DI DOWNLOAD MULTIMEDIALE 100% JSON + FFMPEG EXTRACTION ---
110
- // --- STRATEGIA DI DOWNLOAD MULTIMEDIALE CON DOPPIO FALLBACK ORARIO (LOCALE / UTC) ---
112
+ // --- DOWNLOAD, SALVATAGGIO, CONVERSIONE E RIMOZIONE DOPO 2 MINUTI ---
111
113
  async function downloadMedia(evento, channelID) {
112
- node.warn(`[warn] [hik-media-buffer:test] [TRIGGER] Ricevuto evento. Evento: ${evento}, Canale originario: ${channelID}`);
114
+ const camera = node.cameras.find(c => c.channel == channelID);
115
+ if (!camera) return;
116
+
117
+ const nomeCamera = await getCameraName(camera);
113
118
 
114
119
  const nowTime = Date.now();
115
- if (!lastTriggerTime[channelID]) lastTriggerTime[channelID] = 0;
116
- if (nowTime - lastTriggerTime[channelID] < 5000) return;
117
- lastTriggerTime[channelID] = nowTime;
120
+ if(!lastTriggerTime[camera.ip]){
121
+ lastTriggerTime[camera.ip] = 0;
122
+ }
123
+ if (nowTime - lastTriggerTime[camera.ip] < 5000) return;
124
+ lastTriggerTime[camera.ip] = nowTime;
118
125
 
119
- const camAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
126
+ const camAuth = new AxiosDigestAuth({ username: node.user, password: node.camPass });
120
127
  const referenceTime = new Date();
121
128
  const timestamp = Math.floor(referenceTime.getTime() / 1000);
122
-
123
- // FUNZIONE 1: FORMATTO IN ORA LOCALE (Es. YYYY-MM-DDTHH:mm:ss)
124
- const formatLocaleDate = (d) => {
125
- const pad = (num) => String(num).padStart(2, '0');
126
- return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
127
- };
128
-
129
- // FUNZIONE 2: FORMATTO IN ORA UTC (Es. YYYY-MM-DDTHH:mm:ssZ)
130
- const formatUtcDate = (d) => {
131
- const pad = (num) => String(num).padStart(2, '0');
132
- return `${d.getUTCFullYear()}-${pad(d.getUTCMonth()+1)}-${pad(d.getUTCDate())}T${pad(d.getUTCHours())}:${pad(d.getUTCMinutes())}:${pad(d.getUTCSeconds())}Z`;
133
- };
129
+ const startTime = toHikDate(new Date(referenceTime.getTime() - (10 * 1000)));
130
+ const endTime = toHikDate(new Date(referenceTime.getTime() + (10 * 1000)));
134
131
 
135
132
  node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
136
- const nomeCamera = await getCameraName(channelID);
133
+ await new Promise(resolve => setTimeout(resolve, 6000));
137
134
 
138
- // Aspettiamo 8 secondi per dare tempo all'Hard Disk di stabilizzare il file temporaneo
139
- node.warn(`[warn] [hik-media-buffer:test] Attesa scrittura indice Hard Disk (8s)...`);
140
- await new Promise(resolve => setTimeout(resolve, 8000));
135
+ const baseUrl = `${node.protocol}://${camera.ip}:${node.port}/ISAPI/ContentMgmt`;
141
136
 
142
- const baseUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt`;
143
- const evMinuscolo = evento.toLowerCase();
144
-
145
- // CREIAMO LE FINESTRE TEMPORALI: Dal passato fino a ORA (Senza sforare nel futuro!)
146
- const oraLocaleRichiesta = new Date(); // Questo è il momento esatto del termine degli 8s
147
- const startLocale = formatLocaleDate(new Date(oraLocaleRichiesta.getTime() - (90 * 1000))); // 90 secondi fa
148
- const endLocale = formatLocaleDate(oraLocaleRichiesta); // Esattamente ADESSO
149
-
150
- const oraUtcRichiesta = new Date();
151
- const startUtc = formatUtcDate(new Date(oraUtcRichiesta.getTime() - (90 * 1000))); // 90 secondi fa
152
- const endUtc = formatUtcDate(oraUtcRichiesta); // Esattamente ADESSO
153
-
137
+ // struttura del payload per Python
154
138
  let output = {
155
139
  tipo_messaggio: "evento",
156
140
  nome_cliente: node.name,
157
141
  nome_telecamera: nomeCamera,
158
- ip_telecamera: node.host,
159
- tipo_evento: evMinuscolo.includes("linedetection") ? "LineDetection" : "FieldDetection",
142
+ ip_telecamera: camera.ip,
143
+ tipo_evento: evento,
160
144
  timestamp_epoch: timestamp,
161
145
  stato_telecamera: "ONLINE",
162
146
  channel: channelID.toString(),
@@ -164,143 +148,87 @@ module.exports = function(RED) {
164
148
  video_base64: null
165
149
  };
166
150
 
167
- const parsedChannel = parseInt(channelID, 10);
168
- let fileDaCancellare = [];
169
- let videoUriMatch = null;
170
-
171
- // --- TENTATIVO 1: RICERCA IN ORA LOCALE (La più probabile per i dischi NVR) ---
172
- const startLocale = formatLocaleDate(new Date(referenceTime.getTime() - (120 * 1000)));
173
- const endLocale = formatLocaleDate(new Date(referenceTime.getTime() + (30 * 1000)));
174
-
175
- node.warn(`[warn] [hik-media-buffer:test] [TENTATIVO 1] Ricerca video in Ora Locale: ${startLocale} -> ${endLocale}`);
176
151
 
177
- const trackVideoId = (parsedChannel * 100) + 1;
178
-
179
- let payloadLocale = {
180
- "EventSearchDescription": {
181
- "searchID": "BA80CAF2-A1E5-42E3-8624-529C3D6665D7",
182
- "searchResultPosition": 0,
183
- "maxResults": 10,
184
- "timeSpanList": [{"startTime": startLocale, "endTime": endLocale}],
185
- "type": "all",
186
- "channels": [trackVideoId],
187
- "eventType": "behavior",
188
- "behavior": {"behaviorEventType": evMinuscolo}
189
- }
190
- };
152
+ let fileDaCancellare = [];
191
153
 
192
154
  try {
193
- let resLocale = await camAuth.request({
194
- method: 'POST',
195
- url: `${baseUrl}/eventRecordSearch?format=json`,
196
- data: payloadLocale,
197
- headers: { "Content-Type": "application/json" },
198
- timeout: 5000
199
- });
200
- let dataStr = JSON.stringify(resLocale.data);
201
- videoUriMatch = dataStr.match(/rtsp:\/\/[^"]+/i) || dataStr.match(/\/ISAPI\/ContentMgmt\/download[^"]+/i);
202
- } catch (e) {
203
- node.warn(`[warn] [hik-media-buffer:test] Tentativo 1 fallito o timeout: ${e.message}`);
204
- }
155
+ const trackV = (channelID*100+1).toString();
156
+ const trackI = (channelID*100+3).toString();
157
+ const tracks = [{ id: trackV }, { id: trackI }];
158
+ for (let t of tracks) {
159
+ const searchXml = `<?xml version="1.0" encoding="utf-8"?><CMSearchDescription><searchID>LAST_EVENT</searchID><trackIDList><trackID>${t.id}</trackID></trackIDList><timeSpanList><timeSpan><startTime>${startTime}</startTime><endTime>${endTime}</endTime></timeSpan></timeSpanList><maxResults>100</maxResults><metadataList><metadataDescriptor>//recordType.meta.std-cgi.com/${evento}</metadataDescriptor></metadataList></CMSearchDescription>`;
160
+
161
+ const resSearch = await camAuth.request({
162
+ method: 'POST', url: `${baseUrl}/search`, data: searchXml, headers: { "Content-Type": "application/xml" }
163
+ });
164
+
165
+ let xml = resSearch.data.replace(/<(\/?)\w+:/g, "<$1");
166
+ const uriMatch = xml.match(/<playbackURI>([^<]+)</);
167
+
168
+ if (uriMatch) {
169
+ const rawUri = uriMatch[1].replace(/&amp;/g, '&');
170
+ const resDown = await camAuth.request({
171
+ method: 'GET',
172
+ url: `${baseUrl}/download`,
173
+ data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
174
+ responseType: 'arraybuffer'
175
+ });
205
176
 
206
- // --- TENTATIVO 2: FALLBACK IN ORA UTC (Se il primo tentativo è andato a vuoto) ---
207
- if (!videoUriMatch) {
208
- const startUtc = formatUtcDate(new Date(referenceTime.getTime() - (120 * 1000)));
209
- const endUtc = formatUtcDate(new Date(referenceTime.getTime() + (30 * 1000)));
210
-
211
- node.warn(`[warn] [hik-media-buffer:test] [TENTATIVO 2] Video locale non trovato. Provo Fallback in Ora UTC: ${startUtc} -> ${endUtc}`);
212
-
213
- let payloadUtc = {
214
- "EventSearchDescription": {
215
- "searchID": "BA80CAF2-A1E5-42E3-8624-529C3D6665D7",
216
- "searchResultPosition": 0,
217
- "maxResults": 10,
218
- "timeSpanList": [{"startTime": startUtc, "endTime": endUtc}],
219
- "type": "all",
220
- "channels": [trackVideoId],
221
- "eventType": "behavior",
222
- "behavior": {"behaviorEventType": evMinuscolo}
177
+ let buffer = Buffer.from(resDown.data);
178
+ if (t.id === "203") {
179
+ // SALVA FOTO IN LOCALE
180
+ const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
181
+ fs.writeFileSync(fullImgPath, buffer);
182
+
183
+ // La convertiamo subito in testo per il payload
184
+ output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
185
+
186
+ // Registriamo il file per la distruzione futura
187
+ fileDaCancellare.push(fullImgPath);
188
+ } else {
189
+ // SALVA VIDEO IN LOCALE-
190
+ if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
191
+ const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
192
+ const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
193
+ fs.writeFileSync(rawPath, buffer);
194
+
195
+ // Eseguiamo ffmpeg localmente
196
+ await new Promise((resolve) => {
197
+ exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
198
+ if (!err && fs.existsSync(fixedPath)) {
199
+ output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
200
+ fileDaCancellare.push(fixedPath);
201
+ } else {
202
+ output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
203
+ }
204
+ fileDaCancellare.push(rawPath);
205
+ resolve();
206
+ });
207
+ });
208
+ }
223
209
  }
224
- };
225
-
226
- try {
227
- let resUtc = await camAuth.request({
228
- method: 'POST',
229
- url: `${baseUrl}/eventRecordSearch?format=json`,
230
- data: payloadUtc,
231
- headers: { "Content-Type": "application/json" },
232
- timeout: 5000
233
- });
234
- let dataStr = JSON.stringify(resUtc.data);
235
- videoUriMatch = dataStr.match(/rtsp:\/\/[^"]+/i) || dataStr.match(/\/ISAPI\/ContentMgmt\/download[^"]+/i);
236
- } catch (e) {
237
- node.error(`[error] [hik-media-buffer:test] Errore critico anche nel Fallback UTC: ${e.message}`);
238
210
  }
239
- }
240
211
 
241
- // --- PROCESSO DI SCARICAMENTO SE UNA DELLE DUE QUERIES HA TROVATO IL VIDEO ---
242
- if (videoUriMatch) {
243
- let rawVideoUri = videoUriMatch[0].replace(/\\/g, '');
244
- node.warn(`[warn] [hik-media-buffer:test] [FOUND] Trovato playbackURI video: ${rawVideoUri}`);
245
-
246
- try {
247
- const resDownVideo = await camAuth.request({
248
- method: 'GET',
249
- url: `${baseUrl}/download`,
250
- data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawVideoUri.replace(/&/g, '&amp;')}</playbackURI></downloadRequest>`,
251
- responseType: 'arraybuffer'
252
- });
253
-
254
- let videoBuffer = Buffer.from(resDownVideo.data);
255
- if (videoBuffer.slice(0, 4).toString() === 'IMKH') videoBuffer = videoBuffer.slice(40);
256
-
257
- const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
258
- const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
259
- const extractedImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
212
+ // Spediamo il pacchetto completo verso l'HTTP Request tramite Node-RED
213
+ if (output.foto_base64 || output.video_base64) {
214
+ node.send({ payload: output });
260
215
 
261
- fs.writeFileSync(rawPath, videoBuffer);
262
- fileDaCancellare.push(rawPath);
263
-
264
- node.warn(`[warn] [hik-media-buffer:test] Esecuzione FFMPEG (Estrazione JPG + FastStart MP4)...`);
265
- await new Promise((resolve) => {
266
- exec(`ffmpeg -y -i "${rawPath}" -ss 00:00:01 -vframes 1 "${extractedImgPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
267
- if (!err) {
268
- if (fs.existsSync(extractedImgPath)) {
269
- output.foto_base64 = fs.readFileSync(extractedImgPath, { encoding: 'base64' });
270
- fileDaCancellare.push(extractedImgPath);
271
- node.warn(`[warn] [hik-media-buffer:test] Snapshot FOTO estratto con successo.`);
272
- }
273
- if (fs.existsSync(fixedPath)) {
274
- output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
275
- fileDaCancellare.push(fixedPath);
276
- node.warn(`[warn] [hik-media-buffer:test] Video MP4 indicizzato con successo.`);
216
+ // TIMER A 2 MINUTI PER LA PULIZIA DEL DISCO
217
+ setTimeout(() => {
218
+ for (let file of fileDaCancellare) {
219
+ try {
220
+ if (fs.existsSync(file)) {
221
+ fs.unlinkSync(file);
277
222
  }
278
- } else {
279
- node.warn(`[warn] [hik-media-buffer:test] Errore FFMPEG, uso file grezzo: ${err.message}`);
280
- output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
223
+ } catch (err) {
224
+ node.error(`Errore durante la pulizia del file temporaneo ${file}: ${err.message}`);
281
225
  }
282
- resolve();
283
- });
284
- });
285
- } catch (downErr) {
286
- node.error(`[error] [hik-media-buffer:test] Errore durante il download fisico del file: ${downErr.message}`);
226
+ }
227
+ }, 120000);
287
228
  }
288
- } else {
289
- node.warn(`[warn] [hik-media-buffer:test] [NOT FOUND] Il video non è stato trovato in nessuna delle due finestre temporali (Locale/UTC). Verificare se l'NVR sta effettivamente registrando l'evento.`);
290
- }
291
229
 
292
- // --- SPEDIZIONE PAYLOAD VERSO PYTHON ---
293
- if (output.foto_base64 || output.video_base64) {
294
- node.warn(`[warn] [hik-media-buffer:test] Spedizione payload multimediale completata con successo!`);
295
- node.send({ payload: output });
296
-
297
- setTimeout(() => {
298
- for (let file of fileDaCancellare) {
299
- try { if (fs.existsSync(file)) fs.unlinkSync(file); } catch (err) {}
300
- }
301
- }, 120000);
302
- } else {
303
- node.warn(`[warn] [hik-media-buffer:test] Nessun pacchetto inviato a Node-RED.`);
230
+ } catch (e) {
231
+ node.error(`Errore Download Cam ${channelID}: ${e.message}`);
304
232
  }
305
233
  updateNodeStatus();
306
234
  }
@@ -310,22 +238,18 @@ module.exports = function(RED) {
310
238
  if (isClosing) return;
311
239
  const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
312
240
  const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
313
-
314
241
  nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
315
242
  .then(response => {
316
243
  streamRequest = response;
317
244
  if (!nvrOnline) { node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", ip: node.host, msg: "NVR Online", nome_cliente: node.name } }); nvrOnline = true; }
318
245
  updateNodeStatus();
319
-
320
246
  response.data.on('data', (chunk) => {
321
- if (isClosing) return;
322
247
  const data = chunk.toString().toLowerCase();
323
248
  if (data.includes("active")) {
324
249
  const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
325
250
  if (chMatch) {
326
251
  let ev = "Unknown";
327
252
  for (let e of EventList) { if (data.includes(e.toLowerCase())) { ev = e; break; } }
328
- if (ev === "Unknown") return;
329
253
  downloadMedia(ev, chMatch[1]);
330
254
  }
331
255
  }
@@ -343,11 +267,10 @@ module.exports = function(RED) {
343
267
 
344
268
  startAlertStream();
345
269
  setTimeout(checkCameras, 2000);
346
-
347
270
  node.on('close', (done) => {
348
271
  isClosing = true;
349
272
  clearInterval(heartbeatInterval);
350
- if (streamRequest && streamRequest.data) streamRequest.data.destroy();
273
+ if (streamRequest) streamRequest.data.destroy();
351
274
  done();
352
275
  });
353
276
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-red-contrib-hik-media-buffer",
3
- "version": "1.1.70",
3
+ "version": "1.1.72",
4
4
  "description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
5
5
  "keywords": [
6
6
  "node-red",