@camstack/addon-provider-rtsp 0.1.11 → 0.1.12

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/dist/index.js CHANGED
@@ -29,22 +29,25 @@ module.exports = __toCommonJS(index_exports);
29
29
  // src/rtsp-device.ts
30
30
  var import_types = require("@camstack/types");
31
31
  var RtspDevice = class {
32
- constructor(config, providerId, ctx) {
33
- this.config = config;
34
- this.id = `${providerId}/${config.id}`;
35
- this.name = config.name;
36
- this.providerId = providerId;
37
- this.ctx = ctx;
38
- this.capabilities = ["camera"];
39
- this.capabilityMap.set("camera", this.createCamera());
40
- }
41
32
  id;
42
33
  name;
43
34
  providerId;
44
35
  type = import_types.DeviceType.Camera;
45
36
  capabilities;
46
37
  ctx;
38
+ stableId;
39
+ settings;
47
40
  capabilityMap = /* @__PURE__ */ new Map();
41
+ constructor(device, settings, ctx) {
42
+ this.id = device.id;
43
+ this.stableId = device.stableId;
44
+ this.name = device.name;
45
+ this.providerId = device.integrationId;
46
+ this.settings = settings;
47
+ this.ctx = ctx;
48
+ this.capabilities = ["camera"];
49
+ this.capabilityMap.set("camera", this.createCamera());
50
+ }
48
51
  getCapability(cap) {
49
52
  return this.capabilityMap.get(cap) ?? null;
50
53
  }
@@ -57,17 +60,16 @@ var RtspDevice = class {
57
60
  getMetadata() {
58
61
  return { manufacturer: "Generic RTSP" };
59
62
  }
60
- /** Return the camera config for persistence */
61
- getCameraConfig() {
62
- return { ...this.config };
63
- }
64
63
  createCamera() {
65
- const cfg = this.config;
64
+ const mainUrl = String(this.settings["main_stream_url"] ?? "");
65
+ const subUrl = String(this.settings["sub_stream_url"] ?? "");
66
+ const snapshotUrl = String(this.settings["snapshot_url"] ?? "");
67
+ const deviceId = this.id;
66
68
  return {
67
69
  kind: "camera",
68
70
  async getSnapshot() {
69
- if (cfg.snapshotUrl) {
70
- const res = await fetch(cfg.snapshotUrl);
71
+ if (snapshotUrl) {
72
+ const res = await fetch(snapshotUrl);
71
73
  return Buffer.from(await res.arrayBuffer());
72
74
  }
73
75
  return Buffer.alloc(0);
@@ -75,26 +77,26 @@ var RtspDevice = class {
75
77
  async getStreamOptions() {
76
78
  const options = [
77
79
  {
78
- id: `${cfg.id}_main`,
80
+ id: `${deviceId}_main`,
79
81
  label: "Main",
80
82
  protocol: "rtsp",
81
83
  quality: "main",
82
- url: cfg.url
84
+ url: mainUrl
83
85
  }
84
86
  ];
85
- if (cfg.subStreamUrl) {
87
+ if (subUrl) {
86
88
  options.push({
87
- id: `${cfg.id}_sub`,
89
+ id: `${deviceId}_sub`,
88
90
  label: "Sub",
89
91
  protocol: "rtsp",
90
92
  quality: "sub",
91
- url: cfg.subStreamUrl
93
+ url: subUrl
92
94
  });
93
95
  }
94
96
  return options;
95
97
  },
96
98
  async getStreamUrl(option) {
97
- return option.url ?? cfg.url;
99
+ return option.url ?? mainUrl;
98
100
  },
99
101
  getConnectionMode() {
100
102
  return "always-on";
@@ -105,147 +107,37 @@ var RtspDevice = class {
105
107
  }
106
108
  };
107
109
 
108
- // src/element-config-store.ts
109
- var ElementConfigStore = class {
110
- constructor(elementId, storage) {
111
- this.elementId = elementId;
112
- this.storage = storage;
113
- }
114
- cache = {};
115
- listeners = /* @__PURE__ */ new Set();
116
- loaded = false;
117
- /** Load config from storage into cache. Called once on first access. */
118
- async ensureLoaded() {
119
- if (this.loaded) return;
120
- if (!this.storage.structured) {
121
- this.loaded = true;
122
- return;
123
- }
124
- try {
125
- const records = await this.storage.structured.query("config", {
126
- where: { id: this.elementId },
127
- limit: 1
128
- });
129
- if (records.length > 0) {
130
- this.cache = records[0].data ?? {};
131
- }
132
- } catch {
133
- }
134
- this.loaded = true;
135
- }
136
- getAll() {
137
- return { ...this.cache };
138
- }
139
- get(key) {
140
- const parts = key.split(".");
141
- let current = this.cache;
142
- for (const part of parts) {
143
- if (current == null || typeof current !== "object") return void 0;
144
- current = current[part];
145
- }
146
- return current;
147
- }
148
- async set(key, value) {
149
- await this.ensureLoaded();
150
- setNestedValue(this.cache, key, value);
151
- await this.persist();
152
- this.notifyListeners();
153
- }
154
- async setAll(config) {
155
- await this.ensureLoaded();
156
- this.cache = { ...config };
157
- await this.persist();
158
- this.notifyListeners();
159
- }
160
- onChange(callback) {
161
- this.listeners.add(callback);
162
- return () => {
163
- this.listeners.delete(callback);
164
- };
165
- }
166
- /** Initialize from storage — called by ContextFactory after creation */
167
- async load() {
168
- await this.ensureLoaded();
169
- }
170
- /** Initialize with default values (doesn't overwrite existing) */
171
- async loadDefaults(defaults) {
172
- await this.ensureLoaded();
173
- if (Object.keys(this.cache).length === 0) {
174
- this.cache = { ...defaults };
175
- await this.persist();
176
- }
177
- }
178
- async persist() {
179
- if (!this.storage.structured) return;
180
- try {
181
- const existing = await this.storage.structured.query("config", {
182
- where: { id: this.elementId },
183
- limit: 1
184
- });
185
- if (existing.length > 0) {
186
- await this.storage.structured.update("config", this.elementId, this.cache);
187
- } else {
188
- await this.storage.structured.insert({
189
- collection: "config",
190
- id: this.elementId,
191
- data: this.cache
192
- });
193
- }
194
- } catch {
195
- }
196
- }
197
- notifyListeners() {
198
- const snapshot = this.getAll();
199
- for (const listener of this.listeners) {
200
- try {
201
- listener(snapshot);
202
- } catch {
203
- }
204
- }
205
- }
206
- };
207
- function setNestedValue(obj, path, value) {
208
- const parts = path.split(".");
209
- let current = obj;
210
- for (let i = 0; i < parts.length - 1; i++) {
211
- const part = parts[i];
212
- if (!(part in current) || typeof current[part] !== "object" || current[part] === null) {
213
- current[part] = {};
214
- }
215
- current = current[part];
216
- }
217
- current[parts[parts.length - 1]] = value;
218
- }
219
-
220
110
  // src/rtsp-provider.ts
111
+ function sanitizeStableId(name) {
112
+ return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || `rtsp-${Date.now()}`;
113
+ }
221
114
  var RtspProvider = class {
222
115
  id;
223
116
  type = "rtsp";
224
117
  name;
225
118
  discoveryMode = "manual";
226
119
  ctx;
120
+ integration;
121
+ registry;
227
122
  devices = [];
228
- constructor(config, ctx) {
229
- this.id = config.id;
230
- this.name = config.name;
123
+ constructor(integration, registry, ctx) {
124
+ this.id = integration.id;
125
+ this.name = integration.name;
126
+ this.integration = integration;
127
+ this.registry = registry;
231
128
  this.ctx = ctx;
232
- for (const cam of config.cameras) {
233
- this.devices.push(this.buildDevice(cam));
234
- }
235
129
  }
236
130
  async start() {
237
- const persisted = this.ctx.config.get("devices");
238
- if (persisted && Array.isArray(persisted)) {
239
- for (const cam of persisted) {
240
- const alreadyExists = this.devices.some((d) => d.id === `${this.id}/${cam.id}`);
241
- if (!alreadyExists) {
242
- this.devices.push(this.buildDevice(cam));
243
- }
244
- }
131
+ const registeredDevices = this.registry.listDevices(this.integration.id);
132
+ for (const device of registeredDevices) {
133
+ if (!device.enabled) continue;
134
+ const settings = this.registry.getDeviceSettings(device.id);
135
+ this.devices.push(this.buildDevice(device, settings));
245
136
  }
246
137
  this.ctx.logger.info(`RTSP provider started with ${this.devices.length} cameras`);
247
138
  }
248
139
  async stop() {
140
+ this.devices.length = 0;
249
141
  this.ctx.logger.info("RTSP provider stopped");
250
142
  }
251
143
  getStatus() {
@@ -275,11 +167,25 @@ var RtspProvider = class {
275
167
  },
276
168
  {
277
169
  type: "text",
278
- key: "snapshotUrl",
170
+ key: "main_stream_url",
171
+ label: "Main Stream URL",
172
+ placeholder: "rtsp://192.168.1.100:554/stream1",
173
+ inputType: "url",
174
+ required: true
175
+ },
176
+ {
177
+ type: "text",
178
+ key: "sub_stream_url",
179
+ label: "Sub Stream URL",
180
+ placeholder: "rtsp://192.168.1.100:554/stream2",
181
+ inputType: "url"
182
+ },
183
+ {
184
+ type: "text",
185
+ key: "snapshot_url",
279
186
  label: "Snapshot URL",
280
187
  placeholder: "http://192.168.1.100/snapshot.jpg",
281
- inputType: "url",
282
- description: "HTTP URL for fetching JPEG snapshots (optional)"
188
+ inputType: "url"
283
189
  }
284
190
  ]
285
191
  }
@@ -287,51 +193,45 @@ var RtspProvider = class {
287
193
  };
288
194
  }
289
195
  async createDevice(config) {
290
- const name = String(config.name ?? "");
291
- if (!name.trim()) {
292
- throw new Error("Device name is required");
293
- }
294
- const streams = Array.isArray(config.streams) ? config.streams.filter(Boolean).map(String) : [];
295
- const snapshotUrl = config.snapshotUrl ? String(config.snapshotUrl) : void 0;
296
- const mainStreamUrl = streams[0];
297
- if (!mainStreamUrl) {
298
- throw new Error("At least one stream URL is required");
299
- }
300
- const deviceId = `rtsp-${Date.now()}`;
301
- const camConfig = {
302
- id: deviceId,
196
+ const name = String(config["name"] ?? "").trim();
197
+ if (!name) throw new Error("Device name is required");
198
+ const mainStreamUrl = String(config["main_stream_url"] ?? "").trim();
199
+ if (!mainStreamUrl) throw new Error("Main stream URL is required");
200
+ const stableId = sanitizeStableId(name);
201
+ const existing = this.registry.getDeviceByStableId(stableId);
202
+ if (existing) throw new Error(`A device with name "${name}" already exists (stableId: ${stableId})`);
203
+ const device = this.registry.createDevice({
204
+ integrationId: this.integration.id,
205
+ stableId,
206
+ type: "camera",
303
207
  name,
304
- url: mainStreamUrl,
305
- subStreamUrl: streams[1],
306
- snapshotUrl
307
- };
308
- const device = this.buildDevice(camConfig);
309
- this.devices.push(device);
310
- await this.persistDeviceConfigs();
311
- this.ctx.logger.info(`Created RTSP device: ${device.id} (${name})`);
312
- return device;
208
+ enabled: true,
209
+ info: {},
210
+ settings: {
211
+ main_stream_url: mainStreamUrl,
212
+ sub_stream_url: config["sub_stream_url"] ? String(config["sub_stream_url"]) : "",
213
+ snapshot_url: config["snapshot_url"] ? String(config["snapshot_url"]) : ""
214
+ }
215
+ });
216
+ const settings = this.registry.getDeviceSettings(device.id);
217
+ const rtspDevice = this.buildDevice(device, settings);
218
+ this.devices.push(rtspDevice);
219
+ this.ctx.logger.info(`Created RTSP device: ${device.id} / ${stableId} (${name})`);
220
+ return rtspDevice;
313
221
  }
314
222
  subscribeLiveEvents(_callback) {
315
223
  return () => {
316
224
  };
317
225
  }
318
- buildDevice(cam) {
226
+ buildDevice(device, settings) {
319
227
  const deviceCtx = {
320
- id: `device:${this.id}/${cam.id}`,
321
- logger: this.ctx.logger.child(cam.name),
228
+ id: `device:${device.id}`,
229
+ logger: this.ctx.logger.child(device.name),
322
230
  eventBus: this.ctx.eventBus,
323
231
  storage: this.ctx.storage,
324
- config: new ElementConfigStore(`device:${this.id}/${cam.id}`, this.ctx.storage)
232
+ config: this.ctx.config
325
233
  };
326
- return new RtspDevice(cam, this.id, deviceCtx);
327
- }
328
- /**
329
- * Persist all dynamically-created device configs so they survive restarts.
330
- * Saves the full list of camera configs under the 'devices' key in the provider's config.
331
- */
332
- async persistDeviceConfigs() {
333
- const configs = this.devices.map((device) => device.getCameraConfig());
334
- await this.ctx.config.set("devices", configs);
234
+ return new RtspDevice(device, settings, deviceCtx);
335
235
  }
336
236
  };
337
237
 
@@ -341,33 +241,45 @@ var RtspProviderAddon = class {
341
241
  id: "provider-rtsp",
342
242
  name: "RTSP Camera Provider",
343
243
  version: "0.1.0",
344
- description: "Connessione diretta a camere via URL RTSP",
244
+ description: "Direct connection to cameras via RTSP URL",
345
245
  capabilities: ["device-provider"]
346
246
  };
347
247
  provider = null;
348
248
  async initialize(context) {
349
- console.log(`[RtspProviderAddon] initialize called, context.id=${context.id}, config=${JSON.stringify(context.addonConfig)}`);
350
- const config = context.addonConfig;
351
- const providerConfig = {
352
- id: config.id ?? "rtsp-default",
353
- name: config.name ?? "RTSP Cameras",
354
- cameras: config.cameras ?? []
355
- };
356
- this.provider = new RtspProvider(providerConfig, {
249
+ const registry = context.integrationRegistry;
250
+ if (!registry) {
251
+ context.logger.warn("IntegrationRegistry not available \u2014 RTSP provider cannot start");
252
+ return;
253
+ }
254
+ let integration = registry.getIntegrationByAddonId("provider-rtsp");
255
+ if (!integration) {
256
+ integration = registry.createIntegration({
257
+ addonId: "provider-rtsp",
258
+ name: "RTSP Cameras",
259
+ enabled: true,
260
+ info: { discoveryMode: "manual", icon: "assets/icon.svg", color: "#78716c" }
261
+ });
262
+ context.logger.info(`Created RTSP integration: ${integration.id}`);
263
+ }
264
+ if (!integration.enabled) {
265
+ context.logger.info("RTSP integration is disabled \u2014 skipping start");
266
+ return;
267
+ }
268
+ this.provider = new RtspProvider(integration, registry, {
357
269
  id: context.id,
358
270
  logger: context.logger,
359
271
  eventBus: context.eventBus,
360
272
  storage: context.storage,
361
273
  config: context.config
362
274
  });
363
- context.logger.info("RTSP provider addon initialized");
275
+ await this.provider.start();
276
+ context.logger.info(`RTSP provider started (integration=${integration.id}, devices=${this.provider.getDevices().length})`);
364
277
  }
365
278
  async shutdown() {
366
279
  await this.provider?.stop();
367
280
  this.provider = null;
368
281
  }
369
282
  getCapabilityProvider(name) {
370
- console.log(`[RtspProviderAddon] getCapabilityProvider("${name}"), provider=${this.provider ? "exists" : "null"}`);
371
283
  if (name === "device-provider" && this.provider) {
372
284
  return this.provider;
373
285
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/rtsp-device.ts","../src/element-config-store.ts","../src/rtsp-provider.ts","../src/addon.ts"],"sourcesContent":["export { RtspProviderAddon } from './addon'\nexport { RtspDevice } from './rtsp-device'\nexport { RtspProvider } from './rtsp-provider'\nexport type { RtspCameraConfig, RtspProviderConfig } from './rtsp-types'\n","import { DeviceType } from '@camstack/types'\nimport type {\n IDevice,\n DeviceState,\n DeviceMetadata,\n DeviceCapabilityName,\n IDeviceCapability,\n CamstackContext,\n ICamera,\n StreamOption,\n ConnectionMode,\n} from '@camstack/types'\nimport type { RtspCameraConfig } from './rtsp-types'\n\nexport class RtspDevice implements IDevice {\n readonly id: string\n readonly name: string\n readonly providerId: string\n readonly type: DeviceType = DeviceType.Camera\n readonly capabilities: DeviceCapabilityName[]\n readonly ctx: CamstackContext\n\n private readonly capabilityMap = new Map<DeviceCapabilityName, IDeviceCapability>()\n\n constructor(\n private readonly config: RtspCameraConfig,\n providerId: string,\n ctx: CamstackContext,\n ) {\n this.id = `${providerId}/${config.id}`\n this.name = config.name\n this.providerId = providerId\n this.ctx = ctx\n\n // Only camera capability is native to RTSP device.\n // motionSensor and objectDetector are provided by pipeline addons\n // via capability binding (not native to RTSP device).\n this.capabilities = ['camera']\n\n this.capabilityMap.set('camera', this.createCamera())\n }\n\n getCapability<T extends IDeviceCapability>(cap: DeviceCapabilityName): T | null {\n return (this.capabilityMap.get(cap) as T) ?? null\n }\n\n hasCapability(cap: DeviceCapabilityName): boolean {\n return this.capabilityMap.has(cap)\n }\n\n getState(): DeviceState {\n return { online: true }\n }\n\n getMetadata(): DeviceMetadata {\n return { manufacturer: 'Generic RTSP' }\n }\n\n /** Return the camera config for persistence */\n getCameraConfig(): RtspCameraConfig {\n return { ...this.config }\n }\n\n private createCamera(): ICamera {\n const cfg = this.config\n return {\n kind: 'camera',\n\n async getSnapshot() {\n if (cfg.snapshotUrl) {\n const res = await fetch(cfg.snapshotUrl)\n return Buffer.from(await res.arrayBuffer())\n }\n return Buffer.alloc(0)\n },\n\n async getStreamOptions(): Promise<StreamOption[]> {\n const options: StreamOption[] = [\n {\n id: `${cfg.id}_main`,\n label: 'Main',\n protocol: 'rtsp',\n quality: 'main',\n url: cfg.url,\n },\n ]\n if (cfg.subStreamUrl) {\n options.push({\n id: `${cfg.id}_sub`,\n label: 'Sub',\n protocol: 'rtsp',\n quality: 'sub',\n url: cfg.subStreamUrl,\n })\n }\n return options\n },\n\n async getStreamUrl(option: StreamOption) {\n return option.url ?? cfg.url\n },\n\n getConnectionMode(): ConnectionMode {\n return 'always-on'\n },\n\n async setConnectionMode() {},\n }\n }\n}\n","import type { IElementConfig } from '@camstack/types'\nimport type { IStorageLocation } from '@camstack/types'\n\n/**\n * Persisted config store for a single element.\n * Reads/writes to the element's scoped storage under the 'config' collection.\n * Notifies listeners on every change.\n */\nexport class ElementConfigStore implements IElementConfig {\n private cache: Record<string, unknown> = {}\n private listeners: Set<(config: Record<string, unknown>) => void> = new Set()\n private loaded = false\n\n constructor(\n private readonly elementId: string,\n private readonly storage: IStorageLocation,\n ) {}\n\n /** Load config from storage into cache. Called once on first access. */\n private async ensureLoaded(): Promise<void> {\n if (this.loaded) return\n if (!this.storage.structured) {\n this.loaded = true\n return\n }\n\n try {\n const records = await this.storage.structured.query('config', {\n where: { id: this.elementId },\n limit: 1,\n })\n if (records.length > 0) {\n this.cache = (records[0] as any).data ?? {}\n }\n } catch {\n // Storage might not be ready yet\n }\n this.loaded = true\n }\n\n getAll(): Record<string, unknown> {\n return { ...this.cache }\n }\n\n get<T = unknown>(key: string): T | undefined {\n const parts = key.split('.')\n let current: unknown = this.cache\n for (const part of parts) {\n if (current == null || typeof current !== 'object') return undefined\n current = (current as Record<string, unknown>)[part]\n }\n return current as T | undefined\n }\n\n async set(key: string, value: unknown): Promise<void> {\n await this.ensureLoaded()\n setNestedValue(this.cache, key, value)\n await this.persist()\n this.notifyListeners()\n }\n\n async setAll(config: Record<string, unknown>): Promise<void> {\n await this.ensureLoaded()\n this.cache = { ...config }\n await this.persist()\n this.notifyListeners()\n }\n\n onChange(callback: (config: Record<string, unknown>) => void): () => void {\n this.listeners.add(callback)\n return () => { this.listeners.delete(callback) }\n }\n\n /** Initialize from storage — called by ContextFactory after creation */\n async load(): Promise<void> {\n await this.ensureLoaded()\n }\n\n /** Initialize with default values (doesn't overwrite existing) */\n async loadDefaults(defaults: Record<string, unknown>): Promise<void> {\n await this.ensureLoaded()\n if (Object.keys(this.cache).length === 0) {\n this.cache = { ...defaults }\n await this.persist()\n }\n }\n\n private async persist(): Promise<void> {\n if (!this.storage.structured) return\n\n try {\n const existing = await this.storage.structured.query('config', {\n where: { id: this.elementId },\n limit: 1,\n })\n\n if (existing.length > 0) {\n await this.storage.structured.update('config', this.elementId, this.cache)\n } else {\n await this.storage.structured.insert({\n collection: 'config',\n id: this.elementId,\n data: this.cache,\n })\n }\n } catch {\n // Storage might not be ready\n }\n }\n\n private notifyListeners(): void {\n const snapshot = this.getAll()\n for (const listener of this.listeners) {\n try {\n listener(snapshot)\n } catch {\n // Don't let one bad listener kill others\n }\n }\n }\n}\n\nfunction setNestedValue(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.')\n let current: Record<string, unknown> = obj\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]!\n if (!(part in current) || typeof current[part] !== 'object' || current[part] === null) {\n current[part] = {}\n }\n current = current[part] as Record<string, unknown>\n }\n current[parts[parts.length - 1]!] = value\n}\n","import { RtspDevice } from './rtsp-device'\nimport { ElementConfigStore } from './element-config-store'\nimport type {\n IDeviceProvider,\n ProviderStatus,\n DiscoveredDevice,\n LiveEvent,\n IDevice,\n CamstackContext,\n ConfigUISchema,\n} from '@camstack/types'\nimport type { RtspCameraConfig, RtspProviderConfig } from './rtsp-types'\n\nexport class RtspProvider implements IDeviceProvider {\n readonly id: string\n readonly type = 'rtsp'\n readonly name: string\n readonly discoveryMode: 'manual' = 'manual'\n readonly ctx: CamstackContext\n\n private readonly devices: RtspDevice[] = []\n\n constructor(config: RtspProviderConfig, ctx: CamstackContext) {\n this.id = config.id\n this.name = config.name\n this.ctx = ctx\n\n // Create devices from static config (initial seed)\n for (const cam of config.cameras) {\n this.devices.push(this.buildDevice(cam))\n }\n }\n\n async start(): Promise<void> {\n // Load any dynamically-created devices from persisted config\n const persisted = this.ctx.config.get<readonly RtspCameraConfig[]>('devices')\n if (persisted && Array.isArray(persisted)) {\n for (const cam of persisted) {\n const alreadyExists = this.devices.some((d) => d.id === `${this.id}/${cam.id}`)\n if (!alreadyExists) {\n this.devices.push(this.buildDevice(cam))\n }\n }\n }\n\n this.ctx.logger.info(`RTSP provider started with ${this.devices.length} cameras`)\n }\n\n async stop(): Promise<void> {\n this.ctx.logger.info('RTSP provider stopped')\n }\n\n getStatus(): ProviderStatus {\n return { connected: true, deviceCount: this.devices.length }\n }\n\n async discoverDevices(): Promise<DiscoveredDevice[]> {\n // Manual-only provider — no auto-discovery\n return []\n }\n\n getDevices(): IDevice[] {\n return [...this.devices]\n }\n\n getDeviceConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'device-info',\n title: 'Device Configuration',\n description: 'Configure the RTSP camera connection',\n columns: 1,\n fields: [\n {\n type: 'text',\n key: 'name',\n label: 'Device Name',\n placeholder: 'e.g. Front Door Camera',\n required: true,\n },\n {\n type: 'text',\n key: 'snapshotUrl',\n label: 'Snapshot URL',\n placeholder: 'http://192.168.1.100/snapshot.jpg',\n inputType: 'url',\n description: 'HTTP URL for fetching JPEG snapshots (optional)',\n },\n ],\n },\n ],\n }\n }\n\n async createDevice(config: Record<string, unknown>): Promise<IDevice> {\n const name = String(config.name ?? '')\n if (!name.trim()) {\n throw new Error('Device name is required')\n }\n\n const streams = Array.isArray(config.streams) ? config.streams.filter(Boolean).map(String) : []\n const snapshotUrl = config.snapshotUrl ? String(config.snapshotUrl) : undefined\n\n const mainStreamUrl = streams[0]\n if (!mainStreamUrl) {\n throw new Error('At least one stream URL is required')\n }\n\n const deviceId = `rtsp-${Date.now()}`\n\n const camConfig: RtspCameraConfig = {\n id: deviceId,\n name,\n url: mainStreamUrl,\n subStreamUrl: streams[1],\n snapshotUrl,\n }\n\n const device = this.buildDevice(camConfig)\n this.devices.push(device)\n\n // Persist the dynamically-created device configs\n await this.persistDeviceConfigs()\n\n this.ctx.logger.info(`Created RTSP device: ${device.id} (${name})`)\n\n return device\n }\n\n subscribeLiveEvents(_callback: (event: LiveEvent) => void): () => void {\n // RTSP provider has no native events — motion/detection come from pipeline\n return () => {}\n }\n\n private buildDevice(cam: RtspCameraConfig): RtspDevice {\n const deviceCtx: CamstackContext = {\n id: `device:${this.id}/${cam.id}`,\n logger: this.ctx.logger.child(cam.name),\n eventBus: this.ctx.eventBus,\n storage: this.ctx.storage,\n config: new ElementConfigStore(`device:${this.id}/${cam.id}`, this.ctx.storage),\n }\n return new RtspDevice(cam, this.id, deviceCtx)\n }\n\n /**\n * Persist all dynamically-created device configs so they survive restarts.\n * Saves the full list of camera configs under the 'devices' key in the provider's config.\n */\n private async persistDeviceConfigs(): Promise<void> {\n const configs: RtspCameraConfig[] = this.devices.map((device) => device.getCameraConfig())\n await this.ctx.config.set('devices', configs)\n }\n}\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n CapabilityProviderMap,\n ConfigUISchema,\n IConfigurable,\n} from '@camstack/types'\nimport { RtspProvider } from './rtsp-provider'\nimport type { RtspProviderConfig } from './rtsp-types'\n\nexport class RtspProviderAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'provider-rtsp',\n name: 'RTSP Camera Provider',\n version: '0.1.0',\n description: 'Connessione diretta a camere via URL RTSP',\n capabilities: ['device-provider'],\n }\n\n private provider: RtspProvider | null = null\n\n async initialize(context: AddonContext): Promise<void> {\n console.log(`[RtspProviderAddon] initialize called, context.id=${context.id}, config=${JSON.stringify(context.addonConfig)}`)\n const config = context.addonConfig as unknown as Partial<RtspProviderConfig>\n\n // Always create the provider even with zero cameras —\n // users add devices manually via createDevice()\n const providerConfig: RtspProviderConfig = {\n id: config.id ?? 'rtsp-default',\n name: config.name ?? 'RTSP Cameras',\n cameras: config.cameras ?? [],\n }\n\n this.provider = new RtspProvider(providerConfig, {\n id: context.id,\n logger: context.logger,\n eventBus: context.eventBus,\n storage: context.storage,\n config: context.config,\n })\n\n context.logger.info('RTSP provider addon initialized')\n }\n\n async shutdown(): Promise<void> {\n await this.provider?.stop()\n this.provider = null\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n console.log(`[RtspProviderAddon] getCapabilityProvider(\"${name}\"), provider=${this.provider ? 'exists' : 'null'}`)\n if (name === 'device-provider' && this.provider) {\n return this.provider as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'general',\n title: 'RTSP Provider',\n description: 'Configure generic RTSP camera connections',\n columns: 1,\n fields: [\n { type: 'text', key: 'name', label: 'Provider Name', placeholder: 'RTSP Cameras' },\n {\n type: 'info',\n key: 'info',\n label: 'Camera Configuration',\n content: 'Individual cameras are configured via the device management interface after adding this provider.',\n variant: 'info',\n },\n ],\n },\n ],\n }\n }\n\n getConfig(): Record<string, unknown> {\n return {}\n }\n\n async onConfigChange(_config: Record<string, unknown>): Promise<void> {\n // Restart provider with new config\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/rtsp-device.ts","../src/rtsp-provider.ts","../src/addon.ts"],"sourcesContent":["export { RtspProviderAddon } from './addon.js'\nexport { RtspDevice } from './rtsp-device.js'\nexport { RtspProvider } from './rtsp-provider.js'\nexport type { RtspProviderConfig } from './rtsp-types.js'\n","import { DeviceType } from '@camstack/types'\nimport type {\n IDevice,\n DeviceState,\n DeviceMetadata,\n DeviceCapabilityName,\n IDeviceCapability,\n CamstackContext,\n ICamera,\n StreamOption,\n ConnectionMode,\n Device,\n} from '@camstack/types'\n\nexport class RtspDevice implements IDevice {\n readonly id: string\n readonly name: string\n readonly providerId: string\n readonly type: DeviceType = DeviceType.Camera\n readonly capabilities: DeviceCapabilityName[]\n readonly ctx: CamstackContext\n\n readonly stableId: string\n private readonly settings: Record<string, unknown>\n private readonly capabilityMap = new Map<DeviceCapabilityName, IDeviceCapability>()\n\n constructor(\n device: Device,\n settings: Record<string, unknown>,\n ctx: CamstackContext,\n ) {\n this.id = device.id\n this.stableId = device.stableId\n this.name = device.name\n this.providerId = device.integrationId\n this.settings = settings\n this.ctx = ctx\n\n this.capabilities = ['camera']\n this.capabilityMap.set('camera', this.createCamera())\n }\n\n getCapability<T extends IDeviceCapability>(cap: DeviceCapabilityName): T | null {\n return (this.capabilityMap.get(cap) as T) ?? null\n }\n\n hasCapability(cap: DeviceCapabilityName): boolean {\n return this.capabilityMap.has(cap)\n }\n\n getState(): DeviceState {\n return { online: true }\n }\n\n getMetadata(): DeviceMetadata {\n return { manufacturer: 'Generic RTSP' }\n }\n\n private createCamera(): ICamera {\n const mainUrl = String(this.settings['main_stream_url'] ?? '')\n const subUrl = String(this.settings['sub_stream_url'] ?? '')\n const snapshotUrl = String(this.settings['snapshot_url'] ?? '')\n const deviceId = this.id\n\n return {\n kind: 'camera',\n\n async getSnapshot() {\n if (snapshotUrl) {\n const res = await fetch(snapshotUrl)\n return Buffer.from(await res.arrayBuffer())\n }\n return Buffer.alloc(0)\n },\n\n async getStreamOptions(): Promise<StreamOption[]> {\n const options: StreamOption[] = [\n {\n id: `${deviceId}_main`,\n label: 'Main',\n protocol: 'rtsp',\n quality: 'main',\n url: mainUrl,\n },\n ]\n if (subUrl) {\n options.push({\n id: `${deviceId}_sub`,\n label: 'Sub',\n protocol: 'rtsp',\n quality: 'sub',\n url: subUrl,\n })\n }\n return options\n },\n\n async getStreamUrl(option: StreamOption) {\n return option.url ?? mainUrl\n },\n\n getConnectionMode(): ConnectionMode {\n return 'always-on'\n },\n\n async setConnectionMode() {},\n }\n }\n}\n","import { RtspDevice } from './rtsp-device.js'\nimport type {\n IDeviceProvider,\n ProviderStatus,\n DiscoveredDevice,\n LiveEvent,\n IDevice,\n CamstackContext,\n ConfigUISchema,\n Integration,\n Device,\n IIntegrationRegistry,\n} from '@camstack/types'\n\nfunction sanitizeStableId(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-|-$/g, '')\n || `rtsp-${Date.now()}`\n}\n\nexport class RtspProvider implements IDeviceProvider {\n readonly id: string\n readonly type = 'rtsp'\n readonly name: string\n readonly discoveryMode: 'manual' = 'manual'\n readonly ctx: CamstackContext\n\n private readonly integration: Integration\n private readonly registry: IIntegrationRegistry\n private readonly devices: RtspDevice[] = []\n\n constructor(integration: Integration, registry: IIntegrationRegistry, ctx: CamstackContext) {\n this.id = integration.id\n this.name = integration.name\n this.integration = integration\n this.registry = registry\n this.ctx = ctx\n }\n\n async start(): Promise<void> {\n // Load devices from centralized registry\n const registeredDevices = this.registry.listDevices(this.integration.id)\n\n for (const device of registeredDevices) {\n if (!device.enabled) continue\n const settings = this.registry.getDeviceSettings(device.id)\n this.devices.push(this.buildDevice(device, settings))\n }\n\n this.ctx.logger.info(`RTSP provider started with ${this.devices.length} cameras`)\n }\n\n async stop(): Promise<void> {\n this.devices.length = 0\n this.ctx.logger.info('RTSP provider stopped')\n }\n\n getStatus(): ProviderStatus {\n return { connected: true, deviceCount: this.devices.length }\n }\n\n async discoverDevices(): Promise<DiscoveredDevice[]> {\n return []\n }\n\n getDevices(): IDevice[] {\n return [...this.devices]\n }\n\n getDeviceConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'device-info',\n title: 'Device Configuration',\n description: 'Configure the RTSP camera connection',\n columns: 1,\n fields: [\n {\n type: 'text',\n key: 'name',\n label: 'Device Name',\n placeholder: 'e.g. Front Door Camera',\n required: true,\n },\n {\n type: 'text',\n key: 'main_stream_url',\n label: 'Main Stream URL',\n placeholder: 'rtsp://192.168.1.100:554/stream1',\n inputType: 'url',\n required: true,\n },\n {\n type: 'text',\n key: 'sub_stream_url',\n label: 'Sub Stream URL',\n placeholder: 'rtsp://192.168.1.100:554/stream2',\n inputType: 'url',\n },\n {\n type: 'text',\n key: 'snapshot_url',\n label: 'Snapshot URL',\n placeholder: 'http://192.168.1.100/snapshot.jpg',\n inputType: 'url',\n },\n ],\n },\n ],\n }\n }\n\n async createDevice(config: Record<string, unknown>): Promise<IDevice> {\n const name = String(config['name'] ?? '').trim()\n if (!name) throw new Error('Device name is required')\n\n const mainStreamUrl = String(config['main_stream_url'] ?? '').trim()\n if (!mainStreamUrl) throw new Error('Main stream URL is required')\n\n const stableId = sanitizeStableId(name)\n\n // Check if stableId already exists globally\n const existing = this.registry.getDeviceByStableId(stableId)\n if (existing) throw new Error(`A device with name \"${name}\" already exists (stableId: ${stableId})`)\n\n // Create in centralized registry\n const device = this.registry.createDevice({\n integrationId: this.integration.id,\n stableId,\n type: 'camera',\n name,\n enabled: true,\n info: {},\n settings: {\n main_stream_url: mainStreamUrl,\n sub_stream_url: config['sub_stream_url'] ? String(config['sub_stream_url']) : '',\n snapshot_url: config['snapshot_url'] ? String(config['snapshot_url']) : '',\n },\n })\n\n const settings = this.registry.getDeviceSettings(device.id)\n const rtspDevice = this.buildDevice(device, settings)\n this.devices.push(rtspDevice)\n\n this.ctx.logger.info(`Created RTSP device: ${device.id} / ${stableId} (${name})`)\n return rtspDevice\n }\n\n subscribeLiveEvents(_callback: (event: LiveEvent) => void): () => void {\n return () => {}\n }\n\n private buildDevice(device: Device, settings: Record<string, unknown>): RtspDevice {\n const deviceCtx: CamstackContext = {\n id: `device:${device.id}`,\n logger: this.ctx.logger.child(device.name),\n eventBus: this.ctx.eventBus,\n storage: this.ctx.storage,\n config: this.ctx.config,\n }\n return new RtspDevice(device, settings, deviceCtx)\n }\n}\n","import type {\n ICamstackAddon,\n AddonManifest,\n AddonContext,\n CapabilityProviderMap,\n ConfigUISchema,\n IConfigurable,\n IIntegrationRegistry,\n} from '@camstack/types'\nimport { RtspProvider } from './rtsp-provider.js'\n\nexport class RtspProviderAddon implements ICamstackAddon, IConfigurable {\n readonly manifest: AddonManifest = {\n id: 'provider-rtsp',\n name: 'RTSP Camera Provider',\n version: '0.1.0',\n description: 'Direct connection to cameras via RTSP URL',\n capabilities: ['device-provider'],\n }\n\n private provider: RtspProvider | null = null\n\n async initialize(context: AddonContext): Promise<void> {\n // Get integration registry from context (injected by server)\n const registry = (context as any).integrationRegistry as IIntegrationRegistry | undefined\n if (!registry) {\n context.logger.warn('IntegrationRegistry not available — RTSP provider cannot start')\n return\n }\n\n // Find or create the integration entry\n let integration = registry.getIntegrationByAddonId('provider-rtsp')\n if (!integration) {\n integration = registry.createIntegration({\n addonId: 'provider-rtsp',\n name: 'RTSP Cameras',\n enabled: true,\n info: { discoveryMode: 'manual', icon: 'assets/icon.svg', color: '#78716c' },\n })\n context.logger.info(`Created RTSP integration: ${integration.id}`)\n }\n\n if (!integration.enabled) {\n context.logger.info('RTSP integration is disabled — skipping start')\n return\n }\n\n // Create provider with registry access\n this.provider = new RtspProvider(integration, registry, {\n id: context.id,\n logger: context.logger,\n eventBus: context.eventBus,\n storage: context.storage,\n config: context.config,\n })\n\n // Auto-start\n await this.provider.start()\n context.logger.info(`RTSP provider started (integration=${integration.id}, devices=${this.provider.getDevices().length})`)\n }\n\n async shutdown(): Promise<void> {\n await this.provider?.stop()\n this.provider = null\n }\n\n getCapabilityProvider<K extends keyof CapabilityProviderMap>(\n name: K,\n ): CapabilityProviderMap[K] | null {\n if (name === 'device-provider' && this.provider) {\n return this.provider as unknown as CapabilityProviderMap[K]\n }\n return null\n }\n\n getConfigSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'general',\n title: 'RTSP Provider',\n description: 'Configure generic RTSP camera connections',\n columns: 1,\n fields: [\n { type: 'text', key: 'name', label: 'Provider Name', placeholder: 'RTSP Cameras' },\n {\n type: 'info',\n key: 'info',\n label: 'Camera Configuration',\n content: 'Individual cameras are configured via the device management interface after adding this provider.',\n variant: 'info',\n },\n ],\n },\n ],\n }\n }\n\n getConfig(): Record<string, unknown> {\n return {}\n }\n\n async onConfigChange(_config: Record<string, unknown>): Promise<void> {\n // Config changes handled by integration settings\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.mjs CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  RtspDevice,
3
3
  RtspProvider,
4
4
  RtspProviderAddon
5
- } from "./chunk-4KD2JX6K.mjs";
5
+ } from "./chunk-2B5J5HPN.mjs";
6
6
  export {
7
7
  RtspDevice,
8
8
  RtspProvider,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-rtsp",
3
- "version": "0.1.11",
3
+ "version": "0.1.12",
4
4
  "description": "Generic RTSP camera device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",