node-red-contrib-hik-media-buffer 1.1.57 → 1.1.59
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 +122 -143
- package/package.json +2 -1
package/hik-media-buffer.js
CHANGED
|
@@ -12,7 +12,7 @@ module.exports = function(RED) {
|
|
|
12
12
|
const node = this;
|
|
13
13
|
|
|
14
14
|
node.name = config.name || "TEST";
|
|
15
|
-
node.host = config.host; // IP dell'NVR
|
|
15
|
+
node.host = config.host; // IP dell'NVR Centrale
|
|
16
16
|
node.port = config.port || "80";
|
|
17
17
|
node.protocol = config.protocol || "http";
|
|
18
18
|
node.user = config.user;
|
|
@@ -25,19 +25,19 @@ module.exports = function(RED) {
|
|
|
25
25
|
let statoCanale = {};
|
|
26
26
|
|
|
27
27
|
const httpsAgent = new https.Agent({ rejectUnauthorized: false });
|
|
28
|
-
|
|
29
|
-
// EventList per l'alertStream
|
|
30
28
|
const EventList = ["FieldDetection", "LineDetection"];
|
|
31
29
|
|
|
32
|
-
// CARTELLA TEMPORANEA
|
|
33
30
|
const baseStorage = path.join(os.tmpdir(), "hik_temp_media");
|
|
34
31
|
if (!fs.existsSync(baseStorage)) fs.mkdirSync(baseStorage, { recursive: true });
|
|
35
32
|
|
|
36
33
|
node.status({fill:"grey", shape:"ring", text:"Inizializzazione..."});
|
|
37
34
|
|
|
38
|
-
|
|
35
|
+
// FORMATTAZIONE DATA CORRETTA PER HARD DISK NVR (Rimosso lo zulu 'Z' che mandava in 400 il database)
|
|
36
|
+
function toHikDate(d) {
|
|
37
|
+
const pad = (num) => String(num).padStart(2, '0');
|
|
38
|
+
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
39
|
+
}
|
|
39
40
|
|
|
40
|
-
// --- PRENDE IL NOME DEL CANALE DIRETTAMENTE DALL'NVR ---
|
|
41
41
|
async function getCameraName(channelID) {
|
|
42
42
|
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
43
43
|
try {
|
|
@@ -47,7 +47,6 @@ module.exports = function(RED) {
|
|
|
47
47
|
timeout: 5000,
|
|
48
48
|
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
49
49
|
});
|
|
50
|
-
|
|
51
50
|
const data = res.data.toString();
|
|
52
51
|
const match = data.match(/<name>([^<]+)<\/name>/i);
|
|
53
52
|
return match && match[1] ? match[1].trim() : `Canale_${channelID}`;
|
|
@@ -56,11 +55,9 @@ module.exports = function(RED) {
|
|
|
56
55
|
}
|
|
57
56
|
}
|
|
58
57
|
|
|
59
|
-
// --- CONTROLLO STATUS DINAMICO DEI CANALI DIGITALI DALL'NVR ---
|
|
60
58
|
async function checkCameras() {
|
|
61
59
|
if (isClosing) return;
|
|
62
60
|
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
63
|
-
|
|
64
61
|
try {
|
|
65
62
|
const res = await nvrAuth.request({
|
|
66
63
|
method: 'GET',
|
|
@@ -68,23 +65,19 @@ module.exports = function(RED) {
|
|
|
68
65
|
timeout: 5000,
|
|
69
66
|
httpsAgent: node.protocol === "https" ? httpsAgent : undefined
|
|
70
67
|
});
|
|
71
|
-
|
|
72
68
|
nvrOnline = true;
|
|
73
69
|
const xmlData = res.data.toString();
|
|
74
|
-
|
|
75
70
|
const bloccoRegex = /<InputProxyChannelStatus[\s\\S]*?>([\s\S]*?)<\/InputProxyChannelStatus>/gi;
|
|
76
71
|
let matchBlocco;
|
|
77
72
|
|
|
78
73
|
while ((matchBlocco = bloccoRegex.exec(xmlData)) !== null) {
|
|
79
74
|
const bloccoContenuto = matchBlocco[1];
|
|
80
|
-
|
|
81
75
|
const idMatch = bloccoContenuto.match(/<id>(\d+)<\/id>/i);
|
|
82
76
|
const statusMatch = bloccoContenuto.match(/<status>([^<]+)</i);
|
|
83
77
|
|
|
84
78
|
if (idMatch && statusMatch) {
|
|
85
79
|
const ch = idMatch[1];
|
|
86
80
|
const statusStr = statusMatch[1].toLowerCase();
|
|
87
|
-
|
|
88
81
|
const isOnline = statusStr.includes("online") || statusStr.includes("recording");
|
|
89
82
|
|
|
90
83
|
if (isOnline && statoCanale[ch] === false) {
|
|
@@ -118,42 +111,42 @@ module.exports = function(RED) {
|
|
|
118
111
|
|
|
119
112
|
const heartbeatInterval = setInterval(checkCameras, 30000);
|
|
120
113
|
|
|
121
|
-
// ---
|
|
114
|
+
// --- IL TUO DOWNLOAD MEDIA INIZIALE CORRETTO (100% XML SEPARATO) ---
|
|
122
115
|
async function downloadMedia(evento, channelID) {
|
|
123
|
-
node.warn(`[STEP 1] Inizio downloadMedia. Evento: ${evento}, Canale originario: ${channelID}`);
|
|
116
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 1] Inizio downloadMedia. Evento: ${evento}, Canale originario: ${channelID}`);
|
|
124
117
|
|
|
125
118
|
const nowTime = Date.now();
|
|
126
119
|
if (!lastTriggerTime[channelID]) lastTriggerTime[channelID] = 0;
|
|
127
|
-
if (nowTime - lastTriggerTime[channelID] < 5000)
|
|
120
|
+
if (nowTime - lastTriggerTime[channelID] < 5000) {
|
|
121
|
+
node.warn(`[warn] [hik-media-buffer:test] Antirimbalzo bloccato.`);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
128
124
|
lastTriggerTime[channelID] = nowTime;
|
|
129
125
|
|
|
130
126
|
const camAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
131
127
|
const referenceTime = new Date();
|
|
132
128
|
const timestamp = Math.floor(referenceTime.getTime() / 1000);
|
|
133
129
|
|
|
134
|
-
//
|
|
135
|
-
const
|
|
136
|
-
const
|
|
137
|
-
|
|
138
|
-
// Finestre temporali per il JSON dell'NVR (con specifica dell'offset locale, es. "+02:00")
|
|
139
|
-
const formatJsonDate = (d) => {
|
|
140
|
-
const pad = (num) => String(num).padStart(2, '0');
|
|
141
|
-
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())} 02:00`;
|
|
142
|
-
};
|
|
143
|
-
const startTimeJson = formatJsonDate(new Date(referenceTime.getTime() - (20 * 1000)));
|
|
144
|
-
const endTimeJson = formatJsonDate(new Date(referenceTime.getTime() + (20 * 1000)));
|
|
130
|
+
// Generazione finestre temporali pulite per il database dei dischi dell'NVR
|
|
131
|
+
const startTime = toHikDate(new Date(referenceTime.getTime() - (20 * 1000)));
|
|
132
|
+
const endTime = toHikDate(new Date(referenceTime.getTime() + (20 * 1000)));
|
|
133
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 2] Finestra temporale impostata: ${startTime} -> ${endTime}`);
|
|
145
134
|
|
|
146
135
|
node.status({fill:"yellow", shape:"dot", text:`Download Cam ${channelID}...`});
|
|
147
136
|
const nomeCamera = await getCameraName(channelID);
|
|
148
137
|
|
|
149
|
-
node.warn(`[STEP
|
|
138
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 3] Attesa di 6 secondi per la scrittura su disco dell'NVR...`);
|
|
150
139
|
await new Promise(resolve => setTimeout(resolve, 6000));
|
|
151
140
|
|
|
152
141
|
const baseUrl = `${node.protocol}://${node.host}:${node.port}/ISAPI/ContentMgmt`;
|
|
153
142
|
|
|
154
|
-
|
|
155
|
-
let
|
|
156
|
-
|
|
143
|
+
let eventoNVR = "FieldDetection";
|
|
144
|
+
for (let e of EventList) {
|
|
145
|
+
if (evento.toLowerCase() === e.toLowerCase()) {
|
|
146
|
+
eventoNVR = e;
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
157
150
|
|
|
158
151
|
let output = {
|
|
159
152
|
tipo_messaggio: "evento",
|
|
@@ -169,144 +162,129 @@ module.exports = function(RED) {
|
|
|
169
162
|
};
|
|
170
163
|
|
|
171
164
|
const parsedChannel = parseInt(channelID, 10);
|
|
172
|
-
const
|
|
165
|
+
const trackVideo = (parsedChannel * 100) + 1;
|
|
166
|
+
const trackFoto = (parsedChannel * 100) + 3;
|
|
167
|
+
|
|
168
|
+
// Le due tracce originali del tuo ciclo
|
|
169
|
+
const tracks = [
|
|
170
|
+
{ id: trackVideo.toString(), isFoto: false, label: "VIDEO" },
|
|
171
|
+
{ id: trackFoto.toString(), isFoto: true, label: "FOTO" }
|
|
172
|
+
];
|
|
173
173
|
let fileDaCancellare = [];
|
|
174
174
|
|
|
175
|
-
// --- PARTE 1: RICERCA E SCARICO VIDEO VIA JSON ---
|
|
176
|
-
node.warn(`[STEP 3 - VIDEO] Invio query JSON a eventRecordSearch per canale ${parsedChannel}...`);
|
|
177
|
-
const searchJsonPayload = {
|
|
178
|
-
"EventSearchDescription": {
|
|
179
|
-
"searchID": "BA80CAF2-A1E5-42E3-8624-529C3D6665D7",
|
|
180
|
-
"searchResultPosition": 0,
|
|
181
|
-
"maxResults": 30,
|
|
182
|
-
"timeSpanList": [{
|
|
183
|
-
"startTime": startTimeJson,
|
|
184
|
-
"endTime": endTimeJson
|
|
185
|
-
}],
|
|
186
|
-
"type": "all",
|
|
187
|
-
"channels": [parsedChannel],
|
|
188
|
-
"eventType": "behavior",
|
|
189
|
-
"behavior": {
|
|
190
|
-
"behaviorEventType": eventoMinuscolo
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
};
|
|
194
|
-
|
|
195
175
|
try {
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
url: `${baseUrl}/eventRecordSearch?format=json`,
|
|
199
|
-
data: searchJsonPayload,
|
|
200
|
-
headers: { "Content-Type": "application/json" }
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
const resDataStr = JSON.stringify(resSearchVideo.data);
|
|
204
|
-
const videoUriMatch = resDataStr.match(/rtsp:\/\/[^"]+/i) || resDataStr.match(/\/ISAPI\/ContentMgmt\/download[^"]+/i);
|
|
205
|
-
|
|
206
|
-
if (videoUriMatch) {
|
|
207
|
-
let rawVideoUri = videoUriMatch[0].replace(/\\/g, '');
|
|
208
|
-
node.warn(`[STEP 3.1 - VIDEO] Trovato URI video nel JSON: ${rawVideoUri}`);
|
|
176
|
+
for (let t of tracks) {
|
|
177
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 4] Elaborazione traccia ${t.label} (ID: ${t.id})`);
|
|
209
178
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
url: `${baseUrl}/download`,
|
|
213
|
-
data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawVideoUri.replace(/&/g, '&')}</playbackURI></downloadRequest>`,
|
|
214
|
-
responseType: 'arraybuffer'
|
|
215
|
-
});
|
|
179
|
+
// SEPARAZIONE DEI DESCRITTORI: timing per la traccia video, evento per lo snapshot foto
|
|
180
|
+
let descriptorTarget = t.isFoto ? `recordType.meta.hikvision.com/${eventoNVR}` : `recordType.meta.hikvision.com/timing`;
|
|
216
181
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
182
|
+
// LA TUA STRUTTURA XML INIZIALE PULITA E ALLINEATA AI MANUALI NVR
|
|
183
|
+
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>30</maxResults><metadataList><metadataDescriptor>${descriptorTarget}</metadataDescriptor></metadataList></CMSearchDescription>`;
|
|
184
|
+
|
|
185
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 4.1] Invio POST /search per traccia ${t.id}. XML inviato:\n${searchXml}`);
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
const resSearch = await camAuth.request({
|
|
189
|
+
method: 'POST',
|
|
190
|
+
url: `${baseUrl}/search`,
|
|
191
|
+
data: searchXml,
|
|
192
|
+
headers: { "Content-Type": "application/xml" }
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 4.2] Risposta ricevuta da /search per traccia ${t.id}: Status ${resSearch.status}`);
|
|
196
|
+
|
|
197
|
+
let xml = resSearch.data.replace(/<(\/?)\w+:/g, "<$1");
|
|
198
|
+
const uriMatch = xml.match(/<playbackURI>([^<]+)</);
|
|
199
|
+
|
|
200
|
+
if (uriMatch) {
|
|
201
|
+
const rawUri = uriMatch[1].replace(/&/g, '&');
|
|
202
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 5] Trovato playbackURI per traccia ${t.id}: ${rawUri}`);
|
|
203
|
+
|
|
204
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 5.1] Invio richiesta GET /download per traccia ${t.id}`);
|
|
205
|
+
const resDown = await camAuth.request({
|
|
206
|
+
method: 'GET',
|
|
207
|
+
url: `${baseUrl}/download`,
|
|
208
|
+
data: `<?xml version="1.0" encoding="UTF-8"?><downloadRequest><playbackURI>${rawUri.replace(/&/g, '&')}</playbackURI></downloadRequest>`,
|
|
209
|
+
responseType: 'arraybuffer'
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 5.2] Scaricati ${resDown.data.byteLength} byte per traccia ${t.id}`);
|
|
213
|
+
let buffer = Buffer.from(resDown.data);
|
|
214
|
+
|
|
215
|
+
if (t.isFoto) {
|
|
216
|
+
const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
|
|
217
|
+
fs.writeFileSync(fullImgPath, buffer);
|
|
218
|
+
output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
|
|
219
|
+
fileDaCancellare.push(fullImgPath);
|
|
220
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 5.3] Immagine salvata e convertita in Base64.`);
|
|
230
221
|
} else {
|
|
231
|
-
|
|
222
|
+
if (buffer.slice(0, 4).toString() === 'IMKH') buffer = buffer.slice(40);
|
|
223
|
+
const rawPath = path.join(baseStorage, `raw_${timestamp}.mp4`);
|
|
224
|
+
const fixedPath = path.join(baseStorage, `hik_v_${channelID}_${timestamp}.mp4`);
|
|
225
|
+
fs.writeFileSync(rawPath, buffer);
|
|
226
|
+
|
|
227
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 5.3] Video grezzo salvato. Avvio conversione FFMPEG...`);
|
|
228
|
+
await new Promise((resolve) => {
|
|
229
|
+
exec(`ffmpeg -y -i "${rawPath}" -c copy -movflags +faststart "${fixedPath}"`, (err) => {
|
|
230
|
+
if (!err && fs.existsSync(fixedPath)) {
|
|
231
|
+
output.video_base64 = fs.readFileSync(fixedPath, { encoding: 'base64' });
|
|
232
|
+
fileDaCancellare.push(fixedPath);
|
|
233
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 5.4] Conversione FFMPEG completata con successo.`);
|
|
234
|
+
} else {
|
|
235
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 5.4] Errore FFMPEG (uso il video grezzo di backup): ${err ? err.message : 'File mancante'}`);
|
|
236
|
+
output.video_base64 = fs.readFileSync(rawPath, { encoding: 'base64' });
|
|
237
|
+
}
|
|
238
|
+
fileDaCancellare.push(rawPath);
|
|
239
|
+
resolve();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
232
242
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
}
|
|
236
|
-
})
|
|
237
|
-
|
|
238
|
-
|
|
243
|
+
} else {
|
|
244
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 4.3] Nessun playbackURI trovato nel documento di risposta XML per traccia ${t.id}. XML di risposta:\n${resSearch.data}`);
|
|
245
|
+
}
|
|
246
|
+
} catch (searchError) {
|
|
247
|
+
let errorDetails = searchError.message;
|
|
248
|
+
if (searchError.response) {
|
|
249
|
+
errorDetails += ` | Status: ${searchError.response.status} | Dati Risposta: ${JSON.stringify(searchError.response.data)}`;
|
|
250
|
+
}
|
|
251
|
+
node.error(`[error] [hik-media-buffer:test] [CATCH TRACCIA ${t.id}] Errore durante la POST di ricerca: ${errorDetails}`);
|
|
252
|
+
}
|
|
239
253
|
}
|
|
240
|
-
} catch (videoErr) {
|
|
241
|
-
node.error(`[VIDEO ERR] Errore durante la ricerca JSON: ${videoErr.message}`);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
// --- PARTE 2: RICERCA E SCARICO FOTO VIA XML (CORRETTO) ---
|
|
245
|
-
node.warn(`[STEP 4 - FOTO] Invio query XML a /search per traccia foto ${trackFoto}...`);
|
|
246
|
-
|
|
247
|
-
const searchXmlFoto = `<?xml version="1.0" encoding="utf-8"?><CMSearchDescription><searchID>LAST_EVENT</searchID><trackIDList><trackID>${trackFoto}</trackID></trackIDList><timeSpanList><timeSpan><startTime>${startTimeZ}</startTime><endTime>${endTimeZ}</endTime></timeSpan></timeSpanList><contentTypeList><contentType>metadata</contentType></contentTypeList><maxResults>30</maxResults><searchResultPosition>0</searchResultPosition><metadataList><metadataDescriptor>recordType.meta.hikvision.com/timing</metadataDescriptor></metadataList></CMSearchDescription>`;
|
|
248
254
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
url: `${baseUrl}/search`,
|
|
253
|
-
data: searchXmlFoto,
|
|
254
|
-
headers: { "Content-Type": "application/xml" }
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
let xml = resSearchFoto.data.replace(/<(\/?)\w+:/g, "<$1");
|
|
258
|
-
const uriMatch = xml.match(/<playbackURI>([^<]+)</);
|
|
259
|
-
|
|
260
|
-
if (uriMatch) {
|
|
261
|
-
const rawUriFoto = uriMatch[1].replace(/&/g, '&');
|
|
262
|
-
node.warn(`[STEP 4.1 - FOTO] Trovato URI foto: ${rawUriFoto}`);
|
|
255
|
+
if (output.foto_base64 || output.video_base64) {
|
|
256
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 6] Spedizione payload verso Node-RED effettuata con successo!`);
|
|
257
|
+
node.send({ payload: output });
|
|
263
258
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
const fullImgPath = path.join(baseStorage, `img_${timestamp}.jpg`);
|
|
272
|
-
fs.writeFileSync(fullImgPath, Buffer.from(resDownFoto.data));
|
|
273
|
-
output.foto_base64 = fs.readFileSync(fullImgPath, { encoding: 'base64' });
|
|
274
|
-
fileDaCancellare.push(fullImgPath);
|
|
275
|
-
node.warn(`[STEP 4.2 - FOTO] Foto salvata e convertita correttamente.`);
|
|
259
|
+
setTimeout(() => {
|
|
260
|
+
for (let file of fileDaCancellare) {
|
|
261
|
+
try { if (fs.existsSync(file)) fs.unlinkSync(file); } catch (err) {}
|
|
262
|
+
}
|
|
263
|
+
}, 120000);
|
|
276
264
|
} else {
|
|
277
|
-
node.warn(`[
|
|
265
|
+
node.warn(`[warn] [hik-media-buffer:test] [STEP 6] Nessun media Base64 popolato, nessun pacchetto inviato a Node-RED.`);
|
|
278
266
|
}
|
|
279
|
-
} catch (fotoErr) {
|
|
280
|
-
node.error(`[FOTO ERR] Errore durante la ricerca XML: ${fotoErr.message}`);
|
|
281
|
-
}
|
|
282
267
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
node.warn(`[STEP 5] Spedizione payload multimediale completata!`);
|
|
286
|
-
node.send({ payload: output });
|
|
287
|
-
|
|
288
|
-
setTimeout(() => {
|
|
289
|
-
for (let file of fileDaCancellare) {
|
|
290
|
-
try { if (fs.existsSync(file)) fs.unlinkSync(file); } catch (err) {}
|
|
291
|
-
}
|
|
292
|
-
}, 120000);
|
|
293
|
-
} else {
|
|
294
|
-
node.warn(`[STEP 5] Nessun file multimediale scaricato, nessun pacchetto inviato.`);
|
|
268
|
+
} catch (e) {
|
|
269
|
+
node.error(`[error] [hik-media-buffer:test] [FATAL ERROR] Blocco principale downloadMedia fallito: ${e.message}`);
|
|
295
270
|
}
|
|
296
271
|
updateNodeStatus();
|
|
297
272
|
}
|
|
298
273
|
|
|
299
|
-
// --- ALERT STREAM ---
|
|
274
|
+
// --- ALERT STREAM ORIGINALE ---
|
|
300
275
|
function startAlertStream() {
|
|
301
276
|
if (isClosing) return;
|
|
302
277
|
const nvrAuth = new AxiosDigestAuth({ username: node.user, password: node.pass });
|
|
303
278
|
const url = `${node.protocol}://${node.host}:${node.port}/ISAPI/Event/notification/alertStream`;
|
|
279
|
+
|
|
304
280
|
nvrAuth.request({ method: 'GET', url: url, responseType: 'stream', httpsAgent: node.protocol === "https" ? httpsAgent : undefined })
|
|
305
281
|
.then(response => {
|
|
306
282
|
streamRequest = response;
|
|
307
283
|
if (!nvrOnline) { node.send({ payload: { tipo_messaggio: "status", stato_telecamera: "online", ip: node.host, msg: "NVR Online", nome_cliente: node.name } }); nvrOnline = true; }
|
|
308
284
|
updateNodeStatus();
|
|
285
|
+
|
|
309
286
|
response.data.on('data', (chunk) => {
|
|
287
|
+
if (isClosing) return;
|
|
310
288
|
const data = chunk.toString().toLowerCase();
|
|
311
289
|
if (data.includes("active")) {
|
|
312
290
|
const chMatch = data.match(/<channelid>(\d+)<\/channelid>/i);
|
|
@@ -331,10 +309,11 @@ module.exports = function(RED) {
|
|
|
331
309
|
|
|
332
310
|
startAlertStream();
|
|
333
311
|
setTimeout(checkCameras, 2000);
|
|
312
|
+
|
|
334
313
|
node.on('close', (done) => {
|
|
335
314
|
isClosing = true;
|
|
336
315
|
clearInterval(heartbeatInterval);
|
|
337
|
-
if (streamRequest) streamRequest.data.destroy();
|
|
316
|
+
if (streamRequest && streamRequest.data) streamRequest.data.destroy();
|
|
338
317
|
done();
|
|
339
318
|
});
|
|
340
319
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-red-contrib-hik-media-buffer",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.59",
|
|
4
4
|
"description": "Ottiene buffer video e immagine da camere Hikvision via ISAPI",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"node-red",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"dependencies": {
|
|
23
23
|
"@mhoc/axios-digest-auth": "^0.8.0",
|
|
24
24
|
"axios": "^1.6.0",
|
|
25
|
+
"node-red-contrib-hik-media-buffer": "^1.1.58",
|
|
25
26
|
"xml2js": "^0.6.2"
|
|
26
27
|
}
|
|
27
28
|
}
|