@apocaliss92/camstack-js-sdk 0.1.5
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/README.md +57 -0
- package/dist/index.cjs +1074 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +759 -0
- package/dist/index.d.ts +759 -0
- package/dist/index.js +995 -0
- package/dist/index.js.map +1 -0
- package/package.json +31 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,995 @@
|
|
|
1
|
+
// src/proxy-client.ts
|
|
2
|
+
import {
|
|
3
|
+
createTRPCClient,
|
|
4
|
+
httpBatchLink,
|
|
5
|
+
splitLink,
|
|
6
|
+
createWSClient,
|
|
7
|
+
wsLink
|
|
8
|
+
} from "@trpc/client";
|
|
9
|
+
import superjson from "superjson";
|
|
10
|
+
var ProxyClient = class {
|
|
11
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
12
|
+
trpc;
|
|
13
|
+
baseUrl;
|
|
14
|
+
constructor(config) {
|
|
15
|
+
this.baseUrl = config.proxyUrl.replace(/\/+$/, "");
|
|
16
|
+
const trpcUrl = `${this.baseUrl}/trpc`;
|
|
17
|
+
const headers = {};
|
|
18
|
+
if (config.token) {
|
|
19
|
+
headers["Authorization"] = `Bearer ${config.token}`;
|
|
20
|
+
}
|
|
21
|
+
const wsProtocol = this.baseUrl.startsWith("https") ? "wss" : "ws";
|
|
22
|
+
const wsHost = this.baseUrl.replace(/^https?:\/\//, "");
|
|
23
|
+
const wsUrl = config.token ? `${wsProtocol}://${wsHost}/trpc?token=${encodeURIComponent(config.token)}` : `${wsProtocol}://${wsHost}/trpc`;
|
|
24
|
+
const wsClient = createWSClient({ url: wsUrl });
|
|
25
|
+
this.trpc = createTRPCClient({
|
|
26
|
+
links: [
|
|
27
|
+
splitLink({
|
|
28
|
+
condition: (op) => op.type === "subscription",
|
|
29
|
+
true: wsLink({ client: wsClient, transformer: superjson }),
|
|
30
|
+
false: httpBatchLink({
|
|
31
|
+
url: trpcUrl,
|
|
32
|
+
transformer: superjson,
|
|
33
|
+
headers
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
]
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
// ─── Providers ───────────────────────────────────────────────────
|
|
40
|
+
async listProviders() {
|
|
41
|
+
return this.trpc.providers.list.query();
|
|
42
|
+
}
|
|
43
|
+
// ─── Config ────────────────────────────────────────────────────
|
|
44
|
+
async getConfig(providerId) {
|
|
45
|
+
return this.trpc.config.getConfig.query({ providerId });
|
|
46
|
+
}
|
|
47
|
+
async getStreams(providerId) {
|
|
48
|
+
return this.trpc.config.getStreams.query({ providerId });
|
|
49
|
+
}
|
|
50
|
+
// ─── Cameras ───────────────────────────────────────────────────
|
|
51
|
+
async listCameras(providerId) {
|
|
52
|
+
return this.trpc.cameras.list.query({ providerId });
|
|
53
|
+
}
|
|
54
|
+
async getSnapshot(camera, providerId) {
|
|
55
|
+
return this.trpc.cameras.getSnapshot.query({ camera, providerId });
|
|
56
|
+
}
|
|
57
|
+
// ─── Events ────────────────────────────────────────────────────
|
|
58
|
+
async getEvents(params) {
|
|
59
|
+
return this.trpc.events.list.query(params);
|
|
60
|
+
}
|
|
61
|
+
async getRecordings(params) {
|
|
62
|
+
return this.trpc.events.getRecordings.query(params);
|
|
63
|
+
}
|
|
64
|
+
async getMotionActivity(params) {
|
|
65
|
+
return this.trpc.events.getMotionActivity.query(params);
|
|
66
|
+
}
|
|
67
|
+
async getAudioSegments(camera, after, before, providerId) {
|
|
68
|
+
return this.trpc.events.getAudioSegments.query({ camera, after, before, providerId });
|
|
69
|
+
}
|
|
70
|
+
async getEventThumbnail(eventId, providerId) {
|
|
71
|
+
return this.trpc.events.getEventThumbnail.query({ eventId, providerId });
|
|
72
|
+
}
|
|
73
|
+
async getEventSnapshot(eventId, providerId) {
|
|
74
|
+
return this.trpc.events.getEventSnapshot.query({ eventId, providerId });
|
|
75
|
+
}
|
|
76
|
+
// ─── WebRTC ────────────────────────────────────────────────────
|
|
77
|
+
async webrtcOffer(streamName, sdpOffer, providerId) {
|
|
78
|
+
const result = await this.trpc.webrtc.offer.mutate({ streamName, sdpOffer, providerId });
|
|
79
|
+
return result.sdpAnswer;
|
|
80
|
+
}
|
|
81
|
+
// ─── HLS ───────────────────────────────────────────────────────
|
|
82
|
+
async getRecordingHls(camera, startTime, endTime, providerId) {
|
|
83
|
+
const result = await this.trpc.hls.getRecordingHls.query({
|
|
84
|
+
camera,
|
|
85
|
+
startTime,
|
|
86
|
+
endTime,
|
|
87
|
+
providerId
|
|
88
|
+
});
|
|
89
|
+
return `${this.baseUrl}${result.hlsUrl}`;
|
|
90
|
+
}
|
|
91
|
+
async getEventHls(eventId, providerId) {
|
|
92
|
+
const result = await this.trpc.hls.getEventHls.query({ eventId, providerId });
|
|
93
|
+
return `${this.baseUrl}${result.hlsUrl}`;
|
|
94
|
+
}
|
|
95
|
+
// ─── Live subscriptions ───────────────────────────────────────
|
|
96
|
+
subscribeLiveEvents(callback, options) {
|
|
97
|
+
const { onError, onStarted, onStopped, ...subOptions } = options ?? {};
|
|
98
|
+
return this.trpc.live.onEvent.subscribe(subOptions, {
|
|
99
|
+
onData: callback,
|
|
100
|
+
onError: onError ?? ((err) => console.error("[ProxyClient] subscription error:", err)),
|
|
101
|
+
onStarted: onStarted ?? (() => console.log("[ProxyClient] subscription started")),
|
|
102
|
+
onStopped: onStopped ?? (() => console.log("[ProxyClient] subscription stopped"))
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
subscribeMotion(camera, callback, providerId) {
|
|
106
|
+
return this.trpc.live.onMotion.subscribe(
|
|
107
|
+
{ camera, providerId },
|
|
108
|
+
{ onData: callback }
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
subscribeDetection(camera, callback, providerId) {
|
|
112
|
+
return this.trpc.live.onDetection.subscribe(
|
|
113
|
+
{ camera, providerId },
|
|
114
|
+
{ onData: callback }
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
subscribeAudioLevel(camera, callback, providerId) {
|
|
118
|
+
return this.trpc.live.onAudioLevel.subscribe(
|
|
119
|
+
{ camera, providerId },
|
|
120
|
+
{ onData: callback }
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
// src/direct-client.ts
|
|
126
|
+
var DirectClient = class {
|
|
127
|
+
baseUrl;
|
|
128
|
+
type;
|
|
129
|
+
username;
|
|
130
|
+
password;
|
|
131
|
+
token = null;
|
|
132
|
+
constructor(config) {
|
|
133
|
+
this.baseUrl = config.directUrl.replace(/\/+$/, "");
|
|
134
|
+
this.type = config.type;
|
|
135
|
+
this.username = config.username;
|
|
136
|
+
this.password = config.password;
|
|
137
|
+
}
|
|
138
|
+
// ─── HTTP helpers ──────────────────────────────────────────────
|
|
139
|
+
authHeaders() {
|
|
140
|
+
if (this.token) return { Authorization: `Bearer ${this.token}` };
|
|
141
|
+
if (this.username && this.password) {
|
|
142
|
+
const basic = btoa(`${this.username}:${this.password}`);
|
|
143
|
+
return { Authorization: `Basic ${basic}` };
|
|
144
|
+
}
|
|
145
|
+
return {};
|
|
146
|
+
}
|
|
147
|
+
async request(path, init) {
|
|
148
|
+
const url = `${this.baseUrl}${path}`;
|
|
149
|
+
const headers = {
|
|
150
|
+
...this.authHeaders(),
|
|
151
|
+
...init?.headers
|
|
152
|
+
};
|
|
153
|
+
const res = await fetch(url, { ...init, headers });
|
|
154
|
+
if (!res.ok) throw new Error(`NVR API ${res.status}: ${path}`);
|
|
155
|
+
return res;
|
|
156
|
+
}
|
|
157
|
+
// ─── Config ────────────────────────────────────────────────────
|
|
158
|
+
async getConfig() {
|
|
159
|
+
const res = await this.request("/api/config");
|
|
160
|
+
const raw = await res.json();
|
|
161
|
+
return { type: this.type, version: raw.version, raw };
|
|
162
|
+
}
|
|
163
|
+
async getStreams() {
|
|
164
|
+
if (this.type !== "frigate") return [];
|
|
165
|
+
for (const path of ["/api/go2rtc/streams", "/go2rtc/streams"]) {
|
|
166
|
+
try {
|
|
167
|
+
const res = await this.request(path);
|
|
168
|
+
const data = await res.json();
|
|
169
|
+
return Object.keys(data).map((name) => ({
|
|
170
|
+
name,
|
|
171
|
+
camera: name.split("_")[0]
|
|
172
|
+
}));
|
|
173
|
+
} catch {
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return [];
|
|
177
|
+
}
|
|
178
|
+
// ─── Cameras ───────────────────────────────────────────────────
|
|
179
|
+
async listCameras() {
|
|
180
|
+
const config = await this.getConfig();
|
|
181
|
+
const raw = config.raw;
|
|
182
|
+
const cameras = raw?.cameras ?? {};
|
|
183
|
+
const streams = await this.getStreams();
|
|
184
|
+
const streamNames = streams.map((s) => s.name);
|
|
185
|
+
return Object.entries(cameras).map(([name, cam]) => {
|
|
186
|
+
const matching = streamNames.filter((s) => {
|
|
187
|
+
const sl = s.toLowerCase();
|
|
188
|
+
const cn = name.toLowerCase();
|
|
189
|
+
return sl === cn || sl.startsWith(`${cn}_`) || sl.startsWith(`${cn}.`);
|
|
190
|
+
});
|
|
191
|
+
return {
|
|
192
|
+
name,
|
|
193
|
+
label: name,
|
|
194
|
+
enabled: cam.enabled !== false,
|
|
195
|
+
hasAudio: cam.audio?.enabled === true,
|
|
196
|
+
hasPtz: !!cam.onvif?.host,
|
|
197
|
+
streams: matching,
|
|
198
|
+
streamOptions: []
|
|
199
|
+
};
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
async getSnapshot(camera) {
|
|
203
|
+
const res = await this.request(
|
|
204
|
+
`/api/${encodeURIComponent(camera)}/latest.jpg`
|
|
205
|
+
);
|
|
206
|
+
const blob = await res.blob();
|
|
207
|
+
const buffer = await blob.arrayBuffer();
|
|
208
|
+
const bytes = new Uint8Array(buffer);
|
|
209
|
+
let binary = "";
|
|
210
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
211
|
+
return btoa(binary);
|
|
212
|
+
}
|
|
213
|
+
// ─── Events ────────────────────────────────────────────────────
|
|
214
|
+
async getEvents(params) {
|
|
215
|
+
const qs = new URLSearchParams();
|
|
216
|
+
if (params.after != null) qs.set("after", String(params.after));
|
|
217
|
+
if (params.before != null) qs.set("before", String(params.before));
|
|
218
|
+
if (params.cameras?.length) qs.set("cameras", params.cameras.join(","));
|
|
219
|
+
if (params.labels?.length) qs.set("labels", params.labels.join(","));
|
|
220
|
+
qs.set("limit", String(params.limit ?? 1e4));
|
|
221
|
+
const res = await this.request(`/api/events?${qs}`);
|
|
222
|
+
const raw = await res.json();
|
|
223
|
+
return raw.map((e) => ({
|
|
224
|
+
id: e.id,
|
|
225
|
+
camera: e.camera,
|
|
226
|
+
label: e.label,
|
|
227
|
+
startTime: e.start_time * 1e3,
|
|
228
|
+
endTime: e.end_time != null ? e.end_time * 1e3 : null,
|
|
229
|
+
severity: e.severity,
|
|
230
|
+
hasClip: e.has_clip === true,
|
|
231
|
+
hasSnapshot: e.has_snapshot === true,
|
|
232
|
+
thumbnailPath: `/api/events/${e.id}/thumbnail.jpg`,
|
|
233
|
+
snapshotPath: `/api/events/${e.id}/snapshot.jpg`,
|
|
234
|
+
clipPath: e.has_clip ? `/api/events/${e.id}/clip.mp4` : void 0
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
async getRecordings(params) {
|
|
238
|
+
const qs = new URLSearchParams();
|
|
239
|
+
qs.set("after", String(params.after));
|
|
240
|
+
qs.set("before", String(params.before));
|
|
241
|
+
const res = await this.request(
|
|
242
|
+
`/api/${encodeURIComponent(params.camera)}/recordings?${qs}`
|
|
243
|
+
);
|
|
244
|
+
const data = await res.json();
|
|
245
|
+
const raw = Array.isArray(data) ? data : [];
|
|
246
|
+
return raw.map(
|
|
247
|
+
(r) => ({
|
|
248
|
+
id: r.id ?? `${params.camera}-${r.start_time}-${r.end_time}`,
|
|
249
|
+
camera: params.camera,
|
|
250
|
+
startTime: r.start_time * 1e3,
|
|
251
|
+
endTime: r.end_time * 1e3,
|
|
252
|
+
duration: r.duration * 1e3,
|
|
253
|
+
motion: r.motion,
|
|
254
|
+
objects: r.objects,
|
|
255
|
+
dBFS: r.dBFS
|
|
256
|
+
})
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
async getMotionActivity(params) {
|
|
260
|
+
const qs = new URLSearchParams();
|
|
261
|
+
qs.set("after", String(params.after));
|
|
262
|
+
qs.set("before", String(params.before));
|
|
263
|
+
if (params.scale != null) qs.set("scale", String(params.scale));
|
|
264
|
+
if (params.cameras?.length) qs.set("cameras", params.cameras.join(","));
|
|
265
|
+
const res = await this.request(`/api/review/activity/motion?${qs}`);
|
|
266
|
+
const data = await res.json();
|
|
267
|
+
const raw = Array.isArray(data) ? data : [];
|
|
268
|
+
return raw.map(
|
|
269
|
+
(m) => ({
|
|
270
|
+
camera: m.camera,
|
|
271
|
+
startTime: m.start_time * 1e3,
|
|
272
|
+
motion: m.motion,
|
|
273
|
+
audio: m.audio
|
|
274
|
+
})
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
/** Audio segments are not available in direct mode (tracked by proxy only). */
|
|
278
|
+
async getAudioSegments(_camera, _after, _before) {
|
|
279
|
+
return [];
|
|
280
|
+
}
|
|
281
|
+
async getEventThumbnail(eventId) {
|
|
282
|
+
const res = await this.request(
|
|
283
|
+
`/api/events/${encodeURIComponent(eventId)}/thumbnail.jpg`
|
|
284
|
+
);
|
|
285
|
+
const blob = await res.blob();
|
|
286
|
+
const buffer = await blob.arrayBuffer();
|
|
287
|
+
const bytes = new Uint8Array(buffer);
|
|
288
|
+
let binary = "";
|
|
289
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
290
|
+
return btoa(binary);
|
|
291
|
+
}
|
|
292
|
+
async getEventSnapshot(eventId) {
|
|
293
|
+
const res = await this.request(
|
|
294
|
+
`/api/events/${encodeURIComponent(eventId)}/snapshot.jpg`
|
|
295
|
+
);
|
|
296
|
+
const blob = await res.blob();
|
|
297
|
+
const buffer = await blob.arrayBuffer();
|
|
298
|
+
const bytes = new Uint8Array(buffer);
|
|
299
|
+
let binary = "";
|
|
300
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
301
|
+
return btoa(binary);
|
|
302
|
+
}
|
|
303
|
+
// ─── WebRTC (direct WHEP to go2rtc) ──────────────────────────
|
|
304
|
+
async webrtcOffer(streamName, sdpOffer) {
|
|
305
|
+
for (const basePath of ["/api/go2rtc/webrtc", "/go2rtc/api/webrtc"]) {
|
|
306
|
+
try {
|
|
307
|
+
const qs = new URLSearchParams({ src: streamName });
|
|
308
|
+
const res = await this.request(`${basePath}?${qs}`, {
|
|
309
|
+
method: "POST",
|
|
310
|
+
headers: { "Content-Type": "application/sdp" },
|
|
311
|
+
body: sdpOffer
|
|
312
|
+
});
|
|
313
|
+
return await res.text();
|
|
314
|
+
} catch {
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
throw new Error(`WebRTC SDP failed for stream: ${streamName}`);
|
|
318
|
+
}
|
|
319
|
+
// ─── HLS (direct to Frigate VOD) ─────────────────────────────
|
|
320
|
+
getRecordingHlsUrl(camera, startTime, endTime) {
|
|
321
|
+
const start = Math.floor(startTime);
|
|
322
|
+
const end = Math.ceil(endTime);
|
|
323
|
+
return `${this.baseUrl}/vod/${encodeURIComponent(camera)}/start/${start}/end/${end}/master.m3u8`;
|
|
324
|
+
}
|
|
325
|
+
getEventHlsUrl(eventId) {
|
|
326
|
+
return `${this.baseUrl}/vod/event/${encodeURIComponent(eventId)}/master.m3u8`;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
|
|
330
|
+
// src/types.ts
|
|
331
|
+
function isProxyConfig(config) {
|
|
332
|
+
return "proxyUrl" in config;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/client.ts
|
|
336
|
+
var CamStackClient = class {
|
|
337
|
+
proxy = null;
|
|
338
|
+
direct = null;
|
|
339
|
+
isProxy;
|
|
340
|
+
constructor(config) {
|
|
341
|
+
if (isProxyConfig(config)) {
|
|
342
|
+
this.proxy = new ProxyClient(config);
|
|
343
|
+
this.isProxy = true;
|
|
344
|
+
} else {
|
|
345
|
+
this.direct = new DirectClient(config);
|
|
346
|
+
this.isProxy = false;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
// ─── Providers ───────────────────────────────────────────────────
|
|
350
|
+
async listProviders() {
|
|
351
|
+
if (!this.proxy) return [];
|
|
352
|
+
return this.proxy.listProviders();
|
|
353
|
+
}
|
|
354
|
+
// ─── Config ────────────────────────────────────────────────────
|
|
355
|
+
async getConfig(providerId) {
|
|
356
|
+
if (this.proxy) return this.proxy.getConfig(providerId);
|
|
357
|
+
return this.direct.getConfig();
|
|
358
|
+
}
|
|
359
|
+
async getStreams(providerId) {
|
|
360
|
+
if (this.proxy) return this.proxy.getStreams(providerId);
|
|
361
|
+
return this.direct.getStreams();
|
|
362
|
+
}
|
|
363
|
+
// ─── Cameras ───────────────────────────────────────────────────
|
|
364
|
+
async listCameras(providerId) {
|
|
365
|
+
if (this.proxy) return this.proxy.listCameras(providerId);
|
|
366
|
+
return this.direct.listCameras();
|
|
367
|
+
}
|
|
368
|
+
async getSnapshot(camera, providerId) {
|
|
369
|
+
if (this.proxy) return this.proxy.getSnapshot(camera, providerId);
|
|
370
|
+
return this.direct.getSnapshot(camera);
|
|
371
|
+
}
|
|
372
|
+
// ─── Events ────────────────────────────────────────────────────
|
|
373
|
+
async getEvents(params) {
|
|
374
|
+
if (this.proxy) return this.proxy.getEvents(params);
|
|
375
|
+
return this.direct.getEvents(params);
|
|
376
|
+
}
|
|
377
|
+
async getRecordings(params) {
|
|
378
|
+
if (this.proxy) return this.proxy.getRecordings(params);
|
|
379
|
+
return this.direct.getRecordings(params);
|
|
380
|
+
}
|
|
381
|
+
async getMotionActivity(params) {
|
|
382
|
+
if (this.proxy) return this.proxy.getMotionActivity(params);
|
|
383
|
+
return this.direct.getMotionActivity(params);
|
|
384
|
+
}
|
|
385
|
+
async getAudioSegments(camera, after, before, providerId) {
|
|
386
|
+
if (this.proxy) return this.proxy.getAudioSegments(camera, after, before, providerId);
|
|
387
|
+
return this.direct.getAudioSegments(camera, after, before);
|
|
388
|
+
}
|
|
389
|
+
async getEventThumbnail(eventId, providerId) {
|
|
390
|
+
if (this.proxy) return this.proxy.getEventThumbnail(eventId, providerId);
|
|
391
|
+
return this.direct.getEventThumbnail(eventId);
|
|
392
|
+
}
|
|
393
|
+
async getEventSnapshot(eventId, providerId) {
|
|
394
|
+
if (this.proxy) return this.proxy.getEventSnapshot(eventId, providerId);
|
|
395
|
+
return this.direct.getEventSnapshot(eventId);
|
|
396
|
+
}
|
|
397
|
+
// ─── WebRTC ────────────────────────────────────────────────────
|
|
398
|
+
async webrtcOffer(streamName, sdpOffer, providerId) {
|
|
399
|
+
if (this.proxy) return this.proxy.webrtcOffer(streamName, sdpOffer, providerId);
|
|
400
|
+
return this.direct.webrtcOffer(streamName, sdpOffer);
|
|
401
|
+
}
|
|
402
|
+
// ─── HLS ───────────────────────────────────────────────────────
|
|
403
|
+
async getRecordingHlsUrl(camera, startTime, endTime, providerId) {
|
|
404
|
+
if (this.proxy) {
|
|
405
|
+
return this.proxy.getRecordingHls(camera, startTime, endTime, providerId);
|
|
406
|
+
}
|
|
407
|
+
return this.direct.getRecordingHlsUrl(camera, startTime, endTime);
|
|
408
|
+
}
|
|
409
|
+
async getEventHlsUrl(eventId, providerId) {
|
|
410
|
+
if (this.proxy) {
|
|
411
|
+
return this.proxy.getEventHls(eventId, providerId);
|
|
412
|
+
}
|
|
413
|
+
return this.direct.getEventHlsUrl(eventId);
|
|
414
|
+
}
|
|
415
|
+
// ─── Live subscriptions (proxy mode only) ─────────────────────
|
|
416
|
+
subscribeLiveEvents(callback, options) {
|
|
417
|
+
if (!this.proxy) {
|
|
418
|
+
throw new Error("Live subscriptions require proxy mode.");
|
|
419
|
+
}
|
|
420
|
+
return this.proxy.subscribeLiveEvents(callback, options);
|
|
421
|
+
}
|
|
422
|
+
subscribeMotion(camera, callback, providerId) {
|
|
423
|
+
if (!this.proxy) throw new Error("Live subscriptions require proxy mode.");
|
|
424
|
+
return this.proxy.subscribeMotion(camera, callback, providerId);
|
|
425
|
+
}
|
|
426
|
+
subscribeDetection(camera, callback, providerId) {
|
|
427
|
+
if (!this.proxy) throw new Error("Live subscriptions require proxy mode.");
|
|
428
|
+
return this.proxy.subscribeDetection(camera, callback, providerId);
|
|
429
|
+
}
|
|
430
|
+
subscribeAudioLevel(camera, callback, providerId) {
|
|
431
|
+
if (!this.proxy) throw new Error("Live subscriptions require proxy mode.");
|
|
432
|
+
return this.proxy.subscribeAudioLevel(camera, callback, providerId);
|
|
433
|
+
}
|
|
434
|
+
};
|
|
435
|
+
function createCamStackClient(config) {
|
|
436
|
+
return new CamStackClient(config);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/detection.ts
|
|
440
|
+
var DetectionClass = /* @__PURE__ */ ((DetectionClass2) => {
|
|
441
|
+
DetectionClass2["Motion"] = "motion";
|
|
442
|
+
DetectionClass2["Person"] = "person";
|
|
443
|
+
DetectionClass2["Vehicle"] = "vehicle";
|
|
444
|
+
DetectionClass2["Animal"] = "animal";
|
|
445
|
+
DetectionClass2["Audio"] = "audio";
|
|
446
|
+
DetectionClass2["Face"] = "face";
|
|
447
|
+
DetectionClass2["Plate"] = "plate";
|
|
448
|
+
DetectionClass2["Package"] = "package";
|
|
449
|
+
DetectionClass2["Doorbell"] = "doorbell";
|
|
450
|
+
DetectionClass2["Sensor"] = "sensor";
|
|
451
|
+
return DetectionClass2;
|
|
452
|
+
})(DetectionClass || {});
|
|
453
|
+
var animalClasses = [
|
|
454
|
+
"animal" /* Animal */,
|
|
455
|
+
"dog_cat",
|
|
456
|
+
"dog",
|
|
457
|
+
"cat",
|
|
458
|
+
"horse",
|
|
459
|
+
"sheep",
|
|
460
|
+
"cow",
|
|
461
|
+
"elephant",
|
|
462
|
+
"bear",
|
|
463
|
+
"zebra",
|
|
464
|
+
"giraffe",
|
|
465
|
+
"mouse",
|
|
466
|
+
"rabbit",
|
|
467
|
+
"deer",
|
|
468
|
+
"lion",
|
|
469
|
+
"tiger",
|
|
470
|
+
"bird",
|
|
471
|
+
"eagle",
|
|
472
|
+
"owl",
|
|
473
|
+
"pigeon",
|
|
474
|
+
"fish",
|
|
475
|
+
"whale",
|
|
476
|
+
"dolphin",
|
|
477
|
+
"snake",
|
|
478
|
+
"turtle",
|
|
479
|
+
"lizard"
|
|
480
|
+
];
|
|
481
|
+
var personClasses = [
|
|
482
|
+
"person" /* Person */,
|
|
483
|
+
"people",
|
|
484
|
+
"pedestrian",
|
|
485
|
+
"rider",
|
|
486
|
+
"driver",
|
|
487
|
+
"cyclist",
|
|
488
|
+
"skier",
|
|
489
|
+
"skateboarder",
|
|
490
|
+
"face",
|
|
491
|
+
"hand",
|
|
492
|
+
"head",
|
|
493
|
+
"body"
|
|
494
|
+
];
|
|
495
|
+
var vehicleClasses = [
|
|
496
|
+
"vehicle" /* Vehicle */,
|
|
497
|
+
"car",
|
|
498
|
+
"truck",
|
|
499
|
+
"bus",
|
|
500
|
+
"motorcycle",
|
|
501
|
+
"bicycle",
|
|
502
|
+
"van",
|
|
503
|
+
"ambulance",
|
|
504
|
+
"police_car",
|
|
505
|
+
"fire_truck",
|
|
506
|
+
"train",
|
|
507
|
+
"subway",
|
|
508
|
+
"tram",
|
|
509
|
+
"airplane",
|
|
510
|
+
"boat",
|
|
511
|
+
"ship",
|
|
512
|
+
"helicopter"
|
|
513
|
+
];
|
|
514
|
+
var faceClasses = [
|
|
515
|
+
"face" /* Face */,
|
|
516
|
+
"eyes",
|
|
517
|
+
"nose",
|
|
518
|
+
"mouth",
|
|
519
|
+
"ears",
|
|
520
|
+
"eyebrows",
|
|
521
|
+
"left_eye",
|
|
522
|
+
"right_eye",
|
|
523
|
+
"pupil",
|
|
524
|
+
"iris",
|
|
525
|
+
"eyelid",
|
|
526
|
+
"eye_corner",
|
|
527
|
+
"upper_lip",
|
|
528
|
+
"lower_lip",
|
|
529
|
+
"teeth",
|
|
530
|
+
"chin",
|
|
531
|
+
"cheek",
|
|
532
|
+
"forehead",
|
|
533
|
+
"jaw",
|
|
534
|
+
"glasses",
|
|
535
|
+
"sunglasses",
|
|
536
|
+
"facial_hair",
|
|
537
|
+
"beard",
|
|
538
|
+
"mustache",
|
|
539
|
+
"facial_landmark",
|
|
540
|
+
"facial_keypoint"
|
|
541
|
+
];
|
|
542
|
+
var licensePlateClasses = [
|
|
543
|
+
"plate" /* Plate */,
|
|
544
|
+
"license_plate",
|
|
545
|
+
"front_plate",
|
|
546
|
+
"rear_plate",
|
|
547
|
+
"motorcycle_plate",
|
|
548
|
+
"temporary_plate",
|
|
549
|
+
"dealer_plate",
|
|
550
|
+
"licensePlate",
|
|
551
|
+
"plate_number",
|
|
552
|
+
"plate_character",
|
|
553
|
+
"plate_digit",
|
|
554
|
+
"plate_letter",
|
|
555
|
+
"plate_symbol",
|
|
556
|
+
"plate_region",
|
|
557
|
+
"plate_country_identifier",
|
|
558
|
+
"plate_frame",
|
|
559
|
+
"plate_bolt",
|
|
560
|
+
"plate_sticker",
|
|
561
|
+
"plate_validation_tag",
|
|
562
|
+
"damaged_plate",
|
|
563
|
+
"obscured_plate",
|
|
564
|
+
"dirty_plate"
|
|
565
|
+
];
|
|
566
|
+
var motionClasses = ["motion" /* Motion */, "movement", "other"];
|
|
567
|
+
var packageClasses = ["package" /* Package */, "packet"];
|
|
568
|
+
var audioClasses = ["audio" /* Audio */];
|
|
569
|
+
var audioLabelClasses = [
|
|
570
|
+
"speech",
|
|
571
|
+
"scream",
|
|
572
|
+
"babbling",
|
|
573
|
+
"yell",
|
|
574
|
+
"bellow",
|
|
575
|
+
"whoop",
|
|
576
|
+
"whispering",
|
|
577
|
+
"laughter",
|
|
578
|
+
"snicker",
|
|
579
|
+
"crying",
|
|
580
|
+
"cry",
|
|
581
|
+
"sigh",
|
|
582
|
+
"singing",
|
|
583
|
+
"choir",
|
|
584
|
+
"chant",
|
|
585
|
+
"mantra",
|
|
586
|
+
"child_singing",
|
|
587
|
+
"rapping",
|
|
588
|
+
"humming",
|
|
589
|
+
"groan",
|
|
590
|
+
"grunt",
|
|
591
|
+
"whistling",
|
|
592
|
+
"breathing",
|
|
593
|
+
"wheeze",
|
|
594
|
+
"snoring",
|
|
595
|
+
"gasp",
|
|
596
|
+
"pant",
|
|
597
|
+
"snort",
|
|
598
|
+
"cough",
|
|
599
|
+
"throat_clearing",
|
|
600
|
+
"sneeze",
|
|
601
|
+
"sniff",
|
|
602
|
+
"cheering",
|
|
603
|
+
"applause",
|
|
604
|
+
"chatter",
|
|
605
|
+
"crowd",
|
|
606
|
+
"children_playing",
|
|
607
|
+
"bark",
|
|
608
|
+
"yip",
|
|
609
|
+
"howl",
|
|
610
|
+
"bow-wow",
|
|
611
|
+
"growling",
|
|
612
|
+
"whimper_dog",
|
|
613
|
+
"purr",
|
|
614
|
+
"meow",
|
|
615
|
+
"hiss",
|
|
616
|
+
"caterwaul",
|
|
617
|
+
"pets",
|
|
618
|
+
"livestock",
|
|
619
|
+
"doorbell",
|
|
620
|
+
"ding-dong",
|
|
621
|
+
"door",
|
|
622
|
+
"slam",
|
|
623
|
+
"knock",
|
|
624
|
+
"alarm",
|
|
625
|
+
"telephone",
|
|
626
|
+
"music",
|
|
627
|
+
"dog",
|
|
628
|
+
"dogs"
|
|
629
|
+
];
|
|
630
|
+
var doorbellClasses = ["doorbell" /* Doorbell */, "ring"];
|
|
631
|
+
var sensorLabelClasses = [
|
|
632
|
+
"lock",
|
|
633
|
+
"binary",
|
|
634
|
+
"flood",
|
|
635
|
+
"entry",
|
|
636
|
+
"door",
|
|
637
|
+
"leak",
|
|
638
|
+
"door_open",
|
|
639
|
+
"flooded",
|
|
640
|
+
"entry_open"
|
|
641
|
+
];
|
|
642
|
+
var detectionClassesDefaultMap = {
|
|
643
|
+
...animalClasses.reduce((tot, curr) => ({ ...tot, [curr]: "animal" /* Animal */ }), {}),
|
|
644
|
+
...personClasses.reduce((tot, curr) => ({ ...tot, [curr]: "person" /* Person */ }), {}),
|
|
645
|
+
...vehicleClasses.reduce((tot, curr) => ({ ...tot, [curr]: "vehicle" /* Vehicle */ }), {}),
|
|
646
|
+
...motionClasses.reduce((tot, curr) => ({ ...tot, [curr]: "motion" /* Motion */ }), {}),
|
|
647
|
+
...packageClasses.reduce((tot, curr) => ({ ...tot, [curr]: "package" /* Package */ }), {}),
|
|
648
|
+
...faceClasses.reduce((tot, curr) => ({ ...tot, [curr]: "face" /* Face */ }), {}),
|
|
649
|
+
...licensePlateClasses.reduce((tot, curr) => ({ ...tot, [curr]: "plate" /* Plate */ }), {}),
|
|
650
|
+
...audioClasses.reduce((tot, curr) => ({ ...tot, [curr]: "audio" /* Audio */ }), {}),
|
|
651
|
+
...audioLabelClasses.reduce((tot, curr) => ({ ...tot, [curr]: "audio" /* Audio */ }), {}),
|
|
652
|
+
...doorbellClasses.reduce((tot, curr) => ({ ...tot, [curr]: "doorbell" /* Doorbell */ }), {}),
|
|
653
|
+
...sensorLabelClasses.reduce((tot, curr) => ({ ...tot, [curr]: "sensor" /* Sensor */ }), {})
|
|
654
|
+
};
|
|
655
|
+
var isFaceClassname = (c) => faceClasses.includes(c);
|
|
656
|
+
var isPlateClassname = (c) => licensePlateClasses.includes(c);
|
|
657
|
+
var isAnimalClassname = (c) => animalClasses.includes(c);
|
|
658
|
+
var isPersonClassname = (c) => personClasses.includes(c);
|
|
659
|
+
var isVehicleClassname = (c) => vehicleClasses.includes(c);
|
|
660
|
+
var isMotionClassname = (c) => motionClasses.includes(c);
|
|
661
|
+
var isDoorbellClassname = (c) => doorbellClasses.includes(c);
|
|
662
|
+
var isPackageClassname = (c) => packageClasses.includes(c);
|
|
663
|
+
var isAudioClassname = (c) => audioClasses.includes(c) || audioLabelClasses.includes(c);
|
|
664
|
+
var isSensorLabelClassname = (c) => sensorLabelClasses.includes(c);
|
|
665
|
+
var isLabelDetection = (c) => isFaceClassname(c) || isPlateClassname(c);
|
|
666
|
+
var getParentClass = (className) => detectionClassesDefaultMap[className];
|
|
667
|
+
var getParentDetectionClass = (det) => {
|
|
668
|
+
const { className } = det;
|
|
669
|
+
const baseMap = {
|
|
670
|
+
["face" /* Face */]: "person" /* Person */,
|
|
671
|
+
["plate" /* Plate */]: "vehicle" /* Vehicle */
|
|
672
|
+
};
|
|
673
|
+
const parentGroup = detectionClassesDefaultMap[className];
|
|
674
|
+
if (parentGroup && parentGroup !== className) return parentGroup;
|
|
675
|
+
return baseMap[className];
|
|
676
|
+
};
|
|
677
|
+
var defaultDetectionClasses = Object.values(DetectionClass);
|
|
678
|
+
var DEFAULT_ENABLED_CLASSES = defaultDetectionClasses.filter(
|
|
679
|
+
(c) => c !== "motion" /* Motion */
|
|
680
|
+
);
|
|
681
|
+
var TIMELINE_PRESET_CRITICAL = [
|
|
682
|
+
"person" /* Person */,
|
|
683
|
+
"doorbell" /* Doorbell */,
|
|
684
|
+
"package" /* Package */
|
|
685
|
+
];
|
|
686
|
+
var TIMELINE_PRESET_IMPORTANT = [
|
|
687
|
+
...TIMELINE_PRESET_CRITICAL,
|
|
688
|
+
"vehicle" /* Vehicle */,
|
|
689
|
+
"animal" /* Animal */,
|
|
690
|
+
"audio" /* Audio */,
|
|
691
|
+
"face" /* Face */,
|
|
692
|
+
"plate" /* Plate */
|
|
693
|
+
];
|
|
694
|
+
var TIMELINE_PRESET_ALL = [...DEFAULT_ENABLED_CLASSES];
|
|
695
|
+
function getClassesForTimelinePreset(preset, customClasses) {
|
|
696
|
+
switch (preset) {
|
|
697
|
+
case "critical":
|
|
698
|
+
return TIMELINE_PRESET_CRITICAL;
|
|
699
|
+
case "important":
|
|
700
|
+
return TIMELINE_PRESET_IMPORTANT;
|
|
701
|
+
case "all":
|
|
702
|
+
return TIMELINE_PRESET_ALL;
|
|
703
|
+
case "custom":
|
|
704
|
+
return customClasses?.length ? customClasses : DEFAULT_ENABLED_CLASSES;
|
|
705
|
+
default:
|
|
706
|
+
return DEFAULT_ENABLED_CLASSES;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// src/devices.ts
|
|
711
|
+
var RAW_TO_CANONICAL = {
|
|
712
|
+
// Scrypted PascalCase
|
|
713
|
+
Light: "light",
|
|
714
|
+
Switch: "switch",
|
|
715
|
+
WindowCovering: "cover",
|
|
716
|
+
Lock: "lock",
|
|
717
|
+
SecuritySystem: "alarm",
|
|
718
|
+
Buttons: "button",
|
|
719
|
+
Select: "select",
|
|
720
|
+
Siren: "siren",
|
|
721
|
+
Sensor: "sensor",
|
|
722
|
+
Entry: "entry",
|
|
723
|
+
Program: "script",
|
|
724
|
+
MediaPlayer: "media_player",
|
|
725
|
+
Outlet: "switch",
|
|
726
|
+
// Home Assistant lowercase domains
|
|
727
|
+
light: "light",
|
|
728
|
+
switch: "switch",
|
|
729
|
+
input_boolean: "switch",
|
|
730
|
+
cover: "cover",
|
|
731
|
+
lock: "lock",
|
|
732
|
+
alarm_control_panel: "alarm",
|
|
733
|
+
input_button: "button",
|
|
734
|
+
button: "button",
|
|
735
|
+
input_select: "select",
|
|
736
|
+
select: "select",
|
|
737
|
+
siren: "siren",
|
|
738
|
+
sensor: "sensor",
|
|
739
|
+
media_player: "media_player",
|
|
740
|
+
script: "script"
|
|
741
|
+
};
|
|
742
|
+
var HA_DOMAIN_TYPE_MAP = {
|
|
743
|
+
light: "light",
|
|
744
|
+
switch: "switch",
|
|
745
|
+
input_boolean: "switch",
|
|
746
|
+
cover: "cover",
|
|
747
|
+
lock: "lock",
|
|
748
|
+
alarm_control_panel: "alarm",
|
|
749
|
+
input_button: "button",
|
|
750
|
+
button: "button",
|
|
751
|
+
input_select: "select",
|
|
752
|
+
select: "select",
|
|
753
|
+
siren: "siren",
|
|
754
|
+
sensor: "sensor",
|
|
755
|
+
binary_sensor: "sensor",
|
|
756
|
+
media_player: "media_player",
|
|
757
|
+
script: "script",
|
|
758
|
+
climate: "climate",
|
|
759
|
+
camera: "camera",
|
|
760
|
+
fan: "fan",
|
|
761
|
+
vacuum: "vacuum",
|
|
762
|
+
automation: "automation",
|
|
763
|
+
scene: "scene",
|
|
764
|
+
input_number: "sensor",
|
|
765
|
+
person: "person",
|
|
766
|
+
device_tracker: "tracker",
|
|
767
|
+
weather: "weather",
|
|
768
|
+
water_heater: "climate"
|
|
769
|
+
};
|
|
770
|
+
var SCRYPTED_TYPE_TO_CANONICAL = {
|
|
771
|
+
Light: "light",
|
|
772
|
+
Switch: "switch",
|
|
773
|
+
WindowCovering: "cover",
|
|
774
|
+
Lock: "lock",
|
|
775
|
+
SecuritySystem: "alarm",
|
|
776
|
+
Buttons: "button",
|
|
777
|
+
Select: "select",
|
|
778
|
+
Siren: "siren",
|
|
779
|
+
Sensor: "sensor",
|
|
780
|
+
Entry: "entry",
|
|
781
|
+
Program: "script",
|
|
782
|
+
MediaPlayer: "media_player",
|
|
783
|
+
Camera: "camera",
|
|
784
|
+
Doorbell: "doorbell",
|
|
785
|
+
Fan: "fan",
|
|
786
|
+
Outlet: "switch"
|
|
787
|
+
};
|
|
788
|
+
function getCanonicalDeviceType(rawType) {
|
|
789
|
+
const canonical = RAW_TO_CANONICAL[rawType];
|
|
790
|
+
if (canonical) return canonical;
|
|
791
|
+
const lower = rawType.toLowerCase();
|
|
792
|
+
return RAW_TO_CANONICAL[lower] ?? null;
|
|
793
|
+
}
|
|
794
|
+
var ELIGIBLE_SCRYPTED_DEVICE_TYPES = [
|
|
795
|
+
"Entry",
|
|
796
|
+
"Light",
|
|
797
|
+
"Switch",
|
|
798
|
+
"Lock",
|
|
799
|
+
"SecuritySystem",
|
|
800
|
+
"Buttons",
|
|
801
|
+
"WindowCovering",
|
|
802
|
+
"Siren",
|
|
803
|
+
"Sensor",
|
|
804
|
+
"Select",
|
|
805
|
+
"Program"
|
|
806
|
+
];
|
|
807
|
+
var ELIGIBLE_SCRYPTED_DEVICE_TYPES_SET = new Set(ELIGIBLE_SCRYPTED_DEVICE_TYPES);
|
|
808
|
+
var ELIGIBLE_HA_DOMAINS = [
|
|
809
|
+
"light",
|
|
810
|
+
"switch",
|
|
811
|
+
"input_boolean",
|
|
812
|
+
"cover",
|
|
813
|
+
"lock",
|
|
814
|
+
"alarm_control_panel",
|
|
815
|
+
"input_button",
|
|
816
|
+
"button",
|
|
817
|
+
"input_select",
|
|
818
|
+
"select",
|
|
819
|
+
"siren",
|
|
820
|
+
"media_player",
|
|
821
|
+
"script"
|
|
822
|
+
];
|
|
823
|
+
var ELIGIBLE_HA_DOMAINS_SET = new Set(ELIGIBLE_HA_DOMAINS);
|
|
824
|
+
|
|
825
|
+
// src/features.ts
|
|
826
|
+
var FEATURE_MATRIX = [
|
|
827
|
+
{
|
|
828
|
+
id: "liveStream",
|
|
829
|
+
label: "Live Stream",
|
|
830
|
+
sources: { frigate: true, scrypted: true, rtsp: true },
|
|
831
|
+
adapterMethod: "getLiveStream"
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
id: "multiResolution",
|
|
835
|
+
label: "Multi-Resolution",
|
|
836
|
+
sources: { frigate: true, scrypted: true, rtsp: false },
|
|
837
|
+
adapterMethod: "getResolutions"
|
|
838
|
+
},
|
|
839
|
+
{
|
|
840
|
+
id: "motion",
|
|
841
|
+
label: "Motion Detection",
|
|
842
|
+
sources: { frigate: false, scrypted: true, rtsp: false },
|
|
843
|
+
adapterMethod: "getMotion"
|
|
844
|
+
},
|
|
845
|
+
{
|
|
846
|
+
id: "objectDetection",
|
|
847
|
+
label: "Object Detection",
|
|
848
|
+
sources: { frigate: false, scrypted: true, rtsp: false },
|
|
849
|
+
adapterMethod: "getObjectDetections"
|
|
850
|
+
},
|
|
851
|
+
{
|
|
852
|
+
id: "audioVolume",
|
|
853
|
+
label: "Audio Level",
|
|
854
|
+
sources: { frigate: false, scrypted: true, rtsp: false },
|
|
855
|
+
adapterMethod: "getAudioVolume"
|
|
856
|
+
},
|
|
857
|
+
{
|
|
858
|
+
id: "audioVolumes",
|
|
859
|
+
label: "Audio Volumes (dBFS)",
|
|
860
|
+
sources: { frigate: false, scrypted: true, rtsp: false },
|
|
861
|
+
adapterMethod: "getAudioVolumes"
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
id: "ptz",
|
|
865
|
+
label: "PTZ Control",
|
|
866
|
+
sources: { frigate: false, scrypted: true, rtsp: false },
|
|
867
|
+
adapterMethod: "getPTZ"
|
|
868
|
+
},
|
|
869
|
+
{
|
|
870
|
+
id: "intercom",
|
|
871
|
+
label: "Intercom (Mic)",
|
|
872
|
+
sources: { frigate: false, scrypted: true, rtsp: false },
|
|
873
|
+
adapterMethod: "getIntercomSupport"
|
|
874
|
+
},
|
|
875
|
+
{
|
|
876
|
+
id: "deviceStatus",
|
|
877
|
+
label: "Device Status",
|
|
878
|
+
sources: { frigate: false, scrypted: true, rtsp: false },
|
|
879
|
+
adapterMethod: "getStatus"
|
|
880
|
+
},
|
|
881
|
+
{
|
|
882
|
+
id: "timeline",
|
|
883
|
+
label: "Detection Timeline",
|
|
884
|
+
sources: { frigate: true, scrypted: true, rtsp: false },
|
|
885
|
+
adapterMethod: "getCameraDayData"
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
id: "clusteredTimeline",
|
|
889
|
+
label: "Clustered Timeline",
|
|
890
|
+
sources: { frigate: true, scrypted: true, rtsp: false },
|
|
891
|
+
requiresBackend: true,
|
|
892
|
+
adapterMethod: "getClusteredDayData"
|
|
893
|
+
},
|
|
894
|
+
{
|
|
895
|
+
id: "detectionClasses",
|
|
896
|
+
label: "Detection Classes",
|
|
897
|
+
sources: { frigate: true, scrypted: true, rtsp: false },
|
|
898
|
+
adapterMethod: "getDetectionClasses"
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
id: "videoClips",
|
|
902
|
+
label: "Video Clips",
|
|
903
|
+
sources: { frigate: true, scrypted: true, rtsp: false },
|
|
904
|
+
adapterMethod: "getVideoClips"
|
|
905
|
+
},
|
|
906
|
+
{
|
|
907
|
+
id: "nvrPlayback",
|
|
908
|
+
label: "NVR Playback",
|
|
909
|
+
sources: { frigate: true, scrypted: true, rtsp: false },
|
|
910
|
+
adapterMethod: "getNvrPlaybackSupported"
|
|
911
|
+
},
|
|
912
|
+
{
|
|
913
|
+
id: "nvrScrub",
|
|
914
|
+
label: "NVR Scrub/Seek",
|
|
915
|
+
sources: { frigate: false, scrypted: true, rtsp: false },
|
|
916
|
+
adapterMethod: "seekRecordingStream"
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
id: "recordingThumbnail",
|
|
920
|
+
label: "Recording Thumbnails",
|
|
921
|
+
sources: { frigate: true, scrypted: true, rtsp: false },
|
|
922
|
+
adapterMethod: "getRecordingStreamThumbnail"
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
id: "nvrSeekToLive",
|
|
926
|
+
label: "Seek to Live",
|
|
927
|
+
sources: { frigate: false, scrypted: true, rtsp: false },
|
|
928
|
+
adapterMethod: "seekNvrToLive"
|
|
929
|
+
}
|
|
930
|
+
];
|
|
931
|
+
function isFeatureAvailable(featureId, source, platform) {
|
|
932
|
+
const entry = FEATURE_MATRIX.find((f) => f.id === featureId);
|
|
933
|
+
if (!entry) return false;
|
|
934
|
+
if (!entry.sources[source]) return false;
|
|
935
|
+
if (entry.platforms && entry.platforms[platform] === false) return false;
|
|
936
|
+
return true;
|
|
937
|
+
}
|
|
938
|
+
function getSourceFeatures(source) {
|
|
939
|
+
return FEATURE_MATRIX.filter((f) => f.sources[source]);
|
|
940
|
+
}
|
|
941
|
+
function getBackendRequiredFeatures() {
|
|
942
|
+
return FEATURE_MATRIX.filter((f) => f.requiresBackend);
|
|
943
|
+
}
|
|
944
|
+
export {
|
|
945
|
+
CamStackClient,
|
|
946
|
+
DEFAULT_ENABLED_CLASSES,
|
|
947
|
+
DetectionClass,
|
|
948
|
+
DirectClient,
|
|
949
|
+
ELIGIBLE_HA_DOMAINS,
|
|
950
|
+
ELIGIBLE_HA_DOMAINS_SET,
|
|
951
|
+
ELIGIBLE_SCRYPTED_DEVICE_TYPES,
|
|
952
|
+
ELIGIBLE_SCRYPTED_DEVICE_TYPES_SET,
|
|
953
|
+
FEATURE_MATRIX,
|
|
954
|
+
HA_DOMAIN_TYPE_MAP,
|
|
955
|
+
ProxyClient,
|
|
956
|
+
RAW_TO_CANONICAL,
|
|
957
|
+
SCRYPTED_TYPE_TO_CANONICAL,
|
|
958
|
+
TIMELINE_PRESET_ALL,
|
|
959
|
+
TIMELINE_PRESET_CRITICAL,
|
|
960
|
+
TIMELINE_PRESET_IMPORTANT,
|
|
961
|
+
animalClasses,
|
|
962
|
+
audioClasses,
|
|
963
|
+
audioLabelClasses,
|
|
964
|
+
createCamStackClient,
|
|
965
|
+
defaultDetectionClasses,
|
|
966
|
+
detectionClassesDefaultMap,
|
|
967
|
+
doorbellClasses,
|
|
968
|
+
faceClasses,
|
|
969
|
+
getBackendRequiredFeatures,
|
|
970
|
+
getCanonicalDeviceType,
|
|
971
|
+
getClassesForTimelinePreset,
|
|
972
|
+
getParentClass,
|
|
973
|
+
getParentDetectionClass,
|
|
974
|
+
getSourceFeatures,
|
|
975
|
+
isAnimalClassname,
|
|
976
|
+
isAudioClassname,
|
|
977
|
+
isDoorbellClassname,
|
|
978
|
+
isFaceClassname,
|
|
979
|
+
isFeatureAvailable,
|
|
980
|
+
isLabelDetection,
|
|
981
|
+
isMotionClassname,
|
|
982
|
+
isPackageClassname,
|
|
983
|
+
isPersonClassname,
|
|
984
|
+
isPlateClassname,
|
|
985
|
+
isProxyConfig,
|
|
986
|
+
isSensorLabelClassname,
|
|
987
|
+
isVehicleClassname,
|
|
988
|
+
licensePlateClasses,
|
|
989
|
+
motionClasses,
|
|
990
|
+
packageClasses,
|
|
991
|
+
personClasses,
|
|
992
|
+
sensorLabelClasses,
|
|
993
|
+
vehicleClasses
|
|
994
|
+
};
|
|
995
|
+
//# sourceMappingURL=index.js.map
|