iobroker.agent-dvr 0.0.6 → 0.2.0

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/lib/web.js CHANGED
@@ -42,13 +42,15 @@ class AgentDvrWeb {
42
42
  adapter.getForeignStatesAsync(ns + '.cam_*.status.recording'),
43
43
  adapter.getForeignStatesAsync(ns + '.cam_*.status.detected'),
44
44
  adapter.getForeignStatesAsync(ns + '.cam_*.status.alerted'),
45
- ]).then(([nameStates, mjpegStates, snapStates, onlineStates, ptzStates, recStates, detStates, alertStates]) => {
45
+ adapter.getForeignStatesAsync(ns + '.cam_*.data.width'),
46
+ adapter.getForeignStatesAsync(ns + '.cam_*.data.height'),
47
+ ]).then(([nameStates, mjpegStates, snapStates, onlineStates, ptzStates, recStates, detStates, alertStates, widthStates, heightStates]) => {
46
48
  const cameras = {};
47
49
 
48
50
  Object.entries(nameStates || {}).forEach(([id, state]) => {
49
51
  const rel = id.slice(ns.length + 1);
50
52
  const camKey = rel.split('.')[0];
51
- const m = camKey.match(/^cam_(\d+)_/);
53
+ const m = camKey.match(/^cam_(\d+)/);
52
54
  cameras[camKey] = {
53
55
  name: (state && state.val != null) ? String(state.val) : camKey,
54
56
  oid: m ? m[1] : null,
@@ -106,6 +108,23 @@ class AgentDvrWeb {
106
108
  });
107
109
  }
108
110
 
111
+ // Build aspect from data.width / data.height states written by flattenWrite.
112
+ // Reading from ioBroker states is reliable regardless of whether the web
113
+ // extension runs in the same process as the main adapter.
114
+ const wMap = {}, hMap = {};
115
+ Object.entries(widthStates || {}).forEach(([id, s]) => {
116
+ const k = id.slice(ns.length + 1).split('.')[0];
117
+ if (s && typeof s.val === 'number') wMap[k] = s.val;
118
+ });
119
+ Object.entries(heightStates || {}).forEach(([id, s]) => {
120
+ const k = id.slice(ns.length + 1).split('.')[0];
121
+ if (s && typeof s.val === 'number') hMap[k] = s.val;
122
+ });
123
+ Object.entries(cameras).forEach(([k, cam]) => {
124
+ const w = wMap[k], h = hMap[k];
125
+ cam.aspect = (w >= 100 && h >= 100 && w / h >= 0.3 && w / h <= 4) ? w + '/' + h : '';
126
+ });
127
+
109
128
  res.setHeader('Content-Type', 'application/json');
110
129
  res.end(JSON.stringify({ instance: ns, base, cameras }));
111
130
  }).catch(err => {
@@ -132,15 +151,41 @@ class AgentDvrWeb {
132
151
  dashColorOnline: native.dashColorOnline || '#22c55e',
133
152
  dashColorOffline: native.dashColorOffline || '#ef4444',
134
153
  dashTagPosition: native.dashTagPosition || 'top-right',
135
- go2rtcEnabled: !!native.go2rtcEnabled,
154
+ dashStreamType: native.dashStreamType || 'mjpeg',
136
155
  go2rtcUrl: native.go2rtcUrl || '',
137
156
  go2rtcMapping: Array.isArray(native.go2rtcMapping) ? native.go2rtcMapping : [],
157
+ cameraStreams: (native.cameraStreams && typeof native.cameraStreams === 'object') ? native.cameraStreams : {},
158
+ dashLiveSource: native.dashLiveSource || 'agentdvr',
138
159
  }));
139
160
  });
140
161
 
162
+ // ── FLV stream proxy (avoids browser CORS block for flv.js XHR) ────────
163
+ app.get('/' + ROUTE + '/api/stream', (req, res) => {
164
+ const base = (native.ip && native.port) ? 'http://' + native.ip + ':' + native.port : '';
165
+ if (!base) { res.status(503).end('AgentDVR not configured'); return; }
166
+ const oid = String(req.query.oid || '');
167
+ if (!oid || !/^\d+$/.test(oid)) { res.status(400).end('invalid oid'); return; }
168
+ const size = String(req.query.size || '');
169
+ const sizeStr = /^\d{1,5}x\d{1,5}$/.test(size) ? '&size=' + size : '';
170
+ const url = base + '/video.mp4?oid=' + oid + sizeStr;
171
+ let target;
172
+ try { target = new URL(url); } catch (_) { res.status(500).end(); return; }
173
+ const mod = target.protocol === 'https:' ? https : http;
174
+ const upReq = mod.get(url, upstream => {
175
+ res.setHeader('Content-Type', 'video/x-flv');
176
+ res.setHeader('Cache-Control', 'no-cache');
177
+ res.setHeader('X-Accel-Buffering', 'no');
178
+ upstream.pipe(res);
179
+ upstream.on('error', () => { if (!res.headersSent) res.status(502).end(); else res.end(); });
180
+ });
181
+ upReq.setTimeout(8000, () => upReq.destroy());
182
+ upReq.on('error', () => { if (!res.headersSent) res.status(502).end(); else res.end(); });
183
+ req.on('close', () => { try { upReq.destroy(); } catch (_) {} });
184
+ });
185
+
141
186
  // ── go2rtc streams proxy ─────────────────────────────────────────────
142
187
  app.get('/' + ROUTE + '/api/go2rtc/streams', (req, res) => {
143
- if (!native.go2rtcEnabled || !native.go2rtcUrl) {
188
+ if (!native.go2rtcUrl) {
144
189
  res.setHeader('Content-Type', 'application/json');
145
190
  res.end('{"streams":[]}');
146
191
  return;
@@ -235,6 +280,8 @@ class AgentDvrWeb {
235
280
  if (err) { res.status(404).end('Not found'); return; }
236
281
  const ext = path.extname(file).toLowerCase();
237
282
  res.setHeader('Content-Type', MIME[ext] || 'application/octet-stream');
283
+ // HTML must never be cached so updates are visible immediately
284
+ if (ext === '.html') res.setHeader('Cache-Control', 'no-store');
238
285
  res.end(data);
239
286
  });
240
287
  });
@@ -244,7 +291,7 @@ class AgentDvrWeb {
244
291
  // ── WebSocket proxy for go2rtc (bypasses browser cross-origin block) ─
245
292
  server.on('upgrade', (req, socket, head) => {
246
293
  if (!(req.url || '').startsWith('/' + ROUTE + '/api/ws')) return;
247
- if (!native.go2rtcEnabled || !native.go2rtcUrl) {
294
+ if (!native.go2rtcUrl) {
248
295
  socket.write('HTTP/1.1 503 Service Unavailable\r\n\r\n');
249
296
  socket.destroy();
250
297
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iobroker.agent-dvr",
3
- "version": "0.0.6",
3
+ "version": "0.2.0",
4
4
  "description": "Connects ioBroker to AgentDVR: auto-discovers cameras, mirrors all device values as data points, delivers real-time triggers on new recordings, and generates a responsive HTML gallery with optional search and tag filtering.",
5
5
  "author": {
6
6
  "name": "ipod86",